За восемь лет в разработке я пережил три больших истории про легаси, и первые две закончились одинаково: ночными деплоями, седыми висками и разбором последствий в понедельник. Поэтому когда мне в очередной раз сказали «надо переписать этот монолит», я не побежал создавать ветку с гордым названием, которую никто не увидит. Я сел и написал план, который потом пережил четыре спринта, релизный заморозок и смену продакт-менеджера. Об этом и хочу рассказать.
Контекст был классический: монолит на четыреста с лишним тысяч строк, девять лет истории, покрытие тестами около двенадцати процентов, деплой раз в месяц и двое людей, которые держали всю картину мира в голове. Бизнес в это время хотел фичи каждую неделю. Первый мой заход был честным, но наивным: большая ветка, чистые слои, новые абстракции. Она прожила два спринта, разошлась с основной веткой на сотни конфликтов, не принесла ни одной пользовательской ценности, и её закрыли. И правильно закрыли. Тогда я понял главное: рефакторинг, который соревнуется с продуктом за одно и то же время, всегда проигрывает.
Второй заход я строил как постепенное замещение. Старый код остаётся живым и работающим, а новые куски мы аккуратно наращиваем вокруг него и постепенно вытесняем старое. Никакой даты икс, никакого большого переключения. Мы выносим небольшую часть логики в отдельный модуль, пропускаем трафик через переключатель, сравниваем результаты старого и нового пути на реальных данных и только потом переводим процент трафика. Если становится плохо, переключатель откатывает всё за минуту, а не за ночь.
Но чтобы такой план выжил, ему нужны опоры. Первое: наблюдаемость до начала работ. Если у вас нет логов, метрик и трассировки, вы не докажете ни одной своей победы и не увидите, что что-то поехало. Второе: тесты на критичные сценарии, пусть даже некрасивые и описывающие текущее поведение, включая его странности. Такие тесты не делают код лучше, они делают его защищённым. Третье: договорённости между старым и новым кодом, зафиксированные в виде тестов. Четвёртое: фиксированный бюджет времени на спринт, обычно процентов пятнадцать-двадцать, который нельзя забирать на срочные задачи. Как только бюджет становится переменной, он превращается в ноль.
Отдельно про правило скаута, которое я люблю и ненавижу. Люблю за то, что оно реально работает: тронул файл, оставь его чуть чище. Ненавижу за то, как легко им увлечься и превратить маленькую правку в недельный ремонт. Поэтому у нас было жёсткое ограничение: чистим только то, что нужно для текущей задачи, и только в границах диффа. Всё остальное идёт в отдельный список технического долга, который живёт в том же бэклоге, что и фичи, с реальными приоритетами. Если долг не оценивают в тех же единицах, что и продукт, он никогда не будет сделан.
Держать план живым помогают две вещи: история решений и цифры. Мы завели короткие документы на каждое важное решение, где писали, что выбрали, какие были альтернативы и почему отказались. Через полгода это спасает от повторного изобретения того же колеса. А для бизнеса мы показывали не красоту архитектуры, а время от идеи до релиза, частоту выкаток и количество инцидентов. Продукт понимает эти метрики, поэтому бюджет на рефакторинг перестал быть темой для спора и стал обычной строкой в плане.
Из ошибок, которые я больше не повторяю: переименование всего и сразу, смешивание рефакторинга и фичи в одном большом изменении, отсутствие плана отката и роль одинокого героя, который держит всё в голове. Ещё я перестал называть это рефакторингом на встречах с бизнесом, потому что для них это снижение рисков и ускорение поставки. Формулировка, кстати, не косметика, она меняет отношение к работе.
В итоге за полгода мы вынесли два критичных контура, убрали пару cron-скриптов, о которых знали только по логам падений, и снизили время релиза с недель до дней. Никакого героизма, просто дисциплина и маленькие шаги. А теперь вопрос к вам, форумчане: какой приём помог именно вашей команде удержать рефакторинг живым дольше одного спринта, и что стало для вас самым неожиданным открытием в этом процессе?
Контекст был классический: монолит на четыреста с лишним тысяч строк, девять лет истории, покрытие тестами около двенадцати процентов, деплой раз в месяц и двое людей, которые держали всю картину мира в голове. Бизнес в это время хотел фичи каждую неделю. Первый мой заход был честным, но наивным: большая ветка, чистые слои, новые абстракции. Она прожила два спринта, разошлась с основной веткой на сотни конфликтов, не принесла ни одной пользовательской ценности, и её закрыли. И правильно закрыли. Тогда я понял главное: рефакторинг, который соревнуется с продуктом за одно и то же время, всегда проигрывает.
Второй заход я строил как постепенное замещение. Старый код остаётся живым и работающим, а новые куски мы аккуратно наращиваем вокруг него и постепенно вытесняем старое. Никакой даты икс, никакого большого переключения. Мы выносим небольшую часть логики в отдельный модуль, пропускаем трафик через переключатель, сравниваем результаты старого и нового пути на реальных данных и только потом переводим процент трафика. Если становится плохо, переключатель откатывает всё за минуту, а не за ночь.
Но чтобы такой план выжил, ему нужны опоры. Первое: наблюдаемость до начала работ. Если у вас нет логов, метрик и трассировки, вы не докажете ни одной своей победы и не увидите, что что-то поехало. Второе: тесты на критичные сценарии, пусть даже некрасивые и описывающие текущее поведение, включая его странности. Такие тесты не делают код лучше, они делают его защищённым. Третье: договорённости между старым и новым кодом, зафиксированные в виде тестов. Четвёртое: фиксированный бюджет времени на спринт, обычно процентов пятнадцать-двадцать, который нельзя забирать на срочные задачи. Как только бюджет становится переменной, он превращается в ноль.
Отдельно про правило скаута, которое я люблю и ненавижу. Люблю за то, что оно реально работает: тронул файл, оставь его чуть чище. Ненавижу за то, как легко им увлечься и превратить маленькую правку в недельный ремонт. Поэтому у нас было жёсткое ограничение: чистим только то, что нужно для текущей задачи, и только в границах диффа. Всё остальное идёт в отдельный список технического долга, который живёт в том же бэклоге, что и фичи, с реальными приоритетами. Если долг не оценивают в тех же единицах, что и продукт, он никогда не будет сделан.
Держать план живым помогают две вещи: история решений и цифры. Мы завели короткие документы на каждое важное решение, где писали, что выбрали, какие были альтернативы и почему отказались. Через полгода это спасает от повторного изобретения того же колеса. А для бизнеса мы показывали не красоту архитектуры, а время от идеи до релиза, частоту выкаток и количество инцидентов. Продукт понимает эти метрики, поэтому бюджет на рефакторинг перестал быть темой для спора и стал обычной строкой в плане.
Из ошибок, которые я больше не повторяю: переименование всего и сразу, смешивание рефакторинга и фичи в одном большом изменении, отсутствие плана отката и роль одинокого героя, который держит всё в голове. Ещё я перестал называть это рефакторингом на встречах с бизнесом, потому что для них это снижение рисков и ускорение поставки. Формулировка, кстати, не косметика, она меняет отношение к работе.
В итоге за полгода мы вынесли два критичных контура, убрали пару cron-скриптов, о которых знали только по логам падений, и снизили время релиза с недель до дней. Никакого героизма, просто дисциплина и маленькие шаги. А теперь вопрос к вам, форумчане: какой приём помог именно вашей команде удержать рефакторинг живым дольше одного спринта, и что стало для вас самым неожиданным открытием в этом процессе?