Как понять, что программу пора переписывать
Иллюзия устаревания: почему возраст кода не повод для переписывания
Старый код часто выглядит как очевидный кандидат на переписывание.
Он написан несколько лет назад. В нём давно никто не хочет разбираться. Симптомы типичны:
- › новая функция требует изменений в пяти местах;
- › разработчик, который создавал систему, уже не работает в компании;
- › тестов мало, документация устарела.
Каждый релиз сопровождается фразой:
«Только бы ничего не сломать».
В такой ситуации очень легко принять эмоциональное решение:
Но возраст системы сам по себе ничего не доказывает.
Написан десять лет назад
При этом может оставаться устойчивым, понятным и относительно дешёвым в сопровождении.
Относительно новый стек
Может оказаться настолько связанным, плохо протестированным и дорогим в изменении, что его развитие уже становится проблемой для бизнеса.
Поэтому правильный вопрос звучит иначе:
Это принципиальная разница.
Старый код — не диагноз
У программного обеспечения нет срока годности, после которого его автоматически нужно заменять.
Если система:
- › выполняет свои функции;
- › выдерживает необходимую нагрузку;
- › соответствует требованиям безопасности;
- › поддерживается командой;
- › позволяет выпускать необходимые изменения;
- › имеет приемлемую стоимость эксплуатации,
то сам факт, что её код написан давно, не является причиной для переписывания. В инженерии гораздо важнее не дата появления кода, а стоимость его изменения.
Представим две системы.
Архитектура хорошо известна, основные модули отделены друг от друга, тесты покрывают критические сценарии, развёртывание автоматизировано.
Добавление небольшой функции требует:
- • найти зависимости в нескольких модулях;
- • вручную проверить десятки сценариев;
- • изменить несколько связанных таблиц;
- • выполнить ручное тестирование;
- • несколько раз исправить ошибки после релиза.
Получается парадокс:
Поэтому смотреть только на год разработки бессмысленно.
Что такое технический долг на практике
Технический долг часто используют как универсальное объяснение любой проблемы старой системы. Но сам по себе технический долг — не обязательно катастрофа.
Если команда сознательно выбирает более быстрое решение сегодня, понимая, что позже его придётся улучшить, это тоже форма технического компромисса.
Проблема начинается тогда, когда внутреннее устройство системы постепенно увеличивает стоимость каждого следующего изменения.
Каждая следующая задача становится немного дороже предыдущей. Именно так технический долг начинает проявляться экономически: не в виде красивой метрики, а в виде лишнего времени, риска и стоимости изменений.
Martin Fowler описывает эту идею через «проценты» технического долга: чем хуже внутренняя структура системы, тем больше дополнительной работы требуется для очередных изменений.
Главный показатель — не количество старого кода, а динамика
Одна дорогая задача ещё ничего не означает. Иногда сложная функция действительно требует много работы. Гораздо интереснее посмотреть на несколько последних месяцев.
Если аналогичные изменения систематически становятся дороже, это уже архитектурный сигнал. Особенно если одновременно растёт количество дефектов.
Это намного полезнее, чем утверждение: «Нашему приложению уже десять лет».
Признак №1. Любое изменение начинает затрагивать всё
Один из наиболее неприятных симптомов — отсутствие локальности изменений.
Допустим, нужно добавить новое поле в заказ. На первый взгляд задача простая. Но выясняется, что заказ используется:
- › в API;
- › в отчётах;
- › в расчёте стоимости;
- › в интеграции с ERP;
- › в мобильном приложении;
- › в обмене с внешней системой;
- › в нескольких фоновых обработчиках.
И изменение одной сущности начинает выглядеть так:
Такая связность сама по себе не означает, что систему нужно переписать. Но она показывает, что границы ответственности в системе стали слишком дорогими для изменений.
И тогда первым кандидатом на модернизацию должен быть не весь проект. Нужно найти наиболее проблемный участок.
Признак №2. Разработчик боится менять код
Это звучит субъективно, но на практике является очень полезным индикатором.
Если опытный разработчик перед изменением говорит: «Лучше этот код не трогать», важно выяснить почему.
Одно дело — обоснованная инженерная осторожность вокруг критического финансового расчёта.
Совсем другое — ситуация, когда никто не может объяснить, какие последствия вызовет изменение обычного бизнес-правила.
Особенно опасны системы, где знания существуют только в головах отдельных сотрудников:
Здесь проблема уже не только в старом коде.
Знания о системе перестали быть частью самой системы и процессов компании.
Признак №3. Исправление одного дефекта создаёт другой
Если ошибки регулярно возникают в соседних частях системы после seemingly небольших изменений, это может указывать на чрезмерную связанность.
В таком случае бессмысленно считать каждый дефект отдельной случайностью. Нужно посмотреть на структуру системы.
Иногда проблема находится не в конкретной строке кода, а в том, как компоненты зависят друг от друга.
Признак №4. Производительность упирается не в сервер
Медленная система не всегда означает, что её пора переписывать.
Иногда проблема решается:
- › индексом;
- › оптимизацией запроса;
- › кэшированием;
- › изменением структуры данных;
- › очередью фоновых задач;
- › настройкой инфраструктуры;
- › разделением чтения и записи.
«Программа тормозит, давайте перепишем»
— практически бесполезна без диагностики.
Нужно сначала установить, где находится ограничение.
Если приложение тратит 90% времени, ожидая внешний сервис, переписывание внутреннего кода может почти ничего не изменить.
Признак №5. Безопасность уже нельзя поддерживать нормально
Здесь ситуация принципиально отличается от обычного технического долга.
Если система использует неподдерживаемые компоненты, устаревшие библиотеки или архитектурные решения, которые невозможно привести к современным требованиям безопасности без масштабных изменений, вопрос становится не только экономическим.
Появляются риски:
- - уязвимостей;
- - невозможности установить обновления;
- - отсутствия современных механизмов аутентификации;
- - неконтролируемого доступа;
- - проблем с аудитом;
- - невозможности выполнить требования безопасности компании.
Но и здесь важно не путать: «Старый компонент» с «Небезопасный компонент».
Версия сама по себе не определяет уровень риска.
Нужно смотреть на конкретную технологию, способ эксплуатации, доступность обновлений и фактическую поверхность атаки.
Признак №6. Бизнес хочет менять систему быстрее, чем она способна меняться
Это один из самых важных критериев.
Представим компанию, которая хочет запускать новую услугу раз в месяц. Но техническая команда может безопасно выпускать крупные изменения раз в квартал. Получается конфликт:
быстрее рынка
└── тестирование
└── исправления
└── ручная проверка
└── релиз
└── снова сначала
В этот момент технический долг становится ограничением бизнес-модели. Именно здесь модернизация может иметь экономический смысл.
Когда переписывание действительно оправдано
Полная переработка системы имеет смысл, когда одновременно выполняется несколько условий.
Например:
- › существующая архитектура мешает реализовывать ключевые требования;
- › стоимость изменений постоянно растёт;
- › критические части невозможно нормально тестировать;
- › используемый технологический стек больше нельзя поддерживать;
- › производительность нельзя исправить локальными изменениями;
- › требования безопасности невозможно обеспечить без фундаментальных изменений;
- › система настолько связана, что выделение отдельных компонентов практически невозможно;
- › бизнес действительно получает выгоду от новой архитектуры.
Последний пункт особенно важен.
Техническое желание написать красивый код ещё не является бизнес-причиной для переписывания.
Когда переписывать не стоит
Есть ситуации, когда rewrite почти наверняка будет неправильным первым шагом.
Некрасивый код может отлично выполнять бизнес-функцию.
Если он плохо читается, но изменения стоят приемлемо, проблема может решаться рефакторингом.
Старая технология может быть стабильной и хорошо поддерживаемой.
Нужно оценивать реальные ограничения, а не возраст технологии.
Микросервисы — архитектурный инструмент, а не лекарство от технического долга.
Если просто перенести старую связанную систему в двадцать сервисов, связность никуда не исчезнет. Она может стать распределённой.
Теперь вместо одного сложного приложения появляется распределённая система со сложными сетевыми зависимостями.
«Проще написать заново»
Это одна из самых опасных фраз. Старая система содержит не только код. Она содержит накопленное знание о бизнесе.
И значительная часть этого знания может быть нигде не задокументирована.
Самая опасная часть rewrite — то, чего нет в документации
Представим, что старая система рассчитывает стоимость доставки.
Стоимость зависит от веса и региона.
Но в коде за десять лет накопились исключения:
- › Если регион = X → специальный тариф
- › Если клиент = Y → другой коэффициент
- › Если вес > Z → отдельная формула
- › Если заказ создан в выходной → дополнительное правило
- › Если товар относится к категории Q → исключение
- › Если способ доставки = N → старая интеграция
Разработчик нового проекта может даже не знать о существовании половины этих правил.
Поэтому «переписать один в один» невозможно без глубокого исследования поведения старой системы.
И здесь возникает фундаментальная проблема:
Код — лишь одна часть спецификации.
Почему большой rewrite часто превращается в движущуюся цель
Пока команда год или два пишет новую систему, старую никто не отменяет. Бизнес продолжает работать.
Появляются:
- › новые функции;
- › новые интеграции;
- › новые правила;
- › изменения законодательства;
- › новые пользователи;
- › новые данные.
Получается:
В какой-то момент новая система должна не просто заменить старую. Она должна заменить старую систему в её новой версии, которая уже успела измениться.
Это одна из причин, почему длительные проекты полной замены так трудно удерживать в первоначальных сроках.
Альтернатива: менять систему по частям
Во многих случаях разумнее не уничтожать старую систему, а постепенно уменьшать её ответственность.
Подход часто называют Strangler Fig — по аналогии с растением, которое постепенно обрастает вокруг исходного дерева. Идея давно используется как модель постепенной замены legacy-систем: новая функциональность появляется рядом со старой, а отдельные части системы постепенно переводятся на новую реализацию.
Практический смысл такого подхода в том, что переход становится последовательностью небольших изменений, а не одной огромной датой запуска.
Microsoft также описывает этот паттерн как постепенную замену частей legacy-системы с сохранением работающей старой системы до момента полного переноса.
Но постепенная модернизация — не магия
У неё тоже есть цена.
Какое-то время приходится поддерживать:
+
Новая система
+
Интеграционный слой
+
Синхронизация данных
Поэтому возникает временная архитектура. Она может быть сложнее конечной.
Но её преимущество в другом: риск распределяется по этапам.
Можно перенести один функциональный блок, проверить его на реальных данных, получить обратную связь и только потом двигаться дальше.
Это особенно важно для критических систем, где ошибка полной миграции может остановить бизнес.
Что именно переносить первым
Ошибка — начинать с самого большого модуля только потому, что он самый большой.
Лучше искать участок, который одновременно:
- › создаёт заметную бизнес-боль;
- › имеет понятные границы;
- › достаточно независим;
- › позволяет измерить результат;
- › не требует переписывания всей системы сразу.
Например:
├── Заказы
├── Клиенты
├── Документы ← кандидат
├── Отчётность
├── Платежи
└── Справочники
Можно сначала заменить работу с документами. Если новая реализация работает независимо, постепенно уменьшается зона ответственности legacy-системы.
Это даёт не только результат. Это даёт опыт самой миграции.
Команда узнаёт, где находятся реальные зависимости, какие данные критичны, какие правила нигде не описаны и какие предположения о старой системе были ошибочными.
Иногда переписывать нужно не программу, а отдельный слой
Очень важное наблюдение: проблема может находиться только в одном уровне системы.
Например, локализация деградации по слоям приложения:
API нормально
Бизнес-логика сложно менять
База данных нормальная
Инфраструктура нормальная
В таком случае переписывать всё приложение бессмысленно. Можно заменить только проблемный слой.
Другой пример распределения ограничений:
База данных стала ограничением
Интеграции работают
Тогда задача может заключаться в изменении модели данных, разделении нагрузки или постепенной миграции данных.
Граница переписывания должна совпадать с границей проблемы.
Это один из наиболее полезных принципов при модернизации.
Как оценить решение до начала rewrite
Перед тем как объявлять большой проект, полезно ответить хотя бы на несколько вопросов.
1. Что именно стало неприемлемым?
Не «код плохой», а конкретные измеримые параметры:
- - время разработки;
- - количество дефектов;
- - производительность;
- - безопасность;
- - стоимость инфраструктуры;
- - невозможность интеграции;
- - отсутствие поддержки технологии.
2. Можно ли устранить проблему локально?
Если да, полный rewrite может оказаться избыточным.
3. Сколько стоит текущее состояние?
Нужно оценить не только зарплату разработчиков. Формула включает скрытые издержки:
=
разработка
+
поддержка
+
инциденты
+
простой
+
ручные операции
+
замедление новых проектов
+
стоимость ошибок
4. Сколько будет стоить новая система?
Причем нужно считать не только разработку. Добавляются накладные расходы:
- › миграция;
- › тестирование;
- › двойная эксплуатация;
- › обучение;
- › перенос данных;
- › интеграции;
- › поддержка переходного периода.
5. Как будет доказано, что новая система лучше?
Нужны измеримые критерии сравнения:
Было: релиз → 3 недели
Цель: релиз → 2 дня
Было: 95-й перцентиль API → 1,8 с
Цель: 95-й перцентиль → 300 мс
Было: изменение модуля → 12 дней
Цель: изменение → 3 дня
Без таких показателей rewrite легко превращается в проект ради самого проекта.
Стоит считать не стоимость переписывания, а стоимость двух вариантов
Правильное сравнение выглядит примерно так:
Причём сравнивать нужно не только ближайшие шесть месяцев. Если текущая система ежегодно становится на 15–20% дороже в изменении, это уже другой экономический сценарий.
В какой-то момент технический долг начинает приносить такой большой «процент», что инвестиции в модернизацию становятся рациональными.
Есть ещё один важный вопрос: что именно означает «переписать»
Под этим словом часто смешивают совершенно разные действия. Важно разделять подходы:
Внешнее поведение сохраняется, внутренняя структура улучшается.
↓
рефакторинг
↓
Более поддерживаемый код
Система получает новые технологические или архитектурные возможности, но бизнес-функции сохраняются.
Переписывается отдельный модуль или контур.
Новая система постепенно принимает на себя ответственность старой.
Система создаётся заново и затем целиком заменяет существующую.
Это пять разных стратегий с разным уровнем риска.
Поэтому фраза «нам нужно переписать систему» ещё ничего не говорит о конкретном плане работ.
Самая плохая стратегия — переписать всё и только потом проверить
Представим классический сценарий «большого взрыва» при полной замене системы:
А дальше выясняется:
- - одно бизнес-правило забыли;
- - один внешний контрагент работает иначе;
- - исторические данные имеют исключения;
- - пользователи используют функцию, которой не было в документации;
- - производительность на реальной нагрузке отличается от тестовой;
- - часть старых интеграций невозможно отключить.
Чем позже обнаруживается такая проблема, тем дороже её исправление.
Поэтому хороший процесс модернизации старается уменьшать неопределённость постепенно, а не переносить её на день финального запуска.
Что делать перед принятием решения
Полезно начать не с написания нового кода, а с обследования.
Минимальный набор:
Нужно понять:
- › какие части системы действительно критичны;
- › где находятся самые сильные зависимости;
- › какие данные являются источником истины;
- › какие интеграции нельзя нарушить;
- › какие функции используются на самом деле;
- › где находится основная нагрузка;
- › какие изменения наиболее дороги;
- › какие участки можно отделить;
- › какие результаты должна дать модернизация.
После этого уже можно выбирать стратегию.
Иногда лучший результат — вообще не переписывать систему
Это важный вывод, который легко потерять в разговорах о legacy. После обследования может оказаться, что для решения проблем достаточно точечных мер.
Реальная причина: плохие индексы
Решение: оптимизация БД
Реальная причина: всё выполняется синхронно
Решение: очередь фоновых задач
Реальная причина: один связанный модуль
Решение: выделить его и постепенно переработать
И только иногда комплекс факторов требует радикальных шагов:
архитектурные ограничения
+
высокая стоимость изменений
+
неустранимые технологические ограничения
+
бизнесу нужна другая скорость
↓
полноценная модернизация
Это и есть инженерный подход.
Не искать повод переписать.
И не защищать старую систему любой ценой.
Система должна оправдывать стоимость своего существования
У работающего legacy-приложения есть важное преимущество: оно уже решает реальную бизнес-задачу. Это нельзя недооценивать.
В нём находятся:
- › годы эксплуатации;
- › данные;
- › интеграции;
- › исключения;
- › пользовательские привычки;
- › знания о процессах.
Поэтому начинать с нуля только потому, что код выглядит плохо, — всё равно что снести работающий завод из-за того, что чертежи его первого цеха давно устарели.
Иногда это действительно необходимо.
Но сначала нужно понять, какая часть здания мешает производству и что именно даст новое строительство.
Когда переписывание становится оправданным
Есть простой практический критерий.
Если система всё ещё работает, но каждое изменение становится всё дороже, опаснее и медленнее, нужно смотреть не на её возраст, а на кривую стоимости изменений.
Если эту кривую можно вернуть вниз локальным рефакторингом — не нужен rewrite.
Если проблема решается заменой одного компонента — не нужен rewrite всей системы.
Если систему можно постепенно разделить — вероятно, безопаснее идти поэтапно.
Если же архитектурные ограничения затрагивают сам фундамент и стоимость дальнейшего развития систематически превышает стоимость модернизации, тогда полная переработка может стать разумным решением.
Главное правило
Программу не пора переписывать потому, что она старая.
Её пора серьёзно модернизировать тогда, когда существующая архитектура начинает ограничивать способность бизнеса изменяться — а локальные исправления уже не возвращают систему к приемлемой стоимости, скорости, безопасности и надёжности.
И даже тогда «переписать всё» — только один из вариантов. Альтернативные тактические сценарии:
- › Иногда правильное решение — рефакторинг.
- › Иногда — заменить один модуль.
- › Иногда — вынести отдельный контур.
- › Иногда — изменить базу данных или инфраструктуру.
- › Иногда — постепенно построить новую систему рядом со старой и переносить ответственность частями.
А иногда действительно требуется полный rewrite.
Но определить это можно только после того, как станет понятно, где именно находится ограничение и сколько бизнес платит за его сохранение. Именно поэтому хорошая модернизация начинается не с выбора нового языка программирования.
Она начинается с вопроса:
«Что в текущей системе уже стало слишком дорогим — и можно ли решить именно эту проблему, не переписывая то, что продолжает работать хорошо?»
Если ответ найден точно, дальнейшая архитектура становится значительно менее вопросом вкуса и значительно больше инженерным решением.
API, Webhook или очередь сообщений?
Когда API есть, а интеграция всё равно не работает
Как соединить производство и корпоративные системы
Все инженерные материалы и экспертные статьи
Полный алфавитный справочник ИТ-терминов
🔒 Данные и доступ к системам заказчика обрабатываются в соответствии с согласованными требованиями к безопасности и конфиденциальности. Условия доступа, хранения и обработки данных определяются архитектурой проекта и договором.
Сайт носит исключительно информационный характер и не является публичной офертой в соответствии со статьёй 437 Гражданского кодекса РФ. Цены, состав и условия предоставления услуг уточняются при проектировании решения и фиксируются в договоре. Условия технического сопровождения и уровень сервиса могут дополнительно определяться отдельным соглашением (SLA).
Автоматизация бизнеса и ИИ-решения под ключ
Самозанятый Скопец Антон Викторович ИНН 741709260870