Переписування з нуля провалюється. Рухайте систему юніт за юнітом.
Опубліковано
Рік у темряві
Переписування зазвичай починається з плану, з яким ніхто не сперечається. Замінити стару систему, зберегти поведінку, повернутися за рік із чимось, що можна підтримувати.
Далі все відбувається доволі однаково. Команда зникає. Стара система тим часом не стоїть на місці: у ній правлять баги, дописують граничні випадки, хтось просить звіт — і його роблять. Коли нова система нарешті готова, вона виявляється сумлінною заміною версії, якої вже не існує.
А потім нова приходить з іншою поведінкою, і ніхто не може довести, що саме змінилося. Ось де справжня шкода. Забута функція — це тікет. Поведінка, яка тихо поїхала кудись усередині великої кодової бази, — це черга звернень без сліду до причини.
Чому команди все одно обирають цей шлях
Переписування з нуля обирають не від оптимізму. Його обирають тому, що альтернатива виглядає неможливою.
У кодовій базі, що росла роками, немає чистих швів. Змінюєш один файл — ламаються десять інших, бо ніде не зафіксовано, що від чого насправді залежить. Це знання живе в головах людей, яких у команді може вже й не бути. За таких умов поетапні зміни виглядають як смикання за випадкові нитки, а старт з нуля — як єдиний вихід із будівлі.
Тож чесне питання не в тому, чи провалюються переписування. Питання в тому, чи можна взагалі рухати велику заплутану систему частинами, доводячи поведінку кожної частини до і після переносу. Ми хотіли відповідь, яку самі перевірили, тому провели це як R&D-проєкт, а не як експеримент на клієнті.
Спочатку карта, потім рух
Артефакт, якого бракує, — це карта залежностей. Не діаграма, яку хтось намалював по памʼяті, а карта, виведена з самого коду: що звідки викликається.
Щойно така карта зʼявляється, система перестає бути монолітом і стає впорядкованим списком юнітів. Порядок тут — найважливіше в усьому проєкті: кожен юніт переноситься лише тоді, коли те, від чого він залежить, уже на місці. Саме ця послідовність відрізняє поетапну міграцію від каскаду поломок.
Карта дає ще й те, чого не дає жоден план. Вона показує обсяг роботи ще до першого перенесеного рядка. Ви бачите, скільки там юнітів, наскільки заплутані найгірші з них і де насправді сконцентрується робота, — у момент, коли передумати ще нічого не коштує.
Тести фіксують поведінку, API лишається на місці
Карта задає порядок. Вона не каже, чи поводиться юніт так само після переносу. Для цього навколо кожного юніта пишуться характеризаційні тести на його поточну поведінку — ще до того, як його чіпають. Тести описують те, що код робить сьогодні, включно з тим, що виглядає неправильним. Після переносу ті самі тести мають проходити.
З цього випливає дві речі. Поведінка зафіксована, а не припущена. І міграцію може перевірити той, кого не було в кімнаті, бо аргумент тут не «ми були обережні», а набір тестів, який пройшов зелено по обидва боки зміни.
Паралельно публічний API тримається стабільним, поки всередині все змінюється. Суміжні системи продовжують працювати, і нікому не треба планувати темний період. Можна зупинитися між будь-якими двома юнітами й усе одно мати робочу систему — властивість, якої в переписування з нуля немає в жоден момент до самого кінця.
Де тут місце для агентів
Більша частина міграції — це механічний переклад коду, і саме з цим AI-агенти дають раду: великий обсяг, повторювані дії, обмежені визначеним порядком і тестовим гейтом, який або проходить, або ні. Послідовність, архітектурні рішення та ревʼю лишаються за інженерами. Саме цей розподіл робить результат придатним до перевірки, а не просто швидким.
Ми провели це як R&D на legacy-застосунку на 44 000 рядків, розкладеному на карту й упорядкованому в 14 юнітів. Дійшло до 11 із 14 — це 79% системи — з 918+ написаними тестами і 576+ прибраними надлишковими запитами до бази під час перебудови шару даних. Жоден API-ендпоінт не зламався за весь час. Ми зупинилися на 79%, бо метод на той момент був доведений: це був R&D, а не продукт, який ми випускали.
Чого ми не вимірювали: витрачений час, зекономлені гроші чи рядки на день. Такі цифри є в багатьох презентаціях про міграції. Ми їх на цьому запуску не рахували — тому й не наводимо.
Технічна основа була така: Python із FastAPI, асинхронний SQLAlchemy і pytest, графова кластеризація для карти залежностей і агенти Claude Code, які виконували перенос під керуванням інженерів. Ця частина замінна. Карта, тестовий гейт і стабільний API — ні.
Усі цифри й метод — у R&D-кейсі про міграцію legacy.