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

Миграция устаревшей системы терпит неудачу задолго до того, как сгенерированный код начинает выглядеть неправильным. Это происходит, когда команда одним промптом просит модель разобраться в системе, выбрать новую архитектуру, сохранить скрытое поведение, распланировать работу и оценить собственный результат. Такой промпт может создать впечатляющий репозиторий. Он не может создать обоснованную цепочку решений.
План должен появиться до первого файла на целевом языке. Я говорю не о бэклоге с пунктами «переделать биллинг» и «мигрировать отчеты». Полезный план называет подтверждающие факты для каждого поведения, порядок их извлечения, новую границу, которая его заменит, и тест, разрешающий следующее изменение. Если хотя бы одно поле пусто, генерацию следует отложить.
Это различие важно: языковая модель генерирует из полученного контекста, а инженер отвечает за поиск контекста, который никто не догадался приложить. Старые системы прячут правила в управлении заданиями, триггерах базы данных, привычках операторов, форматах файлов, настройках принтера, сценариях повторного запуска и исключениях при закрытии месяца. Самая трудная работа состоит в том, чтобы решить, что считать поведением, и доказать, что новая система его сохранила.
Сначала инвентаризируйте поведение, а не файлы
Опись миграции должна описывать наблюдаемое поведение, а не только исходные файлы и число языков. Список файлов помогает оценить работу над парсерами, но почти ничего не говорит о контракте, от которого зависят пользователи и связанные системы. Шаг JCL из двенадцати строк может решать, будет ли повторно обработан вчерашний расчетный файл. Большой модуль отчетности может выдавать результат, который никто не читает.
Начните с границы системы. Запишите каждый вход, выход, событие по расписанию, действие оператора, внешний вызов, постоянное хранилище и сигнал ошибки. Для каждого пункта сохраните реальный пример и определите, кто способен подтвердить его правильность. Так появится поведенческая поверхность, которую можно тестировать. Только после этого сопоставляйте с ней исходные модули.
Я использую четыре класса подтверждающих фактов, потому что команды постоянно их смешивают:
- Факты исполнения: промышленные запросы, пакетные входы, изменения базы данных, файлы, сообщения и результаты, которые действующая система действительно обработала.
- Заявленное поведение: руководства, соглашения об интерфейсах, copybook-файлы, схемы, справочные тексты и рабочие инструкции, описывающие ожидаемый результат.
- Реализованные пути: ветви, запросы, задания, триггеры и обработчики ошибок в коде.
- Эксплуатационная практика: время запуска, ручные исправления, точки возобновления и исключения, которые операторы применяют вне программы.
Эти классы могут противоречить друг другу. Противоречие нужно считать находкой, а не помехой, которую стоит сгладить. Если руководство требует заполнить поле, а в записанном трафике встречаются пустые значения, план миграции должен определить приоритет совместимости или письменного правила. Единичная генерация обычно выбирает самый убедительный в ее контексте артефакт. Инженер фиксирует конфликт, находит владельца решения и превращает решение в тест.
До написания целевого кода создайте таблицу границ. Небольшой вариант может выглядеть так:
| Граница | Подтверждающие данные | Ответственный | Правило совместимости | Проверка |
|---|---|---|---|---|
| Ночной файл счетов | Три принятых файла и один отклоненный | Руководитель эксплуатации | Сохранить фиксированную ширину и код отказа 17 | Повторно обработать все четыре файла |
| Расчет налога | Записанные запросы и текущая таблица ставок | Владелец финансовых систем | Сопоставить округленную сумму и поля аудита | Сравнить ответ и строки реестра |
| Печать выписки | Образцы spool и настройки принтера | Руководитель клиентской службы | Сохранить разрывы страниц и порядок сортировки | Отобразить и проверить указанные случаи |
Таблица рано обнаруживает пробелы в фактах. Она также предотвращает привычную ошибку, когда схему базы данных считают полной моделью предметной области. Столбец может допускать null, потому что неудачный импорт оставляет там неполные данные, хотя любая штатная транзакция требует значение. Форма схемы и деловой смысл относятся к разным фактам.
Реестр миграции и есть план
Полезный план миграции устаревшей системы выглядит как исполняемый реестр утверждений, зависимостей и проверочных барьеров. По любой рабочей задаче другой инженер должен ответить на пять вопросов: что мы меняем, какие факты определяют текущее поведение, что уже должно быть готово, как мы сравним старое и новое и кто принимает намеренное отличие.
Одной доски с задачами недостаточно. Задачи меняются, критерии приемки превращаются в прозу, а зависимости остаются в памяти. Храните машиночитаемый реестр в репозитории и ссылайтесь на его идентификаторы при проверке. Подойдет YAML, JSON или таблица базы данных. Важно, чтобы отсутствие доказательств оставалось заметным.
- id: CALC-014
boundary: POST /interest/accrue
evidence:
traffic_set: traffic/interest/month_end_2025_01.ndjson
state_snapshot: snapshots/ledger_before.sql.zst
depends_on: [DATA-006, CLOCK-002]
target: services/interest/accrual.go
invariants:
- response.status == legacy.response.status
- ledger.delta_cents == legacy.ledger.delta_cents
- audit.event_type == legacy.audit.event_type
allowed_differences:
- field: response.request_id
rule: compare_presence_only
approved_by: architecture-review-27
gate: parity/CALC-014
Этот фрагмент предотвращает три ошибки. Разработчик не сможет тестировать расчет без определяющего его состояния. Недетерминированный идентификатор запроса получит явное правило сравнения вместо растущего списка исключений. Кроме того, согласование привязано к конкретному отличию, поэтому никто не сможет позднее незаметно расширить исключение.
Реестр должен отделять открытия от решений. «Устаревший сервис возвращает 200 при повторном запросе» относится к наблюдениям. «Новая система сохранит такой ответ» относится к решениям о совместимости. «Нужно возвращать 409» относится к архитектурным предпочтениям. Если смешать эти фразы, желательная доработка может выдать себя за точную миграцию.
Не записывайте в реестр проценты вроде «модуль мигрирован на 80 %». Проценты скрывают оставшееся поведение. Считайте закрытые барьеры для названных границ. Десять мелких вариантов отчета не перевешивают один непроверенный путь проводки, и план должен это показывать.
Плану также нужны условия остановки. Генерация прекращается, если не хватает фактов, зависимость не имеет проверенного целевого компонента, сравнение дает необъясненное отличие или ответственный не одобрил намеренное изменение. Без условий остановки давление сроков превращает любой красный результат в задачу на будущее.
Извлекайте от границ к центру
Самый безопасный порядок извлечения начинается с наблюдаемых границ, затем переходит к смыслу данных и управлению исполнением и только потом доходит до внутренних алгоритмов. Такой порядок дает каждой последующей генерации контракт и способ измерить результат. Начало с самого автономного модуля кажется эффективным, но часто создает остров с придуманными интерфейсами.
Сначала зафиксируйте входы и выходы на стабильных стыках. Для онлайн-сервиса это могут быть тела запросов и ответов, заголовки, изменения базы данных и отправленные сообщения. Для пакетной системы нужны поколения входных данных, управляющие карты, коды возврата, spool-вывод, созданные файлы и поведение при возобновлении. Для настольного приложения добавьте действия пользователя, локальные файлы, отчеты, состояние реестра или конфигурации и обращения к общим базам данных.
Затем извлеките смысл данных. Сопоставьте идентификаторы, единицы, кодировки, правила null, десятичный масштаб, правила дат и жизненные циклы записей. Пока не нормализуйте их. Упакованное десятичное поле, дополненный пробелами код и отметка в местном времени могут выглядеть некрасиво, но каждый из них способен нести поведение. Прежде чем выбирать более чистое представление, запишите, как значение входит в систему и где становится наблюдаемым.
После этого восстановите управление исполнением. В старых системах деловой порядок часто находится за пределами деловых модулей. JCL определяет, какая программа запускается после кода возврата. Сценарии CL переключают библиотеки. Shell-обертка повторяет одну команду, но не следующую. Планировщик передает деловую дату, отличную от системных часов. Если при генерации видны вызываемые программы, но не это управление, получатся по отдельности правдоподобные функции в неправильной последовательности.
Далее определите переходы состояния и инварианты. Опишите транзакцию через состояние до запуска, стимул, состояние после запуска и выпущенные последствия. Это точнее, чем переводить процедуры одну за другой. Такое описание также обнаруживает повторы, частичные коммиты, компенсирующие действия и точки восстановления.
Только после появления этих слоев команда должна генерировать внутреннюю реализацию и целевую архитектуру. Границы целевых сервисов должны следовать ответственности, требованиям согласованности и характеру изменений. Они не должны копировать дерево каталогов исходной системы. Перевод модулей один к одному сохраняет случайную связанность и называет ее модернизацией.
Есть одно практическое исключение. Иногда на раннем этапе нужен небольшой парсер или эмулятор, чтобы прочитать факты в закрытом формате. Создайте его как инструмент извлечения, явно отметьте как временный и проверьте на известных образцах. Не позволяйте удобному компоненту незаметно стать промышленной архитектурой.
Разделяйте факты, толкования и решения
Инженерам нужны разные записи о том, что система сделала, как они это понимают и что проект решил сохранить. Длинный промпт сливает все три категории в гладкий текст. После слияния читатель не может понять, взято ли сгенерированное требование из трафика, исходного кода или вывода модели.
Для каждого определяющего реализацию поведения используйте запись утверждения. Сложный процесс ей не нужен. Нужны происхождение и статус.
{
"claim_id": "BATCH-031",
"statement": "A rerun skips records already posted for the same business date",
"kind": "observed",
"evidence": ["run-884/input.dat", "run-884/ledger-after.csv", "ops-runbook-4.2"],
"confidence": "confirmed",
"decision": "preserve",
"tests": ["parity/batch_031_first_run", "parity/batch_031_rerun"]
}
Разницу между наблюдаемым и предполагаемым поведением легко отбросить, пока она не приведет к ошибке данных. Предположим, перед записью код проверяет таблицу «уже проведено». Генератор может сделать вывод об идемпотентности. Промышленные данные могут показать, что конкретная процедура восстановления очищает таблицу, поэтому некоторые повторные запуски намеренно проводят записи снова. Путь кода, эксплуатационная практика и предполагаемый контракт расходятся. Запись заставляет команду разрешить это несоответствие.
Считайте комментарии утверждениями, а не истиной. То же относится к именам переменных, мертвым ветвям, старым архитектурным документам и тестам, которые не запускались на состоянии, похожем на промышленное. Каждый источник может направлять исследование. Ни один не должен перевешивать факты исполнения без отдельного решения.
Метки уверенности должны иметь практический смысл. «Подтверждено» может требовать двух независимых видов фактов и проверки ответственным. «Предварительно» может разрешать работу над парсером, но блокировать целевую реализацию. «Неизвестно» должно создавать задачу на извлечение. Если метки передают лишь ощущение, под давлением сроков их смысл исчезнет.
Храните намеренные улучшения в реестре изменений, связанном с утверждением о совместимости. Например, новая система может отклонять неверную дату, которую принимала старая, заменить слабый способ аутентификации или изменить вид отчета. Проверяйте сохраненный путь и новое поведение отдельно. Иначе сбой паритета и задуманное улучшение нельзя будет различить, что затруднит проверку и откат.
Каждая генерация заканчивается барьером паритета
Каждый этап генерации должен заканчиваться барьером паритета, который сравнивает наблюдаемые последствия с действующей системой. Проверка кода и модульные тесты на целевом языке необходимы, но не доказывают совместимость. Они показывают внутреннюю разумность нового кода, а не его соответствие системе, которой пользуются люди.
Полезный цикл состоит из пяти действий:
- Выберите запись реестра, зависимости которой уже прошли проверки.
- Соберите только согласованный контекст: утверждения, схемы, образцы фактов, целевые ограничения и допустимые отличия.
- Сгенерируйте или исправьте наименьшую часть целевой системы, способную выполнить контракт границы.
- Подайте один стимул в старую и новую системы из эквивалентного состояния.
- Классифицируйте каждое отличие, затем примите результат, исправьте код, передайте вопрос выше или измените запись решения.
Эквивалентное состояние требует внимания. Если устаревшая система начинает работу с тридцатилетней историей клиентов, а новая с написанными вручную фикстурами, совпавшие ответы мало что доказывают. Создайте снимок нужного состояния, замаскируйте его при необходимости, сохраните ссылочные связи и задокументируйте состояние, которое нельзя воспроизвести. Для систем, которые нельзя дважды запустить на одном состоянии, клонируйте его или записывайте последствия на стыке.
Сравнение должно учитывать смысл, а не быть обычным текстовым diff. Нормализуйте поля только по причинам, указанным в реестре. Можно сравнивать временные отметки в пределах согласованного допуска, игнорировать значение сгенерированного идентификатора и одновременно требовать его наличия, приводить порядок объектов JSON к каноническому или сравнивать PDF по извлеченному тексту и геометрии страниц. Не добавляйте общее правило игнорирования лишь для того, чтобы красная сборка стала зеленой.
Отчет о паритете должен показывать запись, набор фактов, старый результат, новый результат, правила нормализации, отличия и решение. Такой компактной формы достаточно для автоматизации и проверки:
gate=CALC-014 evidence=month_end_2025_01
cases=184 matched=183 different=1 errored=0
difference[1].path=ledger.entries[2].amount_cents
difference[1].legacy=1250
difference[1].target=1249
difference[1].rule=exact
status=FAIL
Разница в один цент не считается «достаточно близкой». Она указывает на порядок округления, десятичное представление или несовпадение состояния. Инженер прослеживает ее по промежуточным значениям, проверяет арифметические правила исходной системы и добавляет самый маленький тест, изолирующий причину. Повторная генерация всего модуля с более строгой инструкцией часто меняет постороннее поведение и уничтожает цепочку доказательств.
Запускайте барьеры постоянно, а не на финальном этапе приемки. Пройденная граница становится ограничением для дальнейшей работы. Когда меняется общее сопоставление данных, граф зависимостей показывает, какие барьеры нужно пройти заново. Поэтому реестр и проверочный стенд работают вместе: первый говорит, что разрешено менять, второй показывает, что изменилось.
Один огромный промпт ломается предсказуемо
Единый промпт миграции терпит неудачу, потому что его задачи конфликтуют, а в контексте нет механизма исполнения правил. Дополнительный контекст может улучшить запоминание, но он не создает происхождение фактов, порядок зависимостей, независимое суждение или постоянный проверочный барьер. Сгенерированный репозиторий может выглядеть связно и ошибаться на каждой границе, которая не попала в промпт.
Рассмотрим ночную цепочку биллинга. Планировщик передает деловую дату. Шаг JCL сортирует транзакции по правилам конкретной локали. Одна программа проводит допустимые строки и записывает отклоненные. Код возврата определяет, запускать ли выписки. Операторы могут возобновить работу после проводки, не повторяя ее. Исходные модули содержат части этого поведения, но ни один модуль не владеет полным контрактом.
Большой промпт просит переписать систему на Go и включает программы, copybook-файлы, образцы данных и фразу «сохранить поведение». Результат использует системную дату, сортирует строки стандартным способом целевой среды, оборачивает весь пакет в одну транзакцию базы данных и считает любое отклонение фатальной ошибкой. Каждый выбор можно защитить для новой системы. Вместе они ломают закрытие месяца.
Первый тестовый файл содержит обычные записи, поэтому обе системы вычисляют одинаковые суммы. Команда празднует успех. Первый возобновленный запуск повторяет проводки, потому что в целевой системе нет контрольной точки, соответствующей старому шагу. Файл с пустым суффиксом счета сортируется иначе, что меняет группировку. Одна неверная строка теперь откатывает допустимую работу, которую старая цепочка провела бы. Ни одна из этих ошибок не похожа на синтаксический дефект или явно неразумное архитектурное решение.
Плановое извлечение нашло бы их по порядку. Фиксация границ записала бы деловую дату и коды возврата. Анализ данных зафиксировал бы сортировку и дополнение пробелами. Анализ исполнения отметил бы точки коммита и возобновления. Воспроизведение включило бы обычный, отклоненный и возобновленный запуски. Целевая архитектура все еще может улучшить реализацию, но не может случайно стереть эти контракты.
Популярный ответ состоит в том, чтобы удлинить промпт. Это помогает только тогда, когда единственной проблемой было упущение. Длинный промпт затрудняет разрешение конфликтов, прячет связь инструкций с фактами и все равно позволяет генератору судить собственную работу. Разделение промпта между агентами само по себе тоже ничего не исправляет. Нескольким генераторам нужен общий реестр, правила ответственности и барьеры паритета, иначе они лишь быстрее распространяют непроверенные предположения.
Используйте генерацию как ограниченную операцию реализации. Передайте ей один целевой фрагмент, явные ограничения, нужные для него факты и отчет о проваленном паритете предыдущей попытки. Затем проверьте изменение и запустите барьер вне процесса генерации. Такое разделение сохраняет продуктивность модели, но не дает ей полномочий, за которые она не может отвечать.
Инженер отвечает за нерешенные вопросы
Инженер выполняет работу, которую нельзя свести к правдоподобному коду: находит недостающие факты, определяет значимое противоречие, выбирает границу, согласует намеренные изменения и принимает риск. Это не временные пробелы, которые исчезнут с ростом моделей. Это действия ответственного лица внутри конкретной организации.
Инженер спрашивает, кто пострадает, если два артефакта противоречат друг другу. Если исходный код разрешает овердрафт, а финансовые правила запрещают, нужен ответ владельца продукта и службы соответствия, а не вероятностный синтез. Если операторы зависят от приема с повторным запуском, который новая архитектура должна исключить, инженер обязан понять его причину, создать более безопасный путь восстановления и получить согласие на намеренный разрыв совместимости.
Инженер также управляет декомпозицией. Генератор обычно следует видимой структуре. Инженер может понять, что шесть программ образуют одну границу согласованности или что один монолит содержит четыре возможности с разными владельцами. Такое суждение опирается на смысл транзакций, потребности развертывания, историю сбоев и знания людей, которые будут сопровождать новую систему.
При проверке сначала смотрите на утверждения и последствия, а не на стиль. Спросите, какую запись реестра закрывает изменение, какие факты использует, какое целевое решение воплощает и что показывает отчет о паритете. Красивый идиоматичный сервис с необъясненной разницей в выходе остается незавершенным. Неуклюжий адаптер, сохраняющий сложную границу, может быть именно тем временным компонентом, который нужен.
Инженеры также должны защищать стенд от превращения в формальное одобрение. Каждому правилу нормализации нужна причина. Каждому эталонному файлу нужно происхождение. Изменение ожидаемого результата требует такой же проверки, как изменение промышленного поведения. Если команда обновляет снимки после любого сбоя теста, она построила машину одобрения, а не стенд паритета.
Работать быстро при этом можно. Генерируйте парсеры, адаптеры, тесты, код сопоставления и варианты реализации параллельно, когда зависимости реестра это позволяют. Получение фактов и результаты барьеров оставляйте централизованными. Параллельное создание кода полезно; параллельная истина противоречива.
Архитектура меняется только за доказанными контрактами
Модернизация должна менять архитектуру за сохраненными контрактами, а не переводить старую структуру построчно. Когда у границы есть факты и барьер паритета, команда может заменить общее состояние явными сервисами, изолировать численные ядра, перенести данные в Postgres или создать клиент TypeScript, не гадая, изменило ли новое устройство видимое поведение.
Принимайте архитектурное решение на самом малом уровне, где уже достаточно фактов. Некоторые решения нужны в начале: ограничения целевой среды, границы безопасности, среда развертывания, место хранения данных и системы, которые должны одновременно работать при переключении. Другие решения стоит отложить: разделение сервисов, размещение кэша, асинхронные границы и очистка схемы часто зависят от поведения, найденного при извлечении.
Избегайте двух крайностей. Фиксация каждой детали до исследования создает красивый план воображаемой системы. Если позволить каждой генерации придумывать архитектуру, получатся несогласованные границы и дублированная инфраструктура. Записывайте обязательные решения, сохраняйте видимыми отложенные и указывайте, какие факты позволят их принять.
План переключения принадлежит тому же реестру. Назовите источник истины во время перехода, направление синхронизации, запрос сверки, точку отката и максимально допустимый перерыв. Новая система может пройти изолированные тесты паритета и провалиться в эксплуатации, если обе системы записывают одни данные или откат не может восстановить изменения, созданные только в новой системе.
CodeHero работает по такой схеме, когда читает все дерево устаревшей системы, переписывает его на Go, Rust, TypeScript и Postgres и проверяет поведение стендом паритета по записанному промышленному трафику. Обещание поставки меньше чем за 30 дней зависит от строгого порядка извлечения и проверки; план от этого не становится необязательным.
До утверждения первого сгенерированного целевого файла потребуйте одну заполненную запись реестра с реальными фактами, явными зависимостями, правилом сравнения и названным ответственным за отличия. Если команда не может подготовить такую запись, она не готова к миграции. Она готова лишь генерировать код, похожий на мигрированный.
Вопросы
Почему один длинный промпт не может мигрировать устаревшую систему?
Длинный промпт может создать внешне связную кодовую базу, но он не определит, какие факты считать главными, и не разрешит противоречия между кодом, эксплуатацией и правилами. Он также не даст независимую оценку совместимости, когда проверяет собственный результат.
Что должен содержать план миграции устаревшей системы?
Для каждой поведенческой границы запишите факты, зависимости, целевой компонент, инварианты, допустимые отличия, проверочный барьер и ответственного за решение. Плану также нужны условия остановки, чтобы нехватка фактов или необъясненное отличие блокировали генерацию.
Что команда должна сначала извлечь из устаревшего кода?
Начните с наблюдаемых входов, выходов, событий по расписанию, действий операторов, сохраняемых последствий и сигналов ошибки. Затем извлеките смысл данных и порядок исполнения, прежде чем реализовывать внутренние алгоритмы, поскольку эти границы задают контракт для дальнейшей работы.
Как проверить переписанную устаревшую систему?
Подайте одинаковый стимул в старую и новую системы из эквивалентного состояния, а затем сравните все наблюдаемые последствия по записанным правилам нормализации. Отнесите каждое отличие к дефекту, согласованному изменению, проблеме фактов или проблеме среды.
Достаточно ли модульных тестов для миграции устаревшей системы?
Нет. Модульные тесты показывают, что целевой код ведет себя так, как ожидают его авторы, а тесты паритета показывают его соответствие заменяемой системе. Нужны оба вида, потому что чистая реализация может точно воплотить ошибочное предположение.
Могут ли ИИ-агенты спланировать полную миграцию кода?
Агенты могут составлять опись кода, предлагать сопоставления, генерировать ограниченные части и исследовать упавшие тесты. Инженеры по-прежнему отвечают за качество фактов, порядок зависимостей, архитектурные решения, разрешение конфликтов и приемку намеренных изменений поведения.
Нужно ли при модернизации сохранять все устаревшее поведение?
Нет, но каждое отличие должно быть намеренным. Зафиксируйте старый путь в тесте паритета, отдельно запишите согласованное изменение и проверьте новое правило, чтобы желательная доработка не скрыла посторонний дефект совместимости.
Как обрабатывать недетерминированные поля в тестах паритета?
Запишите узкое правило сравнения, например требуйте наличия идентификатора без точного совпадения его значения или разрешите документированный допуск времени. Не используйте общий список игнорирования, потому что он скроет значимые отличия в других местах.
Когда следует определять целевую архитектуру?
Рано зафиксируйте жесткие ограничения и границы безопасности, а зависящие от обнаруженного поведения решения отложите. Делите сервисы, очищайте данные и выбирайте асинхронные потоки только после того, как факты покажут необходимые границы согласованности и ответственности.
Каков первый барьер перед генерацией миграционного кода?
Заполните одну запись реестра реальными входными и выходными данными, известными зависимостями, явными инвариантами, правилами сравнения и именем ответственного за спорные результаты. Если запись неполна, команда должна продолжить извлечение, а не генерировать промышленный код.