10 нояб. 2024 г.·6 мин чтения

Правила удаления сгенерированного кода, чтобы избежать сбоев при рефакторингах

Правила удаления сгенерированного кода помогают командам отдельно проверять удаления, раньше выявлять рискованные широкие рефакторинги и избегать случайных поломок перед релизом.

Правила удаления сгенерированного кода, чтобы избежать сбоев при рефакторингах

Почему удаления требуют дополнительного внимания

Команды обычно внимательно проверяют новый код. Удалённый код часто просматривают быстрее. Его меньше читать, дифф выглядит чище, и удаление кажется безопаснее, чем добавление. Именно здесь команды попадают в беду.

Удалённый хелпер может поломать куда больше, чем файл, в котором он находился. Небольшая утилита может использоваться в оформлении заказа, в письмах по оплате, в админ‑экспортах и в фоновой задаче, которая запускается только ночью. Если ревьюер видит лишь «старый код удалён», он может пропустить цепочку вызовов, от которой это зависело.

Риск растёт с масштабом рефактора. В небольшом зачистке вы можете удалить одну неиспользуемую функцию после проверки ссылок. В широком рефакторинге меняются десятки или сотни файлов одновременно. Хелпер исчезает, сгенерированный клиент заменяют, а переименования затрудняют понимание того, что именно пропало. Дифф становится шумным, и люди просматривают его вбегло.

Сгенерированный код увеличивает радиус поражения, потому что он повторяет одни и те же шаблоны по множеству файлов. Одна правка шаблона может удалить методы, поля или обёртки везде одновременно. Код может выглядеть механистично, но реальные фичи всё ещё на нём зависят. Отсутствующий сериализатор, заглушка маршрута или API‑хелпер могут тихо сломать несколько пользовательских сценариев, прежде чем кто‑то заметит.

Именно поэтому команды должны рассматривать удаления кода как отдельный риск, а не как побочный эффект уборки.

Простой пример показывает причину. Команда рефакторит внутренний SDK и удаляет сгенерированный хелпер, который конвертировал даты в формат, ожидаемый старыми сервисами. Тесты в основном приложении всё ещё проходят, потому что моки принимают любую строку. Через несколько часов экспорт счетов ломается, плановые отчёты останавливаются, и в админ‑панели появляется неверная информация. Одно удаление вызвало три разные проблемы.

Правильный вопрос — не «Этот файл выглядит лишним?», а «Кто всё ещё зависит от этого вывода напрямую или косвенно?». Такое небольшое изменение в привычках ревью предотвращает много случайных поломок.

Почему сгенерированный код повышает риск

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

Человек обычно удаляет код по частям и замечает, что выглядит рискованно. Генератор может убрать 200 похожих строк за несколько секунд, даже если эти строки всё ещё обрабатывают реальный краевой случай.

Почему массовые удаления вводят в заблуждение ревьюеров

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

Вот где начинается проблема. Сгенерированный код часто содержит «клей» — скучный на вид код, который при этом выполняет реальную работу. Он маппит поля, ретраит неудачные вызовы, связывает фоновые задачи, проверяет права или сохраняет совместимость старых ответов API. Когда рефакторинг массово удаляет этот клей, приложение может выглядеть нормально в основном пользовательском потоке.

Отсутствующая часть проявляется позже, в местах, которые ревьюеры редко тестируют вручную. Ночная синхронизация падает. Воркер повторных попыток перестаёт повторно отправлять события. Админ‑страница загружает неполные данные. Редкий формат импорта больше не парсится.

Первым ломаются редкие пути

Широкие рефакторинги усугубляют ситуацию, потому что поломка может тихо распространиться. Если один и тот же сгенерированный шаблон встречается в 40 обработчиках и новый шаблон удаляет одну защитную проверку, вы не получите одну ошибку. Вы получите ту же ошибку в 40 местах, каждое с чуть отличающимся симптомом.

Команды, которые быстро выпускают изменения с AI‑генерируемым кодом, часто сталкиваются с этим. Промпт поправили, вызовы хелперов исчезают, тесты проходят по обычным путям, а проблема проявляется только в фоновой задаче через пару дней. К тому времени удаление уже кажется давним и его труднее отследить.

Риск в том, что удаления из генерации масштабируются быстрее человеческого внимания. Когда код исчезает массово, ревьюеры должны предполагать, что часть этих строк содержала тихое, легко пропускаемое поведение.

Разделяйте удаления и обычные правки

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

Более чистый подход прост: когда можете, открывайте отдельные изменения для удалений и для переписей. Первое изменение отвечает на вопрос «Безопасно ли это удалить?». Второе — «Работает ли новый код?». Это разные вопросы и они требуют разного внимания.

Сделайте удаления легко видимыми

Если в вашей команде уже есть правила ревью, дайте удалениям свой отдельный канал. Даже когда оба изменения происходят в рамках одного рефактора, разделите их так, чтобы дифф рассказывал понятную историю. В одном pull request удалите старые сгенерированные клиенты, неиспользуемые хелперы и устаревшую конфигурацию. В следующем добавьте новую структуру, переименуйте модули или регенерируйте код.

Такое разделение помогает ревьюерам замедлиться в нужном месте. Большая переписка часто выглядит шумной, но безвредной. Небольшое удаление может сломать билды, убрать маршрут или удалить флаг конфигурации, который всё ещё нужен в продакшне.

Перечислите в простых словах, что исчезло. Короткая заметка в описании ревью достаточна:

  • Удалено 24 файла
  • Удалено 6 сгенерированных функций
  • Удалено 3 записи конфигурации
  • 2 шага CI больше не используются

Этот список даёт ревьюерам карту до открытия диффа. Он помогает избежать типичной проблемы, когда все замечают переписанные файлы, но никто не проверяет удалённые.

Отметьте изменения с большим количеством удалений в шаблоне ревью тоже. Простая метка, например «требуется ревью удаления», хорошо работает. Назначьте одного ревьюера, который фокусируется только на отсутствии: отсутствующие файлы, импорты, переменные окружения, документация и тесты.

Команды, работающие с Oleg Sotnikov, часто используют такое разделение в широких рефакторингах, потому что это снижает усталость от ревью. Выигрыш — не в скорости, а в ясности.

Представьте обновление сгенерированного API‑клиента, которое одновременно удаляет старые эндпойнты, а слой приложения переименовывает методы. В одном смешанном диффе ревьюер может пропустить, что фоновая задача всё ещё вызывает удалённый метод клиента. В разделённых изменениях этот отсутствующий вызов заметен намного быстрее.

Простой поток ревью для удалений

Широкие рефакторинги ломают рабочий код, когда удаления находятся рядом с переименованиями, перемещениями и регенерацией в одном переполненном диффе. Хорошие правила удаления — в основном про порядок ревью, а не про бумажную волокиту.

Начните с инвентаря. Автор должен перечислить каждый удалённый файл и каждый удалённый символ, который мог бы вызываться другим кодом: обработчики, задачи, общие типы, ключи конфигурации, скрипты и шаблоны. Простая заметка в pull request достаточна. Если ревьюеры не видят полный список удалений в одном месте, они пропустят крайние случаи.

Далее проверяйте удаления по простой последовательности:

  1. Группируйте инвентарь по тому, как код используется. Ревьюьте одну фичу, сервис или библиотеку за раз. Если рефактор затрагивает React‑экран, Go API и Terraform, держите эти удаления в отдельных кусках.
  2. Отслеживайте, кто ещё зависит от каждой группы. Ищите вызовы, тесты, cron‑задачи, CI‑таски, feature‑флаги и значения конфигурации, которые всё ещё указывают на старый путь.
  3. Запустите целевые тесты по путям, которые использовали удалённый код. Не полагайтесь только на общий набор тестов. Один сфокусированный API‑тест, запуск фоновой задачи или быстрая дымовая проверка UI обычно дают больше информации.
  4. Попросите согласование от владельца этой области. Один ревьюер может принять стиль. Но владелец должен подтвердить риск удаления.
  5. Мёржите изменения с большим количеством удалений только после того, как список удалений, проверки зависимостей и результаты тестов совпали. Если ветка всё ещё смешивает большие удаления с несвязанными правками — разделите её снова.

Это немного замедляет ревью, но намного быстрее, чем восстановление кода после продакшн‑поломки. Команды, использующие AI для широких рефакторингов, нуждаются в этой дисциплине ещё больше, потому что дифф может выглядеть чисто при одновременном удалении того, чего никто не хотел убирать.

Реалистичный пример из широкого рефакторинга

Построить лучший AI-рабочий процесс
Установите практичные правила для AI-генерируемого кода, ревью и регенерации.

Команда чистит схему API и регенерирует клиентский код. Дифф сначала выглядит обычно: меньше моделей, меньше вспомогательных файлов и много автоматически обновлённых сигнатур методов. Большинство ревьюеров фокусируются на новом сгенерированном выводе и на коде приложения, который компилируется.

Один удалённый файл остаётся без внимания. Генератор убрал маппер ошибок, который превращал конкретный ответ биллингового API в повторяемую ошибку. Этот маппер выглядел неважным, потому что он был вне потока оформления заказа, который люди тестируют каждый день. После регенерации основной путь покупки всё ещё работает. Клиент может добавить товары, оплатить и увидеть страницу успеха.

Проблема проявляется позже. Когда биллинговый провайдер возвращает временную ошибку, приложение больше не направляет этот ответ в путь повтора. Вместо этого оно считает ошибку окончательной. Пользователи видят ошибку оплаты, служба поддержки получает тикеты, а финансовая команда замечает падение восстановленных платежей через два дня.

Такая ошибка проскальзывает, потому что широкие рефакторинги скрывают удаления в шумном диффе. Если один PR смешивает регенерированные файлы, ручные правки и удаления, ревьюеры тратят внимание на то, что изменилось, а не на то, что исчезло.

Отдельное ревью удаления меняет исход. Один ревьюер проверяет только удалённые файлы и удалённые ветви в существующих файлах. Он задаёт простой вопрос для каждого удаления: «Кто ещё зависит от этого поведения?». В нашем примере старый маппер ошибок связан с ретраями биллинга и с fallback‑путём, используемым при таймауте провайдера.

Обычно такое ревью не занимает много времени. Нужно перечислить каждый удалённый сгенерированный хелпер, проследить, где он использовался, проверить, сохраняет ли регенерированный код то же fallback‑поведение, и запустить один тест, имитирующий задержку или неуспех внешнего ответа.

Команда замечает отсутствие маппинга ретраев до мёржа. Они сохраняют fallback‑код, регенерируют снова и добавляют тест, который провоцирует временную ошибку биллинга.

Вот в чём суть. Обычные тесты оформления заказа прошли, но бизнес всё равно получил реальную проблему с выручкой. Исправление — не больше процедур, а одно сфокусированное ревью удалений перед тем, как широкая правка попадёт в кодовую базу.

Распространённые ошибки, которые приводят к поломкам

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

Ещё одна распространённая ошибка — предположение, что код просто переехал. Ревьюер видит большое удаление, замечает новую папку или переименованный пакет и подписывает PR, не проследив точно поведение. Отсюда и возникают баги. Если функция перемещена, ревьюеры должны найти новое место, проверить, изменились ли все вызывающие её места, и подтвердить, что новая версия обрабатывает те же входные данные.

Один успешный прогон тестов тоже даёт ложное чувство безопасности. Широкие рефакторинги часто проходят основной набор тестов, ломая при этом рискованные пути: фоновые задачи, логику ретраев, вебхуки, админ‑действия и миграции. Полное покрытие тестами звучит успокаивающе, но редко охватывает «грязные» места. Командам нужна короткая ручная проверка самых вероятных к поломке путей, особенно когда удаления касаются общего кода.

Общие модули усугубляют ситуацию. Команда удаляет якобы мёртвый код в одном приложении, но этот код всё ещё питает биллинговый воркер, задачу отчётности или внутренний инструмент другой группы. Если владельцы этих частей не ревьюят изменение, поломка появится позже и будет выглядеть случайной. Удаления в общих областях не должны мёржиться, пока люди, управляющие этими задачами, не подтвердят влияние.

Feature‑флаги — ещё одна ловушка. Инженеры видят новый путь в продакшне и одновременно удаляют старый флаг, fallback‑код и конфиг в том же рефакторинге. Это слишком рано. Трафик переключается поэтапно, и некоторые пользователи или задачи могут ещё некоторое время зависеть от старого варианта. Удаляйте fallback только после того, как логи, метрики и проверки rollout покажут, что старый путь действительно не используется.

Типичная неисправность выглядит так: рефакторинг удаляет старого API‑клиента после обновления генерации для всех вызывающих мест. Веб‑приложение работает, тесты проходят, дифф выглядит чисто. Через два дня ночная синхронизация падает, потому что один фоновой джоб всё ещё использовал старого клиента через общий хелпер. Никто не проследил перенос и не спросил команду, которой принадлежит задача.

Быстрые проверки перед одобрением

Привлечь Fractional CTO
Получите практическую помощь с рефакторингами, архитектурой и рабочими процессами с AI-поддержкой.

Риск удаления особенно высок в последнем проходе ревью, когда удаление выглядит чистым, но всё ещё скрывает сломанный путь. Файл может выглядеть неиспользуемым в основном приложении и при этом питать ночную задачу, админ‑скрипт или инструмент поддержки, который никто не открывал на этой неделе.

Перед одобрением перестаньте смотреть на рефакторинг как на одно большое изменение и изолируйте дифф удалений. Чтение удалённого кода отдельно облегчает обнаружение пропущенных зависимостей.

Короткий проход перед одобрением должен ответить на пять вопросов:

  • Может ли команда назвать пользовательские сценарии, затронутые каждым удалением?
  • Вызывают ли скрипты, запланированные задачи, инструменты импорта или админ‑действия этот код?
  • Сравнивали ли ревьюеры новый вывод генератора со старым шаблоном или старым сгенерированным кодом?
  • Есть ли план отката, если после слияния обнаружится скрытая зависимость?
  • Читал ли кто‑то дифф удаления отдельно?

Широкие рефакторинги обычно терпят неудачу на краях, а не в основном пути. Представьте, что команда удаляет сгенерированные обёртки API, потому что новый клиент теперь обрабатывает весь веб‑трафик. Сайт в staging выглядит хорошо. Через пару часов финансы не могут запустить месячный экспорт, потому что одна внутренняя задача всё ещё импортирует одну из этих обёрток. Проблема не в новом клиенте. Команда пропустила удаление.

Поэтому одобрение здесь должно быть строже. Если удаление касается сгенерированного кода, попросите кого‑то доказать, что старый путь мёртв, что генератор даёт ожидаемую форму, и что команда может быстро откатить изменение.

Когда сохранять, архивировать или регенерировать

Избавиться от сюрпризов при рефакторинге
Поймайте баги от удалений на раннем этапе с CTO, который руководил большими командами разработки.

Удалять сгенерированный код кажется аккуратно, но аккуратно не значит безопасно. Прежде чем удалять что‑то, решите, нужен ли этот код ещё хотя бы одному вызывающему — даже тихому, например cron‑задаче, админ‑скрипту или старому воркеру.

Оставьте код, если хоть один вызывающий всё ещё существует или если замена покрывает только основной путь, оставляя краевые случаи. Это часто случается в широких рефакторингах. Новый сервис может обрабатывать веб‑трафик, а старые фоновые задачи по‑прежнему полагаться на старого сгенерированного клиента. Если удалить слишком рано, поломка проявится позже и в странных местах.

Архивируйте код на короткое время, когда риск отката ещё высок. Это не значит навсегда оставлять мёртвые файлы. Это значит поместить их за явным временным маркером, назначить владельца и поставить дату удаления. Один‑два спринта обычно достаточно. Если дату не назначить, архив превратится в кладбище.

Регенерируйте, когда контракт остаётся верным, но вывод сдрейфовал или потерял поведение в ходе широкой правки. В таком случае решение часто — восстановить вход генератора, аккуратно обновить шаблон и снова получить чистый дифф, вместо того чтобы патчить вывод вручную.

Следующие шаги для более безопасных рефакторингов

Выберите одно правило и начните применять его на этой неделе. Сделайте его коротким, чтобы люди реально следовали. Хорошее первое правило простое: любое удаление в сгенерированных файлах идёт в отдельный коммит или pull request и получает второго ревьюера.

Это одно изменение резко снижает риск. Широкие рефакторинги часто смешивают переименования, перемещения, регенерации и зачистки. Когда удаления лежат в этом шуме, ревьюеры их пропускают.

Короткий шаблон ревью тоже помогает. Попросите отвечать на несколько простых вопросов перед одобрением:

  • Какой файл или блок был удалён?
  • Был ли он рукописным или сгенерированным?
  • Могут ли вы его регенерировать при необходимости?
  • Что от этого зависит во время выполнения или в сборке?
  • Какой тест или ручная проверка подтвердила безопасность удаления?

Назначьте одного человека специально для ревью удалений. Не оставляйте этот шаг тому, кто первым открыл pull request. Этот ревьюер должен проверять радиус поражения, подтверждать, что генерация работает, и настаивать на сохранении файла, переносе в архив или регенерации, если нужно.

Затем измеряйте результат. Для следующих нескольких рефакторингов отслеживайте каждую ошибку, связанную с удалённым кодом, сломанными импортами, отсутствующими сгенерированными ресурсами или некорректной регенерацией. общей заметки достаточно. После трёх‑четырёх рефакторингов закономерности проявятся быстро.

Если команда увидит две поломки из‑за удалённых сгенерированных типов за месяц, это не случайность. Обычно это означает, что процесс ревью вокруг удалений слишком рыхлый.

AI‑помощь в рефакторингах делает это ещё важнее, а не менее. AI может с уверенностью удалять код, при этом пропуская скрытую зависимость. Отдельное ревью удалений замедляет рискованную часть, не замедляя обычные правки.

Если команде нужна помощь в настройке более безопасных AI‑рабочих процессов, Oleg Sotnikov делится практическими советами на oleg.is. Его работа как Fractional CTO и советника стартапов подходит командам, которым нужны ясные правила для генерации кода, рефакторингов и бережных операций.

Начните с малого, делайте правило видимым и следите за следующими рефакторингами. Если баги от удалений уменьшатся — оставьте процесс. Если нет — ужесточите правило до следующего большого изменения.

Часто задаваемые вопросы

Почему удаление сгенерированного кода рискованнее, чем обычного?

Сгенерированный код часто содержит мелкие фрагменты поведения, которые используют многие фичи. Одна правка шаблона может удалить тот же хелпер, защиту или маппер во множестве файлов, из‑за чего одна ошибка быстро распространяется и проявляется в платежах, фоновых задачах, экспортах или админ‑инструментах.

Стоит ли разделять удаления и переписывания в рефакторинге?

Да, когда это возможно. Поместите удаления в один PR, а правки и переименования — в другой, чтобы ревьюеры могли задать по одному ясному вопросу: безопасно ли это удалить, и работает ли новый код.

Что должен включить автор в pull request с большим количеством удалений?

Начните с простого инвентаря того, что исчезло. Перечислите удалённые файлы, удалённые функции, записи конфигурации, скрипты и любые сгенерированные хелперы, чтобы ревьюеры могли просмотреть весь список удалений до открытия диффа.

Почему тесты всё ещё проходят, если удаление уже что‑то сломало?

Зелёный прогон тестов обычно покрывает основные пути, но не редкие углы. Удалённый код часто ломает логику повторных попыток, фоновые задания, вебхуки, админ‑страницы или инструменты импорта — и эти пути могут иметь слабые тесты или широкие мок‑объекты.

Какие области нужно проверять в первую очередь после удаления?

Проверьте места, которые люди редко открывают вручную: cron‑задачи, воркеры, экспорты, скрипты поддержки, админ‑страницы, старые API‑клиенты, feature‑флаги и значения конфигурации, которые всё ещё ссылаются на удалённый код.

Когда стоит архивировать код вместо немедленного удаления?

Оставьте код на короткий срок, если риск отката ещё высок или если могут существовать тихие вызовы. Архивируйте его с явным владельцем и датой удаления — обычно на один‑два спринта. Если дата не назначена, архив превратится в кладбище, которому перестанут доверять.

Как ревьюить большое удаление сгенерированного кода, чтобы ничего не пропустить?

Обрабатывайте дифф удаления как отдельное ревью. Читайте то, что исчезло, без шума переименований и регенераций, затем трассируйте вызовы для каждого удалённого хелпера или файла и выполните один целевой тест на старом пути.

Кто должен подписывать удаления из общих модулей?

Одобряющий должен быть не только по стилю — владелец области должен подтвердить влияние. Владелец воркера, биллингового потока или общего модуля обычно знает, какие скрытые вызовы всё ещё зависят от этого поведения.

Какой план отката нужен перед слиянием удалений?

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

Какое первое правило для более безопасных рефакторингов?

Правило, которое реально будут соблюдать: любое удаление в сгенерированных файлах идёт в отдельный коммит или pull request и получает второго ревьюера. Эта привычка ловит много проблем до продакшна.