23 окт. 2025 г.·6 мин чтения

Парное программирование с ИИ для наследуемого кода без сбоев

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

Парное программирование с ИИ для наследуемого кода без сбоев

Почему старый код кажется опасным

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

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

Названия усугубляют ситуацию. В старых системах переменные и методы часто несут в себе бизнес‑правила, которые никто не записал. Метод с именем applyFix, legacyMode или specialCase может скрывать скидку с давних времён, обходной путь для крупного клиента или налоговое правило, которое срабатывает только в конце месяца. Имя почти ничего не говорит, но код по‑прежнему решает реальные деньги, реальные даты и реальные права доступа.

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

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

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

Решите, что вы не будете менять

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

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

Запишите эту границу простыми словами. Короткая заметка лучше хитрого промпта.

  • Изменение начинается, когда пользователь открывает экран редактирования счета.
  • Оно заканчивается, когда обновлённый итог сохраняется и отображается.
  • PDF‑экспорт не изменяется.
  • Налоговая логика для других стран не меняется.

Такая заметка держит и вас, и модель в фокусе. Она также упрощает ревью, потому что всем видно, что должно попасть в дифф, а что нет.

Очерчьте границу вокруг одного пользовательского пути

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

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

Пусть первый проход будет скучным

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

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

Сначала зафиксируйте текущее поведение

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

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

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

Для каждого примера сохраните ввод, точный вывод или результат на экране, какое‑то доказательство — строку лога, ответ API или скриншот — и короткую заметку о том, почему этот случай важен.

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

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

Держите материал для сравнения и вне тестов, когда это помогает. Скриншоты ловят сдвиги в верстке. Логи фиксируют изменения полей. Примеры JSON показывают маленькие форматные отличия, которые люди пропускают в ревью.

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

Работайте с ИИ шаг за шагом

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

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

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

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

  • Объясните, что делает этот метод, построчно.
  • Предложите тесты, которые закрепляют текущее поведение.
  • Измените только правило скидки внутри этого метода.
  • Покажите дифф и объясните каждую изменённую строку.

После этого сделайте одно изменение в коде и сразу запустите проверки. Запустите тесты, линтер, проверки типов и минимальную ручную проверку. Если что‑то падает, остановитесь. Не накладывайте следующий промпт на шаткий результат.

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

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

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

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

Старый код наказывает за амбиции. Самый безопасный рефактор обычно скучный: одно переименование, одна извлечённая функция, один перемещённый блок. Если рецензент не может объяснить дифф за минуту, изменение слишком широкое.

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

Небольшой пример с биллингом это проясняет. Поменять total на invoiceTotal легко проверить. Поменять имя, при этом изменить расчёт налогов и перенести код в другой файл — вот где прячутся баги.

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

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

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

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

Очистка заслуживает отдельного изменения. Перестановка импортов, удаление мёртвого кода и исправление стиля — полезная работа, но она не должна стоять рядом с изменением поведения.

Простой пример: изменение старого правила выставления счетов

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

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

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

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

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

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

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

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

Ошибки, которые создают ложную уверенность

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

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

Обычная ошибка начинается с чрезмерно широкого промпта. Кто‑то просит ИИ модернизировать весь модуль, почистить имена, упростить условия и обновить тесты в одном заходе. Вывод может выглядеть аккуратно, но запрос слишком широк, чтобы его можно было внимательно проверить. В рефакторинге наследуемого кода красивая переработка часто скрывает изменение поведения, которое никто не заметил.

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

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

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

Небольшой пример с биллингом это проясняет. Допустим, старая функция хранит странное правило для счетов, созданных в последний день месяца. Модель удаляет его, потому что оно «выглядит избыточным», и пишет тесты только для обычных дат. Набор остаётся зелёным. В продакшене сломается при следующем появлении этого кейса.

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

Быстрые проверки перед релизом

Ужесточите путь релиза
Добавьте лучшие проверки для CI, деплоев и наблюдаемости при рискованных изменениях.

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

Используйте короткий ворот релиза. Тесты, привязанные к изменению, должны падать до правки и проходить после неё. Старые примерные входы должны по‑прежнему возвращать те же результаты, если вы не планировали их менять. Вы должны уметь объяснить обновление в двух–трёх простых предложениях. Другой разработчик должен уметь прочитать дифф за один присест. Вы также должны иметь возможность откатить изменение, вернув небольшой набор файлов, а не распутывая кучу несвязанных правок.

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

Простыми словами — это сильная проверка. Если вы не можете чётко объяснить изменение, вероятно, вы изменили слишком много.

Что делать, если код всё ещё сопротивляется

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

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

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

Здесь ИИ особенно полезен. Используйте его, чтобы прочитать запутанные файлы, объяснить ветки простыми словами, набросать тесты, которые фиксируют текущее поведение, и предложить очень узкие правки. Не просите переписать весь модуль только потому, что он выглядит грязным. Грязный код с известным поведением часто менее рискован, чем аккуратный код с новыми багами.

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

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