JavaRush /Курсы /Claude code /Контракт поведения и пофазный план

Контракт поведения и пофазный план

Claude code
28 уровень , 4 лекция
Открыта

1. План начинается с поведения, а не с build.gradle

На этом этапе очень хочется открыть файл сборки, поменять пару версий и посмотреть, что загорится первым. Искушение понятное: вы знаете blocker'ы, читали changelog, видели, что javax.* в Boot 3 с вами не поедет. Но начнёте с конфигов, не ответив «что должно остаться работающим», — получите техническую суету без инженерного смысла.

Зелёная сборка — ещё не миграция, а условие, чтобы система запустилась. Цель не «поднять версию Spring Boot», а перевести систему в новую среду так, чтобы обязательное поведение сохранилось. Контракт сохранения поведения — те же критерии приёмки, только для целого перехода между технологическими состояниями, а не для одной функции.

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

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

Слабая формулировка Почему не годится Рабочая формулировка
BillingService должен работать это имя класса, а не поведение POST /api/subscriptions создаёт подписку и возвращает ожидаемый ответ
Ошибки не должны измениться слишком расплывчато коды PAYMENT_FAILED и PRORATION_INVALID сохраняются
Система должна быть быстрой нет измеримого критерия p95 для чтения подписки не хуже чем 1.2x от baseline

Если коротко, compatibility matrix отвечает на вопрос что мешает перейти. Контракт сохранения поведения — зачем этот переход делается и что нельзя потерять по дороге.

2. Контракт поведения для CashFlow Dashboard

Контракт сохранения поведения звучит серьёзно, но по сути это просто список обещаний системы внешнему миру на языке наблюдаемого результата. Пользователю, API-клиенту и тестовому стенду всё равно, какой класс вызвался внутри. Им важно, что endpoint ответил, webhook обработался, timestamp не уехал на три часа влево, а код ошибки не превратился в загадочное «что-то пошло не так».

Для CashFlow Dashboard такой контракт обычно строится вокруг нескольких зон: критические потоки, API-контракты, поведение данных, ошибки, интеграции — и только потом производительность, в негероическом смысле: не «стало быстрее на 73%», а «не стало настолько хуже, что пользователи это почувствуют».

Зона поведения Что обычно фиксируют для CashFlow Dashboard
Критические потоки create subscription, cancel, refund, plan switch
API-контракты status code, JSON schema, обязательные поля
Поведение данных UTC, статусы подписки, суммы и округления
Ошибки сохранение важных бизнес-кодов ошибок
Интеграции webhook-подпись, обработка событий платёжного сервиса
Performance sanity latency не деградирует за разумный порог
Обратная совместимость pilot не меняет схему БД и внешние контракты

Ниже — хороший фрагмент такого контракта. Он короткий, но уже пригоден для review, потому что говорит о поведении, а не о внутренностях проекта:

## Контракт сохранения поведения
- create, cancel, refund и plan switch продолжают работать
- `/api/subscriptions/*` возвращает тот же JSON schema
- timestamps остаются в UTC, без нормализации в pilot
- коды `PAYMENT_FAILED` и `PRORATION_INVALID` сохраняются
- webhook от платёжного провайдера валидируется тем же secret
- latency чтения подписки не хуже `1.2x` от baseline

Заметьте, здесь нет ни слова про BillingService, SubscriptionFacade или MigrationHelper. И это хорошо: реализация меняется, контракт должен её переживать и оставаться читаемым для того, кто текущий код не писал.

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

3. Phased migration plan — маршрут с gate

Когда compatibility matrix уже готова, возникает соблазн превратить её в плоский чек-лист: сперва Java, потом Boot, потом Gradle, потом тесты. На салфетке удобно, в живом проекте плохо — нет фазы остановки, нет gate, нет точки, где можно честно сказать «дальше идти рано».

Поэтому пофазный план почти всегда выигрывает у списка задач: он раскладывает переход на четыре состояния — Baseline, Pilot, Broader update и Cleanup. Не магическая схема, но она хорошо подходит для первого прыжка с Boot 2.7 и Java 8 на ветку Boot 3.x и Java 21.

Фаза Смысл Переходим дальше, если Точка отката
Baseline фиксируем исходное состояние characterization baseline зелёный, matrix согласована tag или commit исходного состояния
Pilot мигрируем узкий безопасный slice parity subset проходит для pilot-сценария возврат к baseline tag и старому deployment
Broader update расширяем миграцию на основной контур критические потоки проходят planned validation откат к состоянию после pilot
Cleanup убираем временные shim и старые зависимости нет legacy-хвостов, поведение сохранено откат к последней рабочей фазе

Именно Pilot чаще всего спасает команду от больших неприятностей. Он специально должен быть узким. Для CashFlow Dashboard разумнее брать не самый опасный сценарий записи, а, например, один read-only endpoint вроде GET /api/subscriptions/{id}. Почему не refund? Потому что refund — это уже бизнес-операция с побочными эффектами, а цель pilot-фазы не в том, чтобы сразу выиграть войну, а в том, чтобы проверить, жизнеспособен ли сам migration-подход на небольшом участке.

flowchart TD
    A[Baseline] -->|matrix + baseline готовы| B[Pilot]
    B -->|pilot slice проходит parity subset| C[Broader update]
    C -->|critical flows проходят planned validation| D[Cleanup]

Baseline-фаза при этом не «пустая». Она очень важна, просто в ней мало романтики. В ней вы фиксируете текущий working state, проверяете characterization suite, сохраняете baseline tag, собираете matrix и перечитываете contract. Эта фаза нужна, чтобы потом не вспоминать: «А на каком именно коммите у нас всё ещё работало?»

Broader update — это уже расширение pilot-подхода на основной контур. Но только после того, как pilot доказал, что цепочка «новый runtime → новый Boot → текущий код с правками → planned validation» вообще складывается в рабочий сценарий. Cleanup в конце нужен, чтобы не оставить проект в состоянии «вроде мигрировали, но половина временных совместимых костылей так и живёт рядом со старым кодом».

4. Rollback point — артефакт, а не надежда

Как только речь заходит о rollback, многие автоматически думают про git revert. Git, конечно, прекрасен, но сам по себе он не решает всю проблему. Если вы успели поменять dependency set, переключили deployment, подняли новый runtime или, не дай бог, затронули схему данных, одной команды отката истории уже недостаточно. Поэтому rollback point в миграционном плане должен быть зафиксирован так же явно, как target version или gate фазы.

В простом случае rollback point — это tag или commit, соответствующий последнему гарантированно рабочему состоянию. Но для миграции этого мало. Вам нужно ещё понимать, какой deployment-артефакт соответствует этому состоянию, какой набор зависимостей считается предыдущим стабильным комплектом, есть ли feature flag, который позволяет выключить новый срез без экстренной хирургии, и не изменили ли вы что-то такое, что уже не вернётся простым откатом кода.

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

Хороший блок rollback в плане выглядит очень приземлённо:

## Ожидаемый откат
- baseline tag: `v2.7-baseline`
- deployment rollback: вернуть артефакт `cashflow:2.7-baseline`
- dependency rollback: восстановить wrapper и dependency block из baseline
- pilot toggle: выключить `pilot_v3_endpoint`
- db schema в pilot не меняется

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

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

5. Planned validation и open risks до старта

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

Planned validation обычно привязывается к фазам. Для Baseline это может быть зелёная characterization suite и зафиксированный baseline tag. Для Pilot — subset контрактных проверок на одном endpoint, smoke на staging и сравнение ожидаемого ответа с baseline. Для Broader update — integration tests по критическим потокам, webhook smoke, sanity check по latency, возможно, сравнение важных JSON schema. Для Cleanup — финальный прогон тех же проверок уже без legacy-костылей.

Здесь полезно развести три понятия, которые часто слипаются:

Элемент На какой вопрос отвечает Пример
Gate можно ли переходить к следующей фазе pilot slice прошёл parity subset
Planned validation чем именно это будет подтверждаться characterization, integration, smoke
Open risk что всё ещё может сломать фазу неизвестная совместимость custom UserType

Сюда как раз и доезжают незакрытые результаты исследования. Если в research notes остались assumption и unknown, а для них так и не нашлось достаточного evidence в матрице, они получают статус needs manual validation и переходят в Open risks плана. План не должен делать вид, что этих дыр больше нет.

С open risks история не менее важная. Если вы не записали риск, он не исчез. Он просто готовит неприятный сюрприз. В плане удобно разделять два типа рисков: принятые и нерешённые. Принятый риск — это то, с чем вы сознательно готовы жить на этой фазе, например короткий redeploy с небольшим техническим окном. Нерешённый риск — это то, что реально может заблокировать старт фазы, например непроверенная совместимость кастомного Hibernate-типа с новой веткой.

## Открытые риски
Принятые:
- короткий redeploy с downtime до 5 минут

Нерешённые:
- совместимость custom Hibernate UserType
- поведение старого JsonAdapter на новой jackson-ветке

Такой блок кажется простым, но именно он не даёт плану притворяться завершённым и идеальным. Хороший MIGRATION_PLAN почти всегда чуть-чуть раздражает, потому что в нём честно видно, где ещё болит.

6. Сборка MIGRATION_PLAN.md из артефактов

К этому моменту у вас уже есть почти все входы для нормального плана. Есть current-state inventory, есть compatibility matrix, есть risk map из предыдущего блока, есть characterization baseline, есть контракт сохранения поведения. Сам план не должен рождаться из воздуха. Он собирает эти куски в маршрут, который можно читать, обсуждать, ревьюить и только потом выполнять.

Практически MIGRATION_PLAN.md удобно воспринимать как один главный файл, в котором сходятся все предыдущие артефакты. Это уже не заметка для себя, а рабочий документ для вас, для ревьюера и для Claude Code, если вы используете его как помощника при подготовке черновика.

Костяк файла может выглядеть так:

# MIGRATION_PLAN

## Цель / Границы / Не-цели
## Целевые версии
## Контракт паритета фич
## Фаза 0: baseline
## Фаза 1: pilot
## Фаза 2: широкое обновление
## Фаза 3: чистка
## Планируемая валидация / откат / открытые риски

Если вы подключаете Claude Code к этой работе, ему лучше давать не абстрактную просьбу «составь план миграции», а уже собранный пакет входов. Например так:

На основе `Migration Current State`, `COMPATIBILITY_MATRIX.md`, `RISK_MAP.md` и
контракта сохранения поведения собери черновик `MIGRATION_PLAN.md`.
Нужны фазы Baseline, Pilot, Broader update и Cleanup.
Для каждой фазы укажи gate, planned validation, rollback point и open risks.
Не предлагай выполнять команды и не меняй код.

Здесь важна последняя строка. На этом этапе Claude должен помогать как редактор и аналитик, а не как нетерпеливый разработчик, который уже тянется редактировать build.gradle. Черновик он соберёт быстро, но вот проверять, действительно ли gate измерим, действительно ли rollback point конкретный, действительно ли non-goals удерживают scope, придётся уже вам.

В результате хороший MIGRATION_PLAN.md связывает всё, что вы сделали раньше. Current-state inventory отвечает за честную точку старта, compatibility matrix — за технические ограничения и порядок, контракт сохранения поведения — за смысл перехода, а phased plan — за маршрут. И когда все эти части начинают работать вместе, миграция наконец перестаёт быть фразой «ну мы там стек обновляем» и становится документом, по которому можно идти без героизма и без лотереи.

7. Облачный планировщик

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

Во-первых, у вас на руках большой комплект доказательств. Discovery из предыдущих тем модуля собрал current-state inventory, official migration guide, changelog, compatibility matrix и dependency graph — локальный контекст легко переполняется таким объёмом, а облачная поверхность спокойно держит весь evidence в одном пространстве.

Во-вторых, фазовый план требует согласованности между фазами: baseline, pilot, broader update и cleanup должны быть взаимно непротиворечивы, а облачный планировщик умеет сам ловить такие нестыковки — например, что в phase 1 удаляется устаревший API, на который ещё опирается phase 0.

В-третьих, миграция редко бывает срочной, это работа на недели и месяцы, поэтому небольшая задержка облачного агента не блокер — параллельно вы спокойно готовите baseline и tests.

Вывод облачного планировщика ложится в тот же шаблон, что описан выше: Goal, Scope, Non-goals, Target versions, Feature-parity contract, Phase 0..3, Planned validation, Rollback expectation, Open risks. Граница одобрения при этом не сдвигается: фазовый план утверждает владелец команды или технический лидер, а облачный планировщик — это инструмент для черновика и сверки согласованности, не лицо, принимающее решение.

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

После этого остаётся уже не гадать, а выполнять pilot по тем же опорам: маленьким срезом, с validation evidence и с заранее подготовленным rollback.

1
Задача
Claude code, 28 уровень, 4 лекция
Недоступна
Draft migration plan через Claude CLI
Draft migration plan через Claude CLI
1
Задача
Claude code, 28 уровень, 4 лекция
Недоступна
Полный MIGRATION_PLAN.md без execution
Полный MIGRATION_PLAN.md без execution
1
Опрос
Refactoring, modernization и migration, 28 уровень, 4 лекция
Недоступен
Refactoring, modernization и migration
Три режима изменений, discovery, changelog-driven research и поэтапный план миграции
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ