Рефакторинг легаси: стратегия малых шагов без остановки
Большой переписываемый модуль редко удаётся заменить за один релиз. Стратегия малых шагов снижает риск: сначала фиксируется текущее поведение, затем код меняется короткими обратимыми сериями.

Легаси-сервис обычно ломается не во время большого рефакторинга, а после него: команда меняет сразу несколько слоёв, не знает, какое поведение было исходным, и обнаруживает регрессию через неделю. Стратегия малых шагов устроена иначе: каждый коммит меняет одну характеристику системы, проходит проверку и остаётся обратимым. Версия методики не привязана к языку или фреймворку; конкретные команды и тестовые инструменты нужно сверять с версией вашего стека.
Почему большой рефакторинг почти всегда дорожает
В легаси-коде редко есть один изолированный дефект. Бизнес-правила смешаны с SQL, HTTP-запросами, логированием и побочными эффектами. Название метода может обещать одно, а вызывающий код рассчитывать на другое. Документация неполная, тесты проверяют только счастливый путь, а владельцы системы помнят исключения, которых нет в коде.
При полном переписывании команда теряет сразу три ориентира:
- непонятно, какое старое поведение действительно нужно сохранить;
- невозможно быстро определить, в каком изменении появилась ошибка;
- результат нельзя частично включить или откатить без возврата всей ветки.
Цена вопроса — не только часы разработчиков. Регрессия в расчёте заказа, начислении комиссии или синхронизации данных может потребовать ручного исправления уже обработанных записей. Поэтому цель первого этапа — не «сделать красиво», а уменьшить неопределённость и размер следующего изменения.
Хороший малый шаг меняет внутреннее устройство, но не меняет наблюдаемое поведение системы.
Базовая стратегия: от границы к деталям
1. Выберите один маршрут пользователя
Не начинайте с папки, класса или всего сервиса. Возьмите конкретный сценарий: создание заказа, выдача отчёта, импорт платежа. Зафиксируйте входные данные, внешний результат, побочные эффекты и зависимости. Такой маршрут ограничивает объём работы и позволяет понять, что именно нельзя случайно изменить.
Определите владельца поведения — человека, который знает бизнес-правило. Если такого человека нет, сохранённые примеры из продакшена становятся временным источником истины, но их нужно пометить как гипотезы.
2. Сначала поставьте наблюдение
До перестановки кода добавьте метрики и структурированные события на границах сценария. Минимальный набор: количество вызовов, доля ошибок, длительность, идентификатор операции и причина отказа без персональных данных. Логи не должны содержать токены, пароли, полные номера карт и другие секреты.
Если система уже измеряет этот маршрут, сохраните базовую линию за несколько рабочих дней. Без неё команда не отличит улучшение от изменения нагрузки.
3. Зафиксируйте текущее поведение
Когда бизнес-правило не описано, используйте тесты-характеристики: они записывают, что система делает сейчас, включая странные, но важные случаи. Это не утверждение, что текущее поведение правильное. Это страховка от незаметной подмены правила во время рефакторинга.
Выберите данные из трёх групп:
- обычный успешный сценарий;
- пустые, граничные и повторные входные данные;
- ошибки внешних систем, тайм-ауты и частично сохранённые операции.
Тест должен проверять результат и значимые побочные эффекты, но не внутреннюю структуру вызовов. Если он требует конкретного порядка вызова пяти приватных методов, это сигнал, что тест слишком связан с реализацией.
4. Введите один шов для изменения
Шов — место, где можно изменить поведение или зависимость, не переписывая весь участок сразу. Им может быть отдельный метод, адаптер, интерфейс внешнего клиента или граница модуля. Сначала вынесите код за шов без изменения логики, затем меняйте реализацию по другую сторону границы.
Например, если расчёт цены напрямую вызывает несколько таблиц и API скидок, не переписывайте расчёт целиком. Сначала выделите получение исходных данных в один адаптер. Так вы получите точку для теста и сможете заменить способ чтения, не трогая правила расчёта.
5. Делайте изменения атомарными
Один коммит — одна проверяемая мысль: переименование, извлечение функции, перенос зависимости, изменение алгоритма. Не смешивайте форматирование, обновление библиотек и исправление логики. Иначе ревьюер не увидит смысл изменения, а откат вернёт не только ошибку, но и полезную часть работы.
После каждого шага должны проходить автоматические тесты, статический анализ и минимальная проверка сценария в окружении, близком к рабочему. Если проверка занимает часы, вынесите быстрый набор в обязательный pipeline, а полный — в отдельный этап.
Какие подходы можно сочетать
Малые шаги — не один приём, а порядок действий. Подход выбирают по главному риску: неизвестное поведение, связность модулей или опасность включения нового пути.
| Подход | Когда применять | Что выигрываете | Чем платите |
|---|---|---|---|
| Тесты-характеристики | Правила не описаны, но есть рабочие примеры | Фиксируете фактический контракт | Можно случайно закрепить старую ошибку |
| Выделение шва | Логика связана с БД, сетью или файловой системой | Изолируете изменение и тестирование | Появляются дополнительные абстракции |
| Стратэгический адаптер | Нужно заменить старый клиент или формат | Сохраняете старый контракт для вызывающего кода | Временно поддерживаете два представления |
| Постепенное включение | Новый путь меняет критичный результат | Ограничиваете радиус сбоя | Усложняете конфигурацию и наблюдение |
В таблице под «стратегическим адаптером» имеется в виду прослойка, которая переводит старый интерфейс в новый. Не добавляйте такую прослойку автоматически: если она только переименовывает вызовы и не изолирует реальную несовместимость, она станет ещё одним слоем легаси.
Как проводить серию изменений
- Сформулируйте инвариант. Например: повторная обработка одного события не создаёт вторую выплату. Инвариант должен быть наблюдаемым, а не выглядеть как «код стал чище».
- Снимите базовую линию. Запишите текущую долю ошибок, задержку и объём операций для выбранного маршрута. Если метрика недоступна, сначала добавьте её, а не делайте вид, что сравнение возможно.
- Сделайте структурное изменение. Переименуйте сущность, выделите функцию или вынесите адаптер без изменения результата.
- Проверьте контракт. Запустите тесты, затем сравните результат с контрольными примерами и метриками.
- Измените одну часть поведения. Например, переключите чтение на новый адаптер, оставив запись старым способом до отдельного шага.
- Оставьте точку отката. Удалите временный флаг после стабилизации либо назначьте владельца и дату его удаления. Бессрочный флаг превращает переходную конструкцию в постоянную.
Граница между рефакторингом и исправлением дефекта должна быть явной. Если в процессе вы нашли неправильный расчёт, сначала решите, нужно ли сохранить старое поведение ради совместимости. Затем оформите изменение отдельным коммитом или хотя бы отдельным пунктом ревью.
Что ломается первым
Тесты проверяют реализацию, а не контракт
После переименования класса приходится менять десятки тестов, хотя пользовательское поведение не изменилось. Такие тесты не защищают от регрессии и замедляют малые шаги. Оставляйте проверки деталей только там, где деталь сама является контрактом: формат сообщения, схема миграции или порядок транзакции.
Ветка рефакторинга живёт слишком долго
За несколько недель основная ветка уходит вперёд, и слияние превращается в повторный рефакторинг. Лучше доставлять нейтральные изменения небольшими порциями. Незавершённый новый путь можно скрыть за конфигурацией, но старый путь должен оставаться рабочим.
Сравнение результатов неполное
Новый код может вернуть правильную сумму, но перестать отправлять событие, очищать кеш или записывать аудит. Для каждого сценария составьте список побочных эффектов и проверяйте их отдельно. Особенно опасны операции, где ответ пользователю формируется раньше фоновой отправки данных.
Команда измеряет только скорость
Ускорение запроса не компенсирует рост ошибок или расхождение данных. Смотрите минимум на четыре показателя: ошибки, задержку, корректность результата и стоимость инфраструктуры. Пороговые значения должны быть определены до включения нового пути, иначе решение о возврате станет спором мнений.
Где стратегия малых шагов не подходит
Если компонент уже не собирается, данные повреждены, а контракт неизвестен, сначала нужна аварийная стабилизация и инвентаризация зависимостей. Малые изменения не заменяют восстановление из резервной копии, миграционный план или остановку опасной записи.
Стратегия также плохо работает, когда команда не может получить обратную связь: нет тестового окружения, доступа к метрикам и владельца бизнес-правила. В таком случае первый шаг — не выделение классов, а создание минимального безопасного контура проверки.
Полная замена оправдана, если старую платформу нельзя безопасно поддерживать, она не покрывает обязательные требования или стоимость каждого изменения выше стоимости миграции. Но даже тогда полезно переносить систему по границам и сравнивать результаты, а не выпускать новый монолит одним переключателем.
Что делать дальше
Выберите один маршрут с умеренным риском, опишите его входы и побочные эффекты, добавьте базовые метрики и зафиксируйте три-пять реальных примеров. Затем запланируйте серию изменений, где каждый коммит можно объяснить одним предложением и откатить без потери независимой работы.
После первого цикла проверьте не количество изменённых строк, а время от изменения до обнаружения регрессии, долю откатов и число временных флагов. Когда эти показатели станут видимыми, рефакторинг перестанет быть отдельным проектом и войдёт в обычную поставку.
Проверка рекламы на риски: Что в макете вызовет вопросы ФАС: превосходные степени, сравнения, обещания результата, маркировка.
Спросить за 40 ₽Источники: Refactoring Catalog — каталог приёмов рефакторинга, Git Documentation — git-revert
Частые вопросы
С чего начать, если в легаси-коде вообще нет тестов?
Нужно ли писать тесты на каждый метод перед рефакторингом?
Что делать, если после малого изменения тесты проходят, а в продакшене появилась ошибка?
Можно ли делать рефакторинг прямо перед релизом?
Как понять, что пора удалять старый путь и feature flag?
Нужна ли отдельная стратегия, если у проекта другая версия языка или фреймворка?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.