Знакомая картина: модуль на 4000 строк, который «писал один человек и ушёл в 2019-м», трогать страшно, а бизнес просит новую фичу к пятнице. Рука тянется к ChatGPT: «перепиши этот класс нормально». В половине случаев это заканчивается зелёным PR, в другой половине — инцидентом в проде через три дня. Разница не в модели, а в том, задали ли вы себе два вопроса до промта: есть ли покрытие тестами и насколько модуль связан с остальной кодовой базой.
Ниже — два сценария работы с legacy через LLM, проверенные промты для каждого и критерий, по которому я выбираю между ними. Не «N промтов для категории», а инструменты под конкретное решение.
Миф первый: «LLM перепишет монолит целиком, и станет хорошо»
Это работает ровно в одном случае — когда модуль почти не связан с другими и у вас есть тесты, которые фиксируют его поведение. Если хоть одно условие не выполняется, полное переписывание по описанию — это лотерея с чужими деньгами. Подробнее — в статье «Юнит-тесты на Python с LLM: 12 промтов от pytest-фикстур до Hypothesis, которые закрывают непокрытые ветки».
Механизм простой. LLM не знает неявных контрактов: какой формат ошибок ловит вызывающий код, какие поля записи читает соседний сервис, где на самом деле используется побочный эффект функции, который в коде выглядит как баг. Переписывая «по описанию», модель восстанавливает эти контракты по догадке. Догадка правдоподобна — и потому опасна: ревью её пропускает, тестов нет, а прод падает.
Обратная сторона: постепенное «выщипывание» функций (вытащили одну ответственность, покрыли тестом, вынесли в отдельный модуль) — медленно. Модуль на 4000 строк так можно разбирать неделями.
Как измерить связанность до того, как трогать код
Не начинайте с рефакторинга. Начните с карты зависимостей — её LLM строит по коду достаточно надёжно, потому что это задача на извлечение, а не на догадку.
Промт:
Ты анализируешь legacy-модуль перед рефакторингом. Вот код модуля [вставь код].
Составь карту зависимостей строго по фактам из кода, без предположений:
1. Что модуль импортирует и какие из этих импортов реально используются (перечисли функции/классы).
2. Кто вызывает этот модуль: найди по именам экспортируемых функций/классов [перечисли имена] и укажи файлы и строки.
3. Какие структуры данных (DTO, схемы БД, JSON-поля) модуль разделяет с внешним миром — перечисли поля и где они читаются/пишутся снаружи.
4. Побочные эффекты: запись в БД, файлы, сеть, глобальное состояние, изменение аргументов на месте.
Для каждого пункта — ссылка на файл и строку. Если данных в переданном коде не хватает, напиши «не видно из кода», а не додумывай.
Последняя строка промта — самая важная. Без неё модель дорисует зависимости, которых нет, и вы будете бояться трогать код, который на самом деле изолирован. С «не видно из кода» вы получаете честный список пробелов: именно туда надо посмотреть руками.
Если по карте выходит, что модуль дёргают из десятка мест и он делит структуры с половиной сервиса — переписывание отменяется, переходим к выщипыванию.
Миф второй: «тесты можно сгенерировать после рефакторинга»
Тесты, написанные после изменения кода, фиксируют новое поведение, включая случайно сломанное. Они зелёные, вы спокойны — а баг уже в проде. Тесты-«страховка» генерируются до правок и по исходному коду.
Промт для генерации страховочных тестов:
Вот legacy-функция [вставь код]. Сгенерируй тесты на pytest, которые фиксируют ТЕКУЩЕЕ поведение, включая странное.
Требования:
- Покрой: happy path, граничные значения, обработку None/пустых, исключения.
- Отдельно отметь места, где текущее поведение выглядит как баг (например, молчаливый возврат None вместо ошибки). Для них напиши тест, который фиксирует поведение КАК ЕСТЬ, с комментарием # legacy behavior, do not change without review.
- Не мокай то, что можно вызвать напрямую; моки — только для сети, БД и времени.
- Для каждой ветки кода укажи, какой тест её покрывает. Если ветку покрыть нельзя без рефакторинга — перечисли такие ветки отдельным списком.
Этот последний список — золото. Он показывает, какие части функции вообще нельзя проверить, не разбив её. Именно с них и начинается выщипывание.
Сценарий «выщипывание»: как просить LLM выделить ответственности
Когда связанность высокая, задача меняется: не переписать, а вытащить одну ответственность за раз, каждый шаг — с тестом и с проверкой, что поведение не изменилось.
Промт для выделения ответственностей:
Разбей эту legacy-функцию/класс на ответственности, не меняя код.
Для каждой ответственности укажи:
1. Название (что она делает по существу, одной фразой).
2. Какие строки исходного кода к ней относятся.
3. Какие данные она читает и какие меняет (входы/выходы/побочные эффекты).
4. Связана ли она с другими ответственностями через общее состояние.
В конце предложи порядок выноса: какую ответственность выносить первой, чтобы разорвать минимум связей, и почему именно её.
Не пиши новый код — только анализ и порядок шагов.
Здесь важен запрет «не пиши новый код». Как только модель начинает генерировать реализацию, вы теряете анализ и получаете правдоподобный, но не проверенный рефакторинг. Сначала карта — потом по одному шагу.
Дальше на каждую ответственность — отдельный промт на извлечение, и после каждого шага прогоняете страховочные тесты. Если тест упал — поведение изменилось, шаг откатывается. Это и есть «не сломав продакшн»: не обещание, а проверяемый цикл.
Сценарий «переписывание»: как не потерять контракты
Если карта зависимостей показала изоляцию и тесты есть — можно переписывать. Но не «перепиши нормально», а с явным списком контрактов, которые нельзя менять. Подробнее — в статье «LeakCanary показал стек — а виноват static context: промты, которые заставляют LLM объяснить OOM в Android».
Промт для переписывания с фиксацией поведения:
Перепиши модуль [код] с нуля. Жёсткие ограничения:
- Публичный API (имена функций, аргументы, типы возврата) не меняется.
- Формат ошибок и коды не меняются.
- Побочные эффекты сохраняются: [перечисли из карты зависимостей].
- Внешние структуры данных не трогаются: [перечисли поля].
Для каждого ограничения покажи, где в новом коде оно соблюдено.
Затем перечисли места, где ты сознательно отошёл от старого кода, и объясни, почему это не ломает поведение. Если уверенности нет — не отходи, отметь как «требует решения человека».
Секция «где отошёл» — это ваш чек-лист для ревью. Модель честно показывает развилки, вместо того чтобы тихо принять решение за вас.
Миф третий: «зелёные тесты = рефакторинг не изменил поведение»
Тесты проверяют то, что вы в них заложили. Если тест не покрывает ветку, изменение в ней пройдёт незамеченным, и зелёный прогон будет ложным спокойствием.
Проверочный промт после рефакторинга:
Вот старый код и новый код [оба блока]. Вот тесты, которые оба проходят.
Найди различия в поведении, которые тесты НЕ ловят:
1. Ветки, покрытые в одном варианте и не покрытые в другом.
2. Изменения в порядке побочных эффектов.
3. Изменения в обработке исключений и граничных значений.
4. Изменения в производительности, которые могут проявиться под нагрузкой (лишние запросы в цикле, N+1).
Для каждого различия — конкретный вход, на котором поведение разойдётся, и тест, который его поймает.
Этот промт — по сути, ручной дифференциальный анализ. Он не гарантирует отсутствие багов, но находит те дыры, которые не видит зелёный CI. По опыту, различия в порядке побочных эффектов и N+1 — самые частые «невидимые» поломки при переписывании.
Что выбрать: короткая таблица
| Условие | Выщипывание | Переписывание |
|---|---|---|
| Покрытие тестами | низкое или отсутствует | есть страховочные тесты |
| Связанность с модулями | высокая | низкая |
| Сроки | недели | дни |
| Риск инцидента | ниже, шаги обратимы | выше, откат — целиком |
| Когда оправдано | живой прод, много вызывающих | изолированный модуль, ясные контракты |
Если сомневаетесь — начинайте с карты зависимостей и страховочных тестов. Это дешёвые шаги, которые нужны в обоих сценариях и которые сразу показывают, какой путь реально доступен. Про то, как LLM читает вывод инструментов и стектрейсы, а не додумывает, есть отдельный разбор: Промты для отладки в браузере: как заставить LLM читать стектрейсы, воспроизводить баги и ставить точки останова.
А когда рефакторинг дошёл до ревью PR — не пропускайте его через ту же модель «на глаз»: Промты для код-ревью: как заставить LLM находить баги до мержа.
С чего начать завтра
Не с промта «перепиши». С двух вопросов: чем я докажу, что поведение не изменилось, и кто ещё зависит от этого кода. Если на первый нет ответа — сначала страховочные тесты. Если на второй нет карты — сначала карта зависимостей.
LLM здесь не заменяет инженерное решение, а ускоряет ту часть, где нужна механическая работа: извлечь зависимости, перечислить ветки, сравнить два варианта кода. Решение «выщипывать или переписывать» остаётся за вами — и теперь у него есть проверяемый критерий, а не интуиция в пятницу вечером.