# Рефакторинг legacy-кода с ИИ без поломок в продакшене

URL: https://agentvera.dev/ru/blog/refactor-legacy-code-with-ai/ · Опубликовано: 2026-10-25

Как безопасно проводить рефакторинг legacy-кода с ИИ-агентами: сначала характеризационные тесты, мелкие обратимые шаги, миграции, ревью и откат.

**Коротко:**

- Прежде чем агент изменит legacy-код, пусть он зафиксирует текущее поведение характеризационными тестами; без этой страховки рефакторинг не отличить от переписывания.
- Работайте мелкими шагами с отдельными коммитами, каждый из которых оставляет тесты зелёными, и не смешивайте изменение поведения с рефакторингом, чтобы любой шаг можно было откатить отдельно.
- Миграции вроде перехода с JavaScript на TypeScript или обновления фреймворка лучше идут по модулю за раз, а основную работу пусть делают компилятор, тесты и кодмод.
- В AgentVera каждый шаг может идти в своём worktree, проходить ревью и проверку в конце хода агента и попадать на свежий main через очередь слияния.

Рефакторинг legacy-кода с ИИ — это передача очень быстрому и очень уверенному исполнителю той части кодовой базы, которую никто до конца не понимает. Это может пройти хорошо. Агенты терпеливы к файлам на 3000 строк, им не лень переименовать функцию в двухстах местах, и они читают старый код без вздохов. А может пройти плохо: «уборка», которая меняет правило округления, теряет никем не задокументированный побочный эффект или превращается в переписывание, которое невозможно проверить.

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

## Почему legacy-код труден и для агентов

Legacy-код — это код, который страшно менять, обычно потому что в нём мало тестов и много неписаных правил. То, что делает его трудным для людей, делает его трудным и для агента:

- **Поведение живёт в неожиданных местах.** Формат даты в хелпере, повторная попытка в декораторе, глобальная переменная, которую читает кто-то ещё.
- **Некоторые баги — это фичи.** Соседняя система может зависеть от неправильного значения. «Исправление» ломает интеграцию, о которой вы даже не знали.
- **Мёртвый код выглядит живым, а живой — мёртвым.** Рефлексия, вызовы по строковому имени, cron-задачи и шаблоны вызывают функции, которые не находит никакой поиск.
- **Окно контекста конечно.** Агент видит файлы, которые прочитал. О пакетной задаче в другом репозитории, импортирующей этот модуль, он не узнает.

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

## Шаг 1: сначала постройте страховку

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

Работающий промпт:

> Не меняй код в `src/`. Напиши характеризационные тесты для `src/orders/pricing.js`, которые фиксируют его текущее поведение, включая всё, что выглядит неправильным. Покрой все ветки, которые найдёшь, включая пути ошибок. Где поведение похоже на баг, оставь в тесте проверку текущего результата и добавь комментарий, начинающийся с `SUSPICIOUS:`. Запусти тесты и покажи, что они проходят.

Несколько советов:

- **Тестируйте через публичный интерфейс.** Тесты, вызывающие модуль так же, как остальное приложение, переживают рефакторинг. Тесты, лезущие в приватные хелперы, ломаются, когда вы их переносите.
- **Используйте реальные входные данные.** Примеры запросов из логов (очищенные от персональных данных), файлы фикстур или копия staging-базы покрывают странные случаи гораздо лучше выдуманных входов.
- **Эталонные файлы здесь уместны.** Для генератора отчётов или экспорта записать весь вывод и сравнивать с ним — быстрый и честный характеризационный тест, если вывод детерминирован.
- **Проверьте, что тесты умеют падать.** Сломайте код вручную в паре мест или прогоните по модулю инструмент мутационного тестирования. Если тесты остались зелёными, в страховке дыры.

Подробнее о характеризационных тестах, покрытии и мутационном тестировании — в руководстве [как писать юнит-тесты с ИИ](/ru/blog/write-unit-tests-with-ai/).

Пометки `SUSPICIOUS:` ценны. Это баги, которые вы исправите позже, намеренно, в отдельных коммитах, когда кто-то решит, что новое поведение правильное.

## Шаг 2: рефакторинг мелкими обратимыми шагами

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

### Отделяйте структуру от поведения

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

> Это чистый рефакторинг. Не меняй поведение, не исправляй баги, не меняй публичные сигнатуры. Если видишь что-то, что стоит изменить, перечисли это в конце.

Если в коммите рефакторинга меняются характеризационные тесты, что-то не так. Это даёт простую проверку: `git diff --stat main -- tests/` не должен показывать правок существующих тестов.

### Один вид изменения за шаг

Хорошие размеры шага:

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

Попросите агента запускать тесты после каждого шага и коммитить с понятным сообщением, прежде чем идти дальше. Помогает план: «Предложи рефакторинг как нумерованный список шагов, каждый из которых оставляет тесты зелёными. Дождись моего OK, прежде чем начинать». У большинства кодинг-агентов для этого есть режим планирования; например, режим планирования в Claude Code исследует код и предлагает план, ничего не редактируя.

### Душите, а не переписывайте

Если часть кода действительно надо заменить, поставьте новую реализацию рядом со старой за интерфейсом или флагом, переводите вызывающий код по одному месту за раз и удалите старый код, когда им больше никто не пользуется. Это паттерн strangler fig («фикус-душитель»). Он превращает одно рискованное переключение во множество маленьких и скучных, а скучное агентам удаётся хорошо.

## Шаг 3: миграции с агентами

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

### С JavaScript на TypeScript

Путь, при котором приложение всё время остаётся рабочим:

1. **Добавьте TypeScript рядом с JavaScript.** В `tsconfig.json` задайте `allowJs: true`, чтобы файлы `.js` и `.ts` уживались вместе, и запускайте `npx tsc --noEmit` в CI как проверку типов.
2. **Начните мягко.** Стартуйте с `strict: false`. При желании включите `checkJs` или добавьте `// @ts-check` в отдельные JS-файлы, чтобы найти проблемы ещё до переименования.
3. **Сначала листовые модули.** Проще всего файлы без внутренних импортов, например утилиты и константы. Затем двигайтесь к точкам входа.
4. **Одна папка за шаг.** Попросите агента перевести `src/utils/` на `.ts`, добавить типы, сохранить тесты зелёными и добиться прохождения `tsc --noEmit`. Потом следующая папка.
5. **Ужесточайте постепенно.** Когда всё уже в `.ts`, включите `noImplicitAny`, затем `strictNullChecks`, затем полный `strict`, исправляя ошибки между шагами.

Два правила для агента: никакого `any` без комментария с объяснением и никакого `@ts-ignore`, чтобы ошибки исчезли. Иначе самый быстрый путь к зелёному компилятору — отключать компилятор строка за строкой.

### Обновление фреймворков и библиотек

При обновлении мажорной версии важен порядок:

1. **Сначала прочитайте гайд по миграции.** Пусть агент кратко изложит официальное руководство по обновлению и перечислит ломающие изменения, которые касаются вашего кода, с путями к файлам. Это шаг только для чтения.
2. **Запустите официальный кодмод, если он есть.** Многие проекты поставляют кодмоды, например `npx @next/codemod` для Next.js; для Java многие обновления покрывают рецепты OpenRewrite. Кодмод детерминирован, агент — нет. Пусть кодмод сделает всё, что может.
3. **Остальное отдайте агенту.** Случаи, с которыми кодмод не справился, собственные обёртки, тесты, полагавшиеся на старое поведение.
4. **Обновляйтесь на одну мажорную версию за раз.** Прыжок с 3 на 6 складывает все ломающие изменения в один дифф.

Закрепите ожидаемый результат в промпте: «сборка проходит, набор тестов проходит, новых предупреждений в консоли нет». Расплывчатое «обнови до последней версии» приглашает агента заодно поднять ещё пять пакетов.

## Шаг 4: изолируйте работу в worktree

Долгому рефакторингу не место в вашей основной рабочей папке, а параллельным шагам — в одной общей. [Git worktree](/ru/glossary/git-worktree/) даёт каждому шагу собственную ветку и папку:

```bash
git worktree add ../app-ts-utils -b refactor/ts-utils
git worktree add ../app-ts-api -b refactor/ts-api
```

Теперь один агент переводит `utils`, другой — `api`, а вы продолжаете работать в основной папке. Когда шаг готов и проверен, сливайте его; если он ни к чему не привёл, удалите worktree и ветку. Подробнее — в статье [git worktree для параллельных агентов](/ru/blog/git-worktree-parallel-agents/).

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

## Шаг 5: ревью каждого шага

Дифф рефакторинга легко одобрить, потому что кажется, будто он ничего не делает. Именно поэтому его надо читать. На что смотреть:

- **Изменились ли тесты?** При чистом рефакторинге существующие тесты меняться не должны.
- **Изменились ли публичные сигнатуры?** Экспортируемые функции, ответы API, столбцы базы данных, имена событий.
- **Удалено ли что-нибудь?** Спросите агента, почему каждая удалённая функция не использовалась. «Ссылок не найдено» — не доказательство, если есть рефлексия или вызовы по строке.
- **Изменилась ли обработка ошибок?** Проглоченное исключение, которое теперь пробрасывается, или наоборот, — это изменение поведения.
- **Не вырос ли дифф за рамки шага?** Шаг «перенести этот модуль» не должен заодно переформатировать двадцать других файлов. Просите форматирование отдельным коммитом или не просите вовсе.

Вторая модель, которая проверяет каждый ход, ловит заметную часть этого. Как это работает, рассказано в статье [ИИ-ревью кода](/ru/blog/ai-code-review/).

## Шаг 6: продумайте откат заранее

Откат дёшев, если к нему подготовиться:

- **Маленькие коммиты.** `git revert <sha>` чисто убирает один шаг. Коммит на 4000 строк можно откатить только целиком.
- **Откатывайте, а не переписывайте.** Если что-то уже в main, откатите коммит или merge вместо force-push поверх общей истории.
- **Bisect, если что-то проскочило.** С маленькими зелёными коммитами `git bisect` за несколько прогонов находит шаг, сломавший поведение.
- **Флаги для рискованных переключений.** Когда старый и новый код живут рядом, флаг отключает новый путь без деплоя.
- **Отмена внутри сессии.** Пока ничего не закоммичено, можно отменить последний ход агента; см. [как отменить изменения Claude Code](/ru/blog/undo-claude-code-changes/).

## Частые ошибки

- **«Отрефактори этот модуль» без тестов.** Получите переписывание и никакой возможности его проверить.
- **Смешивание рефакторинга и исправления багов.** Когда поведение меняется, не понять, было ли это задумано.
- **Одна гигантская ветка миграции.** Она уходит от main, конфликты копятся, и проверить её не может никто.
- **Вера в «не используется».** Поиск не видит динамических вызовов, других репозиториев и запланированных задач.
- **Разрешить агенту заглушить компилятор.** `any`, `@ts-ignore` и отключённые правила линтера прячут работу, а не делают её.
- **Слияние параллельных шагов без совместной проверки.** Две зелёные ветки после слияния могут стать красными — для этого и нужна [очередь слияния](/ru/blog/merge-queue-for-ai-agents/).

## Как это сделать в AgentVera

AgentVera запускает CLI кодинг-агентов бок о бок в каждом проекте со встроенным git, и это хорошо подходит к пошаговому стилю рефакторинга legacy-кода.

- **Worktree на каждый шаг.** Через панель worktree откройте ветку и папку для каждого шага и запустите агента прямо в ней: Claude Code в одной, Codex или Gemini CLI в другой. Основная папка остаётся как есть.
- **Чекпоинты на каждый ход.** В начале каждого хода рабочая папка сохраняется снимком в скрытый git ref, и для каждого агента хранятся последние 60 ходов. Смотрите, какие файлы изменил ход, и откатывайте только его, если агент зашёл слишком далеко.
- **Ревью в конце каждого хода.** Автоматическое ревью кода читает написанный агентом код выбранной вами моделью Claude и перечисляет замечания с уровнем серьёзности и file:line; при желании замечания возвращаются агенту, до двух раундов подряд.
- **Verify по вашим критериям.** Напишите в сообщении «чистый рефакторинг, все тесты проходят, ни один существующий файл тестов не изменён». Verify превращает это в критерии, и когда ход заканчивается, отдельный верификатор, который не может править файлы, запускает тесты и отмечает каждый критерий с доказательством.
- **Очередь слияния для параллельных шагов.** Ветки сливаются по одной на свежую целевую ветку, и проверочная команда запускается на результате слияния. Зелёная ветка по умолчанию ждёт вашего одобрения.
- **Параллельная команда для больших миграций.** Если миграция делится на независимые части, параллельная команда запускает каждую часть в своём worktree, сливает готовые части в интеграционную ветку и запускает финальную проверку, так что вы проверяете один pull request.
- **Git, не выходя из приложения.** Менеджер коммитов, диффы и граф истории находятся в правой панели, поэтому проверить каждый шаг и откатить один из них можно быстро.

## Подводим итоги

ИИ не делает legacy-код безопасным для изменений; это делают тесты и мелкие шаги. ИИ меняет стоимость этих шагов. Написать характеризационные тесты, перевести папку на TypeScript или прогнать кодмод и прибрать за ним — раньше на это ни у кого не было времени. С агентами это дёшево, поэтому можно позволить себе делать всё как следует: сначала страховка, одно изменение за раз, каждый шаг проверен, каждый шаг обратим.

Читайте также:

- [Git и GitHub](/ru/features/git-and-github/)
- [Чекпоинты и повтор сессии](/ru/features/checkpoints-and-replay/)
- [Ревью кода](/ru/features/code-review/)
- [Verify](/ru/features/verify/)
- [Очередь слияния](/ru/features/merge-queue/)
- [Параллельная команда](/ru/features/parallel-team/)

Чтобы попробовать AgentVera, начните с [главной страницы](/ru/).

## Частые вопросы

**Можно ли безопасно рефакторить legacy-код с помощью ИИ?** Можно, если дать агенту страховку и мелкие шаги. Пусть он сначала напишет характеризационные тесты, затем делает по одному структурному изменению, после каждого шага запускает тесты и коммитит. Большие запросы вроде «приведи этот модуль в порядок» — как раз то место, где агенты незаметно меняют поведение.

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

**Стоит ли поручить агенту перевести весь проект на TypeScript за раз?** Нет. Включите allowJs, переводите по одному модулю или папке, держите сборку и тесты зелёными между шагами и ужесточайте настройки компилятора постепенно. Одно гигантское изменение очень трудно проверить.

**Как откатить рефакторинг от ИИ, который что-то сломал?** Если каждый шаг — отдельный коммит, git revert убирает только этот шаг. До слияния чекпоинты AgentVera могут откатить и отдельный ход агента. После слияния откатывайте коммит или merge в main, а не переписывайте историю.

**Какой агент лучше всего подходит для рефакторинга?** Справиться могут Claude Code, Codex и Gemini CLI. Процесс важнее модели: сначала тесты, мелкие шаги, ревью каждого диффа. В AgentVera их можно запускать бок о бок на разных шагах.

---

**Об AgentVera.** AgentVera — настольное приложение для macOS, Windows и Linux от Solviera Teknoloji. Оно запускает 18 ИИ-агентов для программирования (Claude Code, Codex, Gemini CLI, Copilot, Cursor, Antigravity, opencode, Hermes, Qwen Code, Kimi Code, Kiro, Amp, Droid, Goose, Vibe, Crush, Aider, Cline) рядом, по проектам, в настоящих терминалах с собственными установками CLI и входами пользователя. В бесплатном тарифе количество агентов не ограничено. Скачать: https://agentvera.dev/ru/download/ · Цены: https://agentvera.dev/ru/pricing/
