Легаси-код: как разбирать чужой код десятилетней давности и не сойти с ума
СОДЕРЖАНИЕ
Почему легаси-код — это не только про нервы, но и про деньги
Первые шаги: как влиться в проект без документации
Git-археология: инструмент, который работает лучше документации
Тесты-характеристики: как менять код, не читая его целиком
Майкл Фэзерс, автор классической книги «Working Effectively with Legacy Code», предложил определение, которое перевернуло разговор про старый код: легаси-код — это код без тестов. Не старый, не уродливый, не написанный «плохими» разработчиками — а такой, поведение которого никто не может подтвердить автоматически. Дальше у Фэзерса жестче: код без тестов — это плохой код, каким бы аккуратным и объектно-ориентированным он ни выглядел, потому что менять его без тестов — значит менять его вслепую.
Это определение снимает с легаси эмоциональный груз. Проблема не в том, что коду десять лет. Проблема в том, что нельзя быстро проверить: сломает ли эта правка что-то в другом месте системы. Пятилетний код без тестов — тоже легаси. Десятилетний код с хорошим покрытием — уже почти нет.
Почему легаси-код — это не только про нервы, но и про деньги
Здесь не помешает немного цифр, чтобы не выглядело как персональная драма отдельного разработчика.
По данным McKinsey, около 70% программного обеспечения в компаниях из списка Fortune 500 старше двух десятилетий. Совокупный технический долг в США, по отчету IT-CISQ за 2022 год, достиг 1,52 трлн долларов, а полная стоимость низкого качества софта в стране оценивается в 2,41 трлн долларов за тот же год.
Еще в 2018 году Stripe опросила больше 1000 разработчиков и больше 1000 руководителей уровня C-level в пяти странах для отчета «The Developer Coefficient». Результат: инженеры тратят в среднем 13,5 часа из 41,1-часовой рабочей недели — то есть около трети рабочего времени — именно на технический долг, а если считать всю работу с плохим легаси-кодом и его поддержкой, цифра вырастает до 17,3 часа, или 42% недели. В деньгах это оценили примерно в 85 млрд долларов потерянной производительности по всему миру. Отчету почти семь лет, но именно на эти цифры до сих пор ссылаются, потому что более свежих исследований такого масштаба просто не проводили.
Цена вопроса чувствуется и на уровне людей, не только бюджетов: по данным опроса разработчиков от Storyblok, 58% готовы задуматься об увольнении из-за неудобного легаси-стека, а 73% знают коллегу, который уже уволился по этой причине. Раздражение от старого кода — это не каприз, а измеримый фактор текучки.
Первые шаги: как влиться в проект без документации
Правило простое: сначала понять, потом трогать. Порядок действий, который реально работает на практике:
-
Запустите проект локально и потыкайте руками — прежде чем читать код, стоит увидеть, что он делает как черный ящик.
-
Найдите точки входа: где начинается обработка запроса, откуда стартует основной процесс.
-
Определите границы модуля, который вам нужно менять, — не всей системы сразу.
-
Поговорите с людьми, которые касались этого кода раньше, даже если их ответ будет «уже не помню, но осторожнее с этим файлом».
-
Прежде чем что-либо менять, зафиксируйте текущее поведение тестами — об этом отдельно ниже.
Многие странные на первый взгляд решения в старом коде на самом деле не случайны: они появились из-за ограничений платформы пятилетней давности, требования конкретного клиента или регуляторной нормы, которая с тех пор могла и не измениться. Прежде чем «исправлять странность», полезно на минуту предположить, что автор был не глупее вас — и поискать причину, а не сразу переписывать.
Git-археология: инструмент, который работает лучше документации
Документации в легаси-проектах обычно либо нет, либо она отстала от кода на пару лет. Зато почти всегда есть история версий — и это куда надежнее чьей-то памяти.
Что смотреть в первую очередь:
-
git log -p путь/к/файлу — покажет не только что менялось, но и зачем, если авторы писали внятные сообщения к коммитам;
-
git blame — какая строка когда и кем добавлена, полезно для поиска автора, у которого можно спросить контекст;
-
git bisect — если баг появился «когда-то между», бинарный поиск по коммитам находит виновный коммит быстрее, чем чтение всей истории вручную;
-
связанные issue или pull request по номеру задачи в сообщении коммита — там часто остается обсуждение, почему выбрали именно такое решение.
Git-история — это единственная документация, которая гарантированно не врет: она не могла устареть, потому что зафиксирована в момент изменения.
Тесты-характеристики: как менять код, не читая его целиком
Ключевая техника Фэзерса называется characterization tests — тесты, которые не проверяют, что код работает «правильно» в теории, а фиксируют, что он делает прямо сейчас, по факту. Логика простая: сначала зафиксировать текущее поведение тестами, потом менять код, ориентируясь на то, чтобы тесты не сломались. Понимать весь код построчно для этого не обязательно — достаточно понимать вход и выход конкретного участка.
Для встраивания тестов в код, который изначально не был спроектирован для тестирования, Фэзерс ввел понятие seams (условно — «швы») — мест, где можно изменить поведение программы для теста, не переписывая всю систему целиком. Например, подменить внешнюю зависимость на заглушку в одной точке, вместо того чтобы рефакторить архитектуру ради тестируемости.
Даже минимальное покрытие тестами в критичных местах дает уверенность, что правка не сломает то, что уже работало.
Почему не стоит переписывать все с нуля
Соблазн большой: код выглядит страшно, хочется все выкинуть и начать заново на чистой архитектуре. Проблема в том, что переписывание с нуля почти никогда не укладывается в изначальную оценку сроков — а все это время старая система должна продолжать работать параллельно с разработкой новой.
Альтернативу предложил Мартин Фаулер в статье про паттерн Strangler Fig («удушающий фикус» — растение, которое обвивает дерево-хозяина и постепенно его замещает): новую функциональность строят рядом со старой, постепенно переключая трафик и пользователей на новые компоненты, пока старая система не станет ненужной сама по себе. Риск размазан по времени, а не сконцентрирован в одном большом релизе.
|
Подход |
Когда применять |
Основной риск |
|
Точечный рефакторинг с тестами-характеристиками |
Нужно поправить конкретный баг или добавить фичу в стабильный модуль |
Локальный, контролируемый |
|
Strangler Fig (постепенная замена) |
Система работает, но архитектура мешает расти |
Требует дисциплины и времени, зато без «большого взрыва» |
|
Полное переписывание с нуля |
Система физически не может развиваться дальше (устаревшая платформа, конец поддержки) |
Высокий: сроки почти всегда превышаются, бизнес-логика теряется при переносе |
Здесь же работает «правило бойскаута», сформулированное Робертом Мартином: оставляй код чище, чем он был до тебя. Не обязательно рефакторить весь модуль — достаточно на каждом проходе через файл слегка улучшать то, что видишь.
Частые вопросы
-
Стоит ли переписывать легаси-проект с нуля, если код выглядит очень плохо?
Почти всегда нет. Полное переписывание редко укладывается в изначальные сроки, а бизнес-логика, накопленная за годы, легко теряется при переносе. Безопаснее — постепенная замена по паттерну Strangler Fig или точечный рефакторинг с тестами.
-
Что делать, если в легаси-проекте совсем нет тестов?
Начать с тестов-характеристик на участке, который собираетесь менять: они фиксируют текущее поведение кода, а не «правильное» поведение в теории. Это позволяет менять код безопасно даже без полного покрытия.
-
С чего начать первый день на легаси-проекте без документации?
Запустить проект и посмотреть на него как на черный ящик, затем изучить git-историю нужного модуля — git log -p и git blame — вместо попытки прочитать весь код целиком.
-
Как быстро найти, из-за какого коммита появился баг в старом коде?
git bisect — бинарный поиск по истории коммитов. Он находит проблемный коммит за логарифмическое число шагов, а не за линейный перебор всей истории вручную.
-
Легаси-код — это всегда плохо написанный код?
Нет. По определению Фэзерса, дело не в качестве кода, а в отсутствии тестов. Аккуратно написанный код без тестов — тоже легаси в этом смысле, просто менее болезненный.