JavaRush /Курсы /Claude code /Safety baseline для модернизации модуля

Safety baseline для модернизации модуля

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

1. Модернизация без baseline — это лотерея

В legacy-модуле вроде mrr-engine опаснее всего начинать с заманчивой мысли: «Давайте сначала чуть-чуть почистим код, а потом посмотрим, что сломалось». Это ремонт квартиры без замеров и фотографий: стену снесли, проводка торчит, а доказать, что розетка была не здесь, уже нечем. Не зафиксировали поведение до правки — после рефакторинга не отличите упрощение от поломки.

В CashFlow Dashboard это особенно болезненно, а цена прямая: MRR влияет на деньги и отчёты. Вынесли «безобидный» метод — MRR за июнь уехал на два процента. Это не инженерный спор, это письмо от финансового менеджера в очень грустном тоне. Legacy-код не кричит «Вы сломали совместимость» — он молча меняет результат, а узнаёте вы позже.

Поэтому перед модернизацией нам нужен не просто набор тестов, а защитный baseline — система проверок: «Вот так модуль ведёт себя на этих сценариях. Пока идёт серия структурных изменений, поведение остаётся стабильным там, где мы обещали его сохранить».

Behavior inventory
    → выбираем критичные сценарии
    → фиксируем наблюдаемое поведение
    → собираем tests + snapshots + smoke-checks
    → описываем это в CHARACTERIZATION_TESTS.md
    → прогоняем baseline перед каждым meaningful diff

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

2. Baseline — это система проверок, а не один тест

Когда впервые слышишь про safety baseline, легко подумать: «Надо просто написать побольше тестов». Нет. Обычный тест проверяет, как система должна работать. Baseline — как система реально работает сейчас, даже если поведение вам не нравится: сначала честно зафиксировать странность, чтобы потом случайно не «починить» её внутри рефакторинга. В CashFlow Dashboard это несколько слоёв проверок, а не один тип.

Вид проверки Что фиксирует Когда особенно полезен Пример в CashFlow
Characterization test Текущее наблюдаемое поведение сценария Когда нужно сохранить поведение при рефакторинге Возврат в периоде влияет на следующий месяц
Golden master Полный снимок сложного результата Когда много полей и формула разветвлённая JSON-отчёт по MRR за май
Smoke check Жизнеспособность критического пути Перед каждым небольшим diff Пересчёт месячного отчёта проходит без ошибки
Contract/API check Совместимость внешнего формата Если модуль кормит другие части системы Схема выгрузки отчёта не меняется
Manual verification Проверка глазами владельца процесса Если важен бизнес-контекст или внешний эталон Сверка с отчётом финансиста за май-2024

Вот почему baseline — именно система, а не один файл с тестом. Одного characterization test мало: прикроет важную ветку, но пропустит смену формата отчёта. Одного golden master тоже мало: грубый, падает из-за мелочей вне смысла.

Здесь полезно вспомнить модуль про безопасный рефакторинг: там мы делали function-level characterization — одна функция, её входы и выходы, маленькое изменение. Сейчас масштаб другой: защищаем не одну функцию, а целый модуль и цепочку будущих рефакторингов. Baseline должен выдержать серию изменений, а не один commit.

3. Сценарии baseline выбираем по риску и ценности

Behavior inventory и RISK_MAP.md у вас уже на руках — и это отличная новость. Плохая новость в том, что туда легко набросать столько сценариев, что baseline станет музеем всего, что вообще когда-либо происходило в модуле — медленным, хрупким, бесполезным. Задача не «покрыть всё», а выбрать сценарии с максимальным риском и максимальной ценностью: важные бизнесу, часто ломающиеся при структурных изменениях и уже помеченные в RISK_MAP как чувствительные — полный refund в первый период, partial refund в середине периода, смена плана в день биллинга, pause/resume подписки. Baseline сначала защищает ядро, а не объемлет весь мир с первого захода.

Посмотреть на отбор можно так:

Сценарий Почему попадает в baseline Чем проверяем
Partial refund mid-period Высокий бизнес-риск и сложная формула Characterization + golden master
Plan switch on billing day Часто ломает расчёт границ периода Characterization
Pause / resume subscription Легко потерять совместимость по датам Characterization + smoke
Monthly MRR export Важен внешний формат для downstream-систем Golden master + contract check

Claude Code здесь полезен не как судья, а как аккуратный аналитик. Отдайте ему inventory и risk map в режиме inspect-only — пусть предложит кандидатов, но решение за вами:

Проанализируй behavior inventory и RISK_MAP для cashflow/mrr-engine.
Не меняй код.

Для каждого кандидата на safety baseline верни:
- сценарий,
- почему он критичен,
- какой тип проверки подходит,
- какие входные данные нужно зафиксировать,
- какие observable outputs нужно сравнивать,
- на какие файлы и evidence ты опираешься.

Обратите внимание на формулировку: здесь нет «напиши тесты». Сначала анализ, потом выбор. Claude уверенно выдаст десяток кейсов, но если среди них нет вашего самого болезненного refund-сценария — он не дочитал контекст или не понял downstream-зависимость. Baseline начинается не с генерации тестов, а с согласования списка сценариев.

4. Characterization test фиксирует реальность

Самый частый промах в модернизации случается очень рано. Человек садится писать characterization test — и незаметно пишет bugfix test: не на текущее поведение, а на желаемое. Психологически это понятно: хочется наконец сделать «как правильно». Но так вы подменяете модернизацию изменением бизнес-логики — другая задача, с другим риском. Чтобы не путаться, жёстко разделяйте четыре категории поведения.

Категория Что это означает Что делать в baseline
Current behavior Так система работает сейчас Зафиксировать
Desired behavior Так хотелось бы, чтобы работала Не включать в refactor baseline
Known bug Мы понимаем, что это баг, но он существует Явно пометить и временно сохранить
Compatibility behavior Поведение странное, но на него уже завязаны другие части Зафиксировать особенно внимательно

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

Небольшой characterization test может выглядеть так:

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

@Test
void refund_в_периоде_пока_влияет_на_следующий_месяц() {
    Report may = mrrEngine.calculate("fixtures/refund-same-period.json", "2024-05");
    Report june = mrrEngine.calculate("fixtures/refund-same-period.json", "2024-06");

    assertEquals("100.00", may.getMrrTotal());   // май не меняется
    assertEquals("80.00", june.getMrrTotal());   // июнь уменьшается на 20
}

Это не тест в жанре «ура, логика верная», а «пока система ведёт себя вот так, и рефакторинг это не сломает». Такой тест дисциплинирует и вас, и Claude Code: предложит AI «улучшить формулу» вместе с выносом метода — у вас уже есть причина сказать нет, это другая задача.

5. Golden master дополняет тесты снимком результата

Иногда модуль устроен так, что разложить его на десяток маленьких assertions трудно, долго или невыгодно. mrr-engine строит итоговый отчёт с агрегатами, разбивкой по планам, churn delta и парой служебных полей. Тут выручает golden master — эталонный снимок результата на фиксированном входе.

Аналогия простая: characterization test — проверка отдельных показателей на приборной панели, golden master — фотография всей панели целиком. Вход фиксирован — значит, после рефакторинга фотография обязана совпасть. Не совпала — вы что-то изменили. Не обязательно плохо, но точно заметно.

Простейший вариант может выглядеть так:

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

@Test
void майский_MRR_отчет_совпадает_со_снимком() {
    String actual = snapshotService.buildNormalizedSnapshot("fixtures/may-2024.json");
    String expected = TestFiles.read("snapshots/may-2024.snapshot.json");

    assertEquals(expected, actual); // снимок совпадает целиком
}

Здесь ключевое слово — Normalized. Timestamp, случайный порядок полей или технический ID прогона — и snapshot падает не по делу. Поэтому хороший golden master почти всегда нормализуется: убирает поля без бизнес-смысла, стабилизирует порядок элементов, приводит округление к единому виду.

Именно поэтому golden master не заменяет characterization tests, а дополняет: быстро ловит массовые изменения, но плохо объясняет, что именно сломалось. Characterization точнее показывает смысл отклонения. Вместе — здоровая пара.

6. CHARACTERIZATION_TESTS.md — карта baseline

Когда baseline начинает расти, в нём легко потеряться. Один сценарий ушёл в JUnit-тест, другой в снимок, третий проверяет бухгалтер вручную, четвёртый помечен как странность ради совместимости. Не собрать это в явный артефакт — и через неделю не вспомните, зачем этот snapshot. Нужен CHARACTERIZATION_TESTS.md — не вместо тестов, а как карта: какие сценарии входят в baseline, чем проверяются, на какие evidence опираются, где остаются ручные шаги. В legacy это особенно ценно — часть знания живёт не в коде, а в головах и старых отчётах.

Фрагмент может выглядеть так:

# CHARACTERIZATION_TESTS.md

## Сценарий: partial_refund_mid_period
- Уровень проверки: characterization + golden master
- Вход: fixtures/partial-refund-mid-period.json
- Наблюдаемые выходы: mrr_total, churn_delta, report_rows
- Известная особенность: refund пока влияет на следующий месяц
- Авто-проверки: MrrEngineCharacterizationTest, snapshots/may-2024.snapshot.json
- Manual verification: сверка с отчётом финансиста за май-2024
- Evidence: src/main/java/.../MrrEngine.java, reports/may-2024.csv

Обратите внимание на важную деталь: manual verification не должна теряться среди автоматических проверок — ручная сверка видна сразу. Иначе через пару PR кто-нибудь решит, что baseline «полный», хотя половина уверенности держится на неформальном знании команды.

Хороший CHARACTERIZATION_TESTS.md всегда отвечает на три вопроса: что мы защищаем, чем это проверяем, почему сценарий важен. Не видно по файлу — значит, он стал декорацией.

7. Четыре слоя verification на уровне модуля

Модель verification вам уже встречалась раньше, но теперь она применяется не к одной функции, а ко всему modernization-циклу модуля. Это важный сдвиг масштаба. Раньше L1 значил: «Как я проверю вот этот change?» Теперь: «Какие critical behaviors всего модуля обязаны оставаться стабильными на протяжении серии рефакторингов?»

На CashFlow это выглядит так:

Слой Вопрос Пример для mrr-engine
L1 — план проверки Что обязано сохраниться? Refund, pause/resume, billing-day switch не меняют observable output
L2 — test design Какими проверками это ловим? Characterization, golden master, smoke, contract check
L3 — local harness Что гоняем локально перед каждым diff? ./gradlew test по characterization suite и snapshot-проверкам
L4 — CI integration Что не даём merge-нуть в PR? Те же suite и snapshots в CI-gate

Здесь полезно запомнить одну мысль. Если после второго-третьего рефакторинга baseline падает на сценарии, который вы «вообще не трогали», — не спешите обновлять snapshot. Остановитесь: изменение задело более широкую область, чем казалось, либо baseline зафиксирован слишком хрупко, либо связи внутри модуля сильнее, чем вы думали.

Именно поэтому verification на уровне модуля — не «больше тестов». Это дисциплина видеть модуль как систему с зависимостями, а не как набор случайных методов.

8. Claude Code как аналитик, а не как автор baseline

Claude Code на этом этапе действительно очень полезен: пройдётся по behavior inventory, найдёт файлы со сценариями, предложит кандидатов на characterization tests, соберёт черновик CHARACTERIZATION_TESTS.md, подскажет output для golden master. При одном принципиальном условии: сначала исследование, потом предложение, потом решение человека. Перескочите порядок — получите красивую автоматизацию самообмана.

Хорошо работает такой запрос:

Проанализируй behavior inventory для cashflow/mrr-engine.
Не меняй код и не пиши тесты сразу.

Нужно:
- предложить 5–7 сценариев для safety baseline,
- для каждого указать observable outputs,
- отметить known quirks и compatibility behavior,
- показать, какие сценарии лучше покрыть characterization test, а какие golden master,
- сослаться на файлы, фикстуры и existing tests.

После этого полезно сделать второй проход уже через reviewer-логику, чтобы проверить сам baseline на здравость:

Проверь предложенный baseline как reviewer.
Ищи две вещи:
- не превратили ли мы characterization-тесты в bugfix-тесты,
- не забыли ли мы явно отметить manual verification и known quirks.
Код не меняй.

Здесь особенно кстати ваши прошлые артефакты agents/tester.md и agents/reviewer.md: первый помогает собрать кандидатов, второй — не поверить в них слишком быстро. Human-in-the-loop без пафоса: Claude ускоряет подготовку, но что замораживать как текущее поведение, решает инженер — только он держит бизнес-контекст и границы задачи.

Когда на руках зелёный baseline, оформленный в CHARACTERIZATION_TESTS.md, legacy-модуль перестаёт быть тёмным лесом, куда страшно заходить даже с фонариком. Он всё ещё сложный, капризный, иногда слегка драматичный — как бухгалтерия в последний день месяца, — но теперь у него есть измеримое поведение. Значит, к нему можно прикасаться не на удачу, а по-настоящему инженерно. И вот с этой точки уже имеет смысл брать первый маленький шаг рефакторинга: baseline будет не фоном, а реальной страховкой.

1
Задача
Claude code, 27 уровень, 0 лекция
Недоступна
Добавление characterization test для legacy-quirk с refund
Добавление characterization test для legacy-quirk с refund
1
Задача
Claude code, 27 уровень, 0 лекция
Недоступна
Документ baseline в CHARACTERIZATION_TESTS.md
Документ baseline в CHARACTERIZATION_TESTS.md
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ