Когда переписать проект с нуля выгоднее рефакторинга
Рефакторинг — не всегда правильный ответ. Если команда уже боится трогать систему, а баги возвращаются в одни и те же модули, цена дальнейших точечных правок может превысить цену полного переписывания. Разбираем, как отличить одну ситуацию от другой на цифрах.

Коротко: если любая мелкая фича требует правок в пяти не связанных местах, разработчики боятся трогать конкретные файлы, а покрытие тестами падает от релиза к релизу — рефакторинг уже не решает проблему, и дешевле спланировать переписывание. Если таких признаков нет, рефакторинг почти всегда остаётся более дешёвым и предсказуемым путём.
Вопрос «рефакторить или переписать» на практике звучит реже, чем следовало бы: команды рефакторят по инерции, пока не упираются в стену, где каждое изменение превращается в риск. У этого решения есть измеримая граница — и по другую её сторону переписывание с нуля становится не авантюрой, а рациональным управленческим выбором, а не признанием поражения.
Если граница уже пройдена, разумнее не тратить ещё один квартал на точечные правки, а честно признать масштаб задачи. В этом случае нужна команда, которая возьмётся переписать проект под ключ с нуля, если рефакторинг уже не спасает — с готовым процессом миграции и приёмки, а не героическим прыжком в неизвестность силами и без того перегруженной внутренней команды.
Дальше — как отличить ситуацию «ещё можно спасти рефакторингом» от «дешевле переписать», сравнение в цифрах и в формате таблицы, конкретный паттерн поэтапной замены Strangler Fig и чек-лист, который стоит пройти до того, как звать подрядчика.
Рефакторинг или переписывание — в чём разница на уровне решения
Рефакторинг — это изменение внутренней структуры кода без изменения его внешнего поведения: система продолжает делать ровно то же самое, но код становится проще читать, тестировать и менять. Переписывание с нуля — это создание новой реализации той же функциональности, которая на определённом этапе полностью замещает старую систему, а не улучшает её постепенно изнутри.
Разница не в объёме правок, а в стратегии управления риском. Рефакторинг двигается маленькими безопасными шагами: тесты остаются зелёными на каждом коммите, система всё время работает в проде, а откатить отдельное изменение можно за минуты. Переписывание сознательно принимает больший разовый риск ради того, чтобы не тащить архитектурные ограничения старой системы в новую — и окупается только тогда, когда эти ограничения уже стоят дороже самого риска миграции.
Выбор между двумя стратегиями — управленческое решение, а не чисто техническое. Опытный инженер почти всегда предпочтёт рефакторинг: он привычнее, предсказуемее и реже ломает прод прямо сейчас. Но это не значит, что рефакторинг объективно дешевле — просто его цена размазана по времени небольшими порциями и поэтому менее заметна, чем единоразовая цена переписывания.
Классический предостерегающий пример — переписывание браузера Netscape в конце 1990-х, которое Джоэл Спольски разобрал в эссе «Things You Should Never Do»: команда остановила выпуск новых версий почти на три года ради полного переписывания с нуля, и за это время конкуренты успели забрать значительную часть рынка. Урок не в том, что переписывание всегда ошибка, а в том, что переписывание «всем составом и сразу», без поэтапного выпуска промежуточных результатов, несёт риск, сопоставимый с риском самой проблемы, которую оно должно решить.
Какие признаки говорят, что рефакторинг больше не спасает
Рефакторинг работает, пока проблема локализована: один неудачный модуль, один устаревший паттерн, один класс, который разросся сверх меры. Он перестаёт работать, когда проблема системная — то есть воспроизводится в разных частях кодовой базы одновременно, а не концентрируется в одном месте, которое можно изолированно переписать.
- Любая небольшая фича требует согласованных правок в пяти и более формально не связанных местах кода
- Разработчики открыто избегают определённых файлов и просят в ревью «не трогать этот модуль без крайней необходимости»
- Тесты либо отсутствуют на критичных путях, либо настолько хрупкие, что падают от изменений, логически с ними не связанных
- Онбординг нового разработчика в проблемный модуль занимает недели вместо дней, типичных для остальной кодовой базы
- Архитектурное решение, заложенное в основании системы — например, синхронная монолитная модель данных, — прямо противоречит текущим требованиям к нагрузке или масштабированию
- Команда уже проводила точечный рефакторинг этого участка два-три раза, но метрики вроде цикломатической сложности и динамики тестового покрытия не улучшаются между итерациями
Один-два признака из списка — обычный повод планово зарефакторить конкретный участок, и это дешевле и быстрее переписывания. Три и больше одновременно, да ещё распределённые по всей кодовой базе, а не сосредоточенные в одном модуле, — сигнал, что рефакторинг лечит симптом в одном месте, пока причина продолжает действовать во всех остальных.
Если несколько из этих сигналов уже отслеживаются в рамках учёта технического долга (см. материал о том, как его считать), сравнение в динамике за два-три квартала почти всегда даёт однозначный ответ быстрее, чем команда успевает годами спорить об этом на словах, опираясь только на общее ощущение.
Когда переписывание выгоднее рефакторинга — четыре ситуации
Даже при системных признаках из предыдущего раздела решение переписать систему стоит принимать по конкретным, а не ощущаемым критериям. Вот четыре ситуации, в которых переписывание действительно обходится дешевле продолжения рефакторинга, а не просто кажется более интересной задачей для команды.
- Стек или платформа физически выходят из поддержки. Если фреймворк больше не получает обновлений безопасности, а наём разработчиков под устаревший стек становится отдельной проблемой, рефакторинг внутри того же стека лишь откладывает неизбежное переписывание, одновременно продолжая накапливать на него же дополнительный долг.
- Архитектура прямо противоречит новым бизнес-требованиям, а не просто неаккуратна. Монолит, спроектированный для одного региона и одной валюты, не станет мультирегиональным через серию локальных рефакторингов — здесь меняется не качество кода, а структурное решение, заложенное в фундамент системы.
- Стоимость поддержки растёт быстрее, чем стоимость разработки нового функционала. Если команда тратит на устранение инцидентов и регрессий в старой системе больше времени, чем на новые фичи, а тренд не переламывается кварталами, рефакторинг уже не возвращает вложенные в него часы.
- Команда, которая писала систему, недоступна, а документации и тестов практически нет. Рефакторинг кода требует понимания его замысла; если этот замысел нигде не зафиксирован и спросить не у кого, безопаснее задокументировать требования заново через переписывание с тестами-характеристиками, чем гадать, что можно менять в непрозрачном коде.
Если хотя бы одна из этих четырёх ситуаций применима к проекту в чистом виде, а не частично, экономика обычно уже на стороне переписывания — вопрос только в том, как провести его с минимальным риском для продакшена, об этом дальше.
Сравнение рефакторинга и переписывания
Короткая таблица ниже не заменяет расчёт для конкретного проекта, но помогает быстро проверить интуицию: если по большинству строк ответ совпадает с колонкой «переписывание», стоит углубляться в детальную оценку именно этого варианта. Важно и обратное: если ответы разделились примерно поровну, это обычно значит, что проблема локальна и полное переписывание избыточно — достаточно выделить проблемный модуль и рефакторить именно его, не трогая остальную систему.
| Критерий | Рефакторинг | Переписывание |
|---|---|---|
| Риск для продакшена в моменте | Низкий — изменения маленькие и обратимые | Выше, особенно на этапе переключения трафика |
| Срок до результата | Растянут, результат виден постепенно | Концентрирован, результат виден после завершения этапа |
| Нужна ли параллельная команда на старую систему | Нет — работает та же команда | Часто да, если старую систему нельзя заморозить на время миграции |
| Когда окупается | Когда проблема локальна и не системна | Когда проблема системная или архитектурная |
| Типичная ошибка | Рефакторинг без метрик — непонятно, помогает ли он | Переписывание одним большим релизом вместо поэтапной замены |
Strangler Fig Pattern — как снизить риск при переписывании

Strangler Fig Pattern — это стратегия замены системы по частям: новый код растёт рядом со старым, постепенно забирая на себя всё больше функциональности, пока старая система не останется без единого реального вызова и её не выключат без риска для пользователей. Название отсылает к дереву-душителю, которое прорастает вокруг хозяина и со временем полностью занимает его место.
- Выделить границы модуля. Определить один логически замкнутый кусок функциональности — например, биллинг или авторизацию, — который можно переписать и протестировать независимо от остальной системы.
- Поднять новый код рядом со старым за прокси или роутингом. Входящие запросы по-прежнему идут в старую систему, но появляется слой, который умеет направить часть трафика в новую реализацию без изменений на стороне клиентов. На практике роль такого прокси чаще всего играет уже существующий API-гейтвей или балансировщик нагрузки — заводить отдельный сервис-маршрутизатор ради одной миграции обычно избыточно, если в инфраструктуре уже есть точка, через которую проходит весь входящий трафик.
- Переключать трафик частями. Сначала на новую реализацию переводят долю трафика или отдельный сегмент пользователей, сверяют результат со старой системой и постепенно увеличивают долю до ста процентов.
- Выключить старый код, когда новый покрыл всё. Только после того как весь трафик стабильно обрабатывается новой реализацией в течение разумного периода, старый модуль удаляют — а не раньше, из опасения, что откат понадобится снова.
Главное преимущество паттерна — в любой момент миграции можно остановиться или откатиться, потому что старая система продолжает существовать и работать параллельно. Это принципиально отличается от переписывания «всё и сразу», где точки отката фактически нет после финального переключения.
Паттерн особенно хорошо сочетается с переходом от монолита к модульной структуре: вместо того чтобы сразу проектировать полноценные микросервисы, можно для начала выделить выносимый модуль внутри того же процесса — подход, похожий на модульный монолит, — и только при реальной необходимости вынести его в отдельный сервис на следующем шаге, не делая архитектурный выбор необратимым раньше времени.
Как не потерять функциональность и данные при переписывании
Самый частый провал переписывания — не техническая сложность новой системы, а тихая потеря нюансов бизнес-логики, которые никто не документировал, но на которые давно полагаются пользователи или смежные системы.
Тесты-характеристики (characterization tests) фиксируют фактическое поведение старой системы на реальных и граничных входных данных, без оценки, правильное это поведение или нет. Их задача — дать объективный критерий, повторяет ли новая реализация старую, а не полагаться на память команды о том, как система «должна» работать.
Параллельный прогон (shadow run) — ещё один слой защиты: новая система обрабатывает тот же трафик, что и старая, но её ответ не возвращается пользователю, а только логируется и сравнивается со старым. Расхождения, найденные на этом этапе, почти всегда дешевле исправить до переключения, чем после — когда расхождение уже стало видно пользователям или бухгалтерии.
План отката должен существовать до переключения, а не придумываться в момент инцидента: что именно откатывать, за какое время, какие данные синхронизировать обратно. Отсутствие такого плана — главная причина, почему переписывания, формально готовые технически, откладывают выкатку на недели из чистой осторожности.
Отдельно стоит зафиксировать, кто принимает решение об откате и по каким критериям — не только инженерным. Если новая система технически работает, но по ней растёт количество обращений в поддержку или падает конверсия в оформлении заказа, бизнес-метрики должны останавливать выкатку так же, как упавшие технические тесты, а не оставаться поводом для спора постфактум.
Сколько стоит отказ от решения
Отложенное решение — тоже решение, просто с невидимой на первый взгляд ценой. Перевести её в деньги можно простой формулой: часы команды, которые уходят на инциденты и обходные пути именно из-за устаревшей архитектуры, умноженные на стоимость часа разработчика.
Пример: если за месяц команда тратит 60 часов на разбор инцидентов в legacy-модуле биллинга при средней стоимости часа 2 500 ₽, прямые издержки отказа от решения составляют 150 000 ₽ в месяц — то есть 1,8 млн ₽ в год, не считая отложенной скорости разработки новых функций, которую отдельно посчитать сложнее, но которая обычно превышает прямые издержки.
Отдельно стоит считать не только часы на инциденты, но и упущенную скорость запуска нового функционала: если временное решение в legaсy-модуле блокирует выход платной функции на две недели, а ожидаемая выручка от неё — 500 000 ₽ в месяц, цена задержки входит в расчёт отдельной строкой и чаще всего оказывается весомее, чем часы, потраченные на сам инцидент.
Эта формула не претендует на бухгалтерскую точность — её задача перевести спор «надо бы когда-нибудь переписать» в конкретную сумму за период, понятную тем, кто принимает решение о бюджете, а не только тем, кто пишет код.
Чек-лист перед тем, как выбрать подрядчика на переписывание
Если решение переписать систему принято и команда обращается к внешнему подрядчику, часть работы по подготовке всё равно останется на стороне заказчика — без неё даже опытная команда разработки будет собирать требования месяцами вместо того, чтобы сразу приступить к миграции.
- Техническое задание на уровне поведения, а не реализации. Что система должна делать для пользователя и смежных систем, а не как это реализовано сейчас в коде — реализацию как раз меняют.
- Явные границы системы. Какие интеграции, базы данных и внешние сервисы затрагивает переписывание, а какие остаются неизменными и просто продолжают получать те же запросы.
- Критерии приёмки по каждому модулю. Конкретные сценарии и граничные случаи, на которых новая система должна вести себя так же, как старая — лучше в виде тестов-характеристик, а не текстового описания.
- План поэтапной выкатки. Будет ли миграция идти через Strangler Fig по модулям или единым релизом, и кто отвечает за решение об откате на каждом этапе.
- Доступ к знаниям о текущей системе. Контакт с человеком, который может ответить на вопрос «а почему это работает именно так», даже если он больше не пишет код в проекте.
- Выделенное окно на тестовую миграцию с данными, похожими на продовые по объёму и структуре. Без таких данных тесты-характеристики проверяют только придуманные сценарии и пропускают именно те граничные случаи, которые чаще всего всплывают уже после запуска у реальных пользователей.
Подрядчик, который просит эти пункты до старта работ, а не после первого найденного расхождения, обычно экономит заказчику месяцы по сравнению с командой, готовой начать писать код сразу после короткого созвона.
Частые вопросы
Как понять, что мы не поторопились с решением переписать систему?
Проверьте, что совпадают минимум два независимых сигнала: метрика (растущее время на типовую фичу, падающее покрытие тестами) и организационный признак (разработчики боятся трогать модуль, онбординг занимает недели). Одного ощущения «код плохой» недостаточно для решения такого масштаба.
Можно ли переписать только часть системы, а не всё целиком?
Да, и это обычно правильнее полного переписывания. Strangler Fig Pattern как раз про это: вы выделяете границы одного модуля, переписываете его, переключаете трафик и повторяете для следующего, вместо того чтобы останавливать всю разработку на месяцы ради одного большого релиза.
Сколько времени обычно занимает переписывание проекта с нуля?
Зависит от масштаба и выбранной стратегии: точечная замена одного модуля через Strangler Fig может занять 4-8 недель, полное переписывание среднего по размеру сервиса — от нескольких месяцев до года. Любая оценка «за две недели перепишем всё» для системы с реальной бизнес-логикой почти всегда занижена.
Что делать, если переписывание уже началось, но буксует?
Проверить, не пытается ли команда переписать всё одним релизом вместо поэтапной замены. Если да — разбить оставшуюся работу на модули и выкатывать по частям через прокси или feature-флаг, не дожидаясь полной готовности новой системы.
Нужно ли тестовое покрытие старой системы перед тем, как её переписывать?
Да, хотя бы в виде тестов-характеристик (characterization tests), которые фиксируют текущее поведение системы как есть, без оценки, правильное оно или нет. Без них нет способа проверить, что новая реализация действительно повторяет старую, а не теряет нюансы бизнес-логики, которые никто не документировал.
Что опаснее — недооценить сложность переписывания или переоценить её?
Недооценка опаснее на практике: она приводит к остановленному в середине пути переписыванию, когда старая система уже частично выключена, а новая ещё не готова. Переоценка в худшем случае затягивает решение, но не оставляет продукт в подвешенном состоянии между двумя системами.
Как выбрать между своей командой и внешним подрядчиком для переписывания?
Внутренняя команда хороша, когда у неё есть запас мощности и опыт миграций такого масштаба; если обеих вещей нет одновременно, разумнее привлечь подрядчика с готовым процессом, а внутреннюю команду держать на стороне бизнес-экспертизы и приёмки.
Что произойдёт, если просто продолжать точечно латать систему, не выбирая ни один из вариантов?
Чаще всего — медленный рост стоимости любой доработки без явного кризисного момента, который заставил бы принять решение. По сути это тот же технический долг, только без осознанного плана выплаты: команда продолжает тратить время на обходные решения, пока очередной крупный инцидент или уход ключевого разработчика не сделает выбор за неё — обычно в худший момент.



