«Let well alone»: почему в больших проектах мы перестали удалять код
Введение
Многие из нас начинали с книг Роберта Мартина и свято верили: чистый код — это святое. Мёртвый код нужно безжалостно удалять, рефакторинг проводить каждый спринт, а технический долг — гасить немедленно. Эта догма впитывается с первых месяцев работы.
Но если вы когда-нибудь работали в проекте, который живёт больше пяти лет, в high‑load системе или enterprise‑среде, вы наверняка сталкивались с парадоксом: самые стабильные части системы — это те, к которым никто не прикасается годами.
В этой статье я хочу поговорить о крамольной для многих идее: удаление кода — это не всегда благо, а рефакторинг в долгоживущих системах часто становится роскошью, которую бизнес не может себе позволить. Мы разберём, почему принцип «не трогай работающее» — это не лень, а зрелая инженерная стратегия, и как архитектурно подойти к сосуществованию старого и нового кода без тотальных переписываний.
1. История, которая всё меняет
В конце 1990‑х инженеры NASA работали над зондом Deep Space 1. На его борту летел код, написанный в 1970‑х для предыдущих миссий. Молодые инженеры предлагали переписать устаревшие модули на более современные языки, провести рефакторинг, улучшить архитектуру.
Руководитель программы отказал. Его аргумент звучал примерно так:
«Этот код работает 20 лет. Мы знаем каждое его поведение в космосе. Если мы его перепишем, мы не узнаем, что сломается, пока не станет слишком поздно. Оставьте его в покое».
Этот случай — классический пример принципа «Let well alone». В инженерной практике он означает: если компонент стабилен, решает свою задачу и не мешает развитию системы, его не трогают, даже если он выглядит «некрасиво» с точки зрения современных стандартов.
NASA в итоге не переписывало код, а оборачивало его в новые адаптеры и интерфейсы, добавляя функциональность снаружи, не затрагивая проверенное ядро.
2. Проблема: догма «удали мёртвый код»
Почему же мы так стремимся удалять и переписывать? Всё начинается с благих намерений:
- Упрощение поддержки — меньше кода, меньше проблем.
- Улучшение читаемости — избавление от «запахов».
- Снижение технического долга — идейный долг нужно возвращать.
Но в реальных проектах, особенно с высокой нагрузкой и многолетней историей, удаление кода несёт риски, которые часто перевешивают преимущества.
2.1 Риск регрессов
Удаление даже, казалось бы, «мёртвого» кода может привести к падению в самых неожиданных местах. Почему?
- Скрытые зависимости. Код может вызываться через reflection, динамическую загрузку классов, по имени в конфигах, через RPC.
- Тестовое покрытие. В старых системах тесты часто покрывают только happy path. Удаление части кода может нарушить бизнес-логику, которая вообще не покрыта автотестами.
- Особенности данных. В продакшене могут существовать данные, которые активируют ветки кода, не используемые в тестовых сценариях годами.
2.2 Потеря институциональной памяти
Когда уходит команда, которая писала модуль, а через год новый разработчик находит «странный» код и удаляет его, потому что «он нигде не вызывается» — это классическая история катастрофы. Оказывается, код обрабатывал миграцию данных, которая запускается раз в полгода, или содержал логику, критичную для compliance.
2.3 Смена вендоров и поставщиков
В enterprise-среде часто используются решения от вендоров, которые поставляют кастомизированные модули. Удаление «лишнего» кода может привести к тому, что вендор откажется поддерживать систему, так как «изменена базовая функциональность».
3. Архитектурный подход: Strangler Fig наоборот
В классической литературе рекомендуют паттерн Strangler Fig (фиговое дерево-удушитель) : постепенно заменять старые компоненты новыми, пока старое приложение не умрёт.
Но я предлагаю посмотреть на этот паттерн с другой стороны: мы не убиваем старое, мы строим новое рядом и переключаем трафик, оставляя старый код в живых как страховку.
3.1 Две стратегии сосуществования
3.2 Feature Toggles как инструмент безопасности
Один из самых мощных инструментов для безопасного сосуществования кода — feature toggles (флаги функций) .
Вместо того чтобы удалять старый код, мы:
- Пишем новую реализацию.
- Оборачиваем вызов в проверку флага.
- Включаем флаг для небольшого процента пользователей.
- Если всё хорошо — увеличиваем процент.
- Если что-то пошло не так — мгновенно откатываем флаг.
При этом старый код физически остаётся в репозитории и в билде ещё долгое время, иногда годами.
4. Пример кода: как оставить старый код без вреда для проекта
Покажу на примере Spring Boot (Java), но принцип применим к любому фреймворку.
4.1 Оборачиваем легаси в интерфейс
Допустим, у нас есть старый сервис расчёта скидок, написанный 5 лет назад. Мы не уверены, что новая реализация покроет все edge cases.
4.2 Фасад с флагом
Создаём фасад, который решает, какой сервис вызвать.
4.3 Изоляция через профили Spring
Для более сложных случаев можно использовать профили и условные бины.
4.4 Тесты: игнорируем старый код
Чтобы тесты не падали из-за легаси, мы можем исключать его из покрытия или мокать.
5. Схема архитектуры
Ниже представлена схема постепенного переключения трафика с монолита на микросервис с сохранением старого кода в качестве fallback.
Пояснение к схеме:
- API Gateway принимает запросы и на основе feature flags решает, куда направить трафик.
- Монолит продолжает существовать и обрабатывает часть запросов. Код монолита не удаляется.
- Микросервис постепенно начинает обрабатывать увеличивающийся процент трафика.
- Feature Toggle Service позволяет в реальном времени менять процент трафика и мгновенно откатываться.
- Старый код живёт в репозитории и в продакшене 2–3 года, пока новая система не наберёт достаточную статистику надёжности.
6. Когда всё-таки нужно удалять?
Я не призываю никогда не удалять код. Есть ситуации, когда удаление необходимо:
- Комплаенс и безопасность. Если в старой версии есть уязвимости (например, использование устаревших библиотек с известными CVE).
- Законодательные требования. GDPR, хранение персональных данных — иногда старые модули нарушают новые законы.
- Смена технологического стека. Когда старая технология перестаёт поддерживаться (например, устаревшая версия Java без обновлений безопасности).
- Высокая стоимость поддержки. Если старый модуль требует уникальных знаний, а специалистов на рынке больше нет.
Но даже в этих случаях удаление должно происходить постепенно, с сохранением возможности отката, а не за одну ночь.
7. Выводы
Зрелость инженера измеряется не количеством удалённых строк кода, а количеством часов uptime после деплоя.
В больших, долгоживущих системах принцип «Let well alone» — не проявление лени или консерватизма. Это стратегический подход к управлению рисками:
- Стабильность важнее красоты. Система, которая работает 5 лет без падений, ценнее системы, которая переписана на современном стеке, но падает раз в месяц.
- Удаление кода — это операция с высоким риском. Перед удалением нужно быть уверенным на 100%, что код действительно мёртвый, а это требует времени и инструментов (анализ покрытия, мониторинг вызовов в продакшене).
- Сосуществование старого и нового — это норма. Feature toggles, паттерн Strangler Fig, адаптеры — это инструменты, позволяющие развивать систему без остановки.
- Технический долг не всегда нужно гасить. Иногда дешевле оставить долг, если его обслуживание дешевле, чем риски рефакторинга.
NASA не зря летает уже полвека. Иногда лучший рефакторинг — это тот, который вы не сделали.