1. Эмоциональный диагноз кода бесполезен
Когда вы впервые открываете legacy-сервис, мозг очень быстро ставит диагнозы: «грязно», «перемудрили», «я хочу домой». Реакция человеческая, особенно если перед вами mrr-engine, документированный на уровне «ну оно как-то работает». Но по эмоциям решения не принимают.
Проблема ведь не в том, что эмоции неправильные, а в том, что по ним нельзя принимать решения. «MrrFormulas.java выглядит подозрительно» — коллега кивнёт из вежливости. «Низкое покрытие, логика pause/resume разбросана по двум классам, файл меняли одиннадцать раз за год разные люди» — вот это по делу.
В legacy-проекте особенно важно отделять впечатление от наблюдения. Code smell — не приговор и не задача в бэклог, а намёк: здесь может быть проблема, проверьте. Не размахивайте руками как гастрономический критик — покажите, из какого контейнера в холодильнике идёт запах и почему его нельзя игнорировать.
| Слабая формулировка | Полезная формулировка |
|---|---|
| mrr-engine какой-то мутный | mrr-engine/MrrFormulas.java меняли 11 раз за год, покрытие около 25%, а сценарий pause/resume не покрыт тестами, поэтому изменение логики MRR здесь рискованно |
| Конфиги разъехались | Флаги биллинга лежат и в application.yml, и в переменных окружения, а правила приоритета не описаны, поэтому поведение на стендах может отличаться |
| Тесты хрупкие | Интеграционный тест MrrSnapshotIT покрывает только 3 happy-path сценария и не трогает partial refund, downgrade и pause/resume |
Вот это и есть нужный вам поворот мышления. Вы не лечите систему словом «некрасиво» — сначала делаете её наблюдаемой.
2. Формула одного полезного сигнала
Чтобы не тонуть в описаниях на полстраницы, удобно пользоваться простой формулой. Держите её короткой:
Сигнал = область + evidence + риск + следующий шаг
Иногда добавляют степень уверенности — если часть вывода пока гипотеза. Это не бюрократия ради бюрократии: одинаковая форма отучает спорить «мне кажется, вот это хуже» и учит сравнивать факты.
Вот так может выглядеть одна запись по CashFlow Dashboard:
## Область
mrr-engine / pause-resume flow
## Доказательства
- `mrr-engine/MrrFormulas.java:88` — paused subscription остаётся в active set
- `subscriptions/SubscriptionService.java:154` — pause меняет статус, но не влияет на MRR
- `tests/integration/MrrSnapshotIT.java` — нет сценария pause/resume
## Риск
Business-critical flow без явной safety net
## Следующий шаг
До любых изменений зафиксировать текущее поведение сценария pause/resume
Позже сигнал уйдёт в current-state документ как подтверждённый факт либо сожмётся в строку RISK_MAP.md. Но начинается всё именно с этой короткой формы.
Обратите внимание, чего здесь нет: ни «переписать», ни «отрефакторить», ни «починить» — на discovery это нормально. Ваша задача не стать героем с плащом и Ctrl+Shift+R, а описать ситуацию так, чтобы другой инженер открыл те же файлы и увидел то же.
Полезно помнить ещё одно правило: один сигнал — одна мысль. Если в записи и deprecated dependency, и слабые тесты, и странный конфиг, и метод на двести строк — это не один сигнал, а четыре. Разделите: иначе не поймёте, что делает область опасной.
3. Источники evidence в legacy-проекте
Когда начинающие разработчики слышат слово «доказательство», они почти всегда думают только о коде. Но в legacy код — лишь один источник правды, и не самый разговорчивый: больше рассказывают тесты, покрытие, история коммитов, конфиги и сборка.
flowchart LR
A[Код и конфиги] --> D[Evidence]
B[Тесты и coverage] --> D
C[Git history и отчёты сборки] --> D
D --> E[Технический сигнал]
E --> F[Риск и следующий шаг]
Если говорить совсем практично, то в CashFlow Dashboard полезны четыре источника. Первый — исходники: классы, методы, конфигурации, миграции. Второй — тесты и покрытие: не наличие теста, а то, что именно он проверяет. Третий — git history: файлы, которые часто меняют разные люди, выдают хрупкую границу. Четвёртый — артефакты сборки: устаревшие библиотеки, предупреждения, deprecated API, нестабильные конфиги.
Простейшие команды здесь уже дают много информации:
git log --since="1 year ago" --oneline -- mrr-engine/MrrFormulas.java
# 9f3a2c1 Исправлен partial refund
# 6a4d8e0 Учтён pause/resume
# 21bc774 Коррекция MRR на границе месяца
Такой вывод сам по себе ещё не означает «файл плохой». Но если вы видите, что файл постоянно чинят по соседним бизнес-сценариям, — это повод насторожиться: логика тут, возможно, слишком центральная и хрупкая.
Ещё один полезный сигнал — покрытие:
./gradlew test jacocoTestReport
# BUILD SUCCESSFUL
# HTML-отчёт покрытия: build/reports/jacoco/test/html/index.html
Если отчёт показывает, что mrr-engine покрыт на 25%, а payments — на 60%, это не «хороший против плохого», а факт, который соединяется с бизнес-критичностью: для модуля, влияющего на MRR и отчёты для Commerce OS, 25% — повод быть аккуратным.
Иногда полезны и совсем «приземлённые» команды:
grep -R "TODO\|FIXME" -n legacy/
# legacy/OldBillingUtils.java:47 // TODO убрать после миграции PSP
# legacy/RefundMath.java:113 // FIXME временный расчёт proration
TODO и FIXME не доказывают проблему автоматически. Но если они стоят в расчётах refund и proration без тестов рядом — это уже не записка на холодильнике, а часть картины.
4. Static signals, заслуживающие внимания
Когда вы смотрите на legacy, соблазн велик: длинный метод, старый класс, странный комментарий — всё в список. Через полчаса у вас двадцать пять пунктов, и пользы как от прогноза погоды на Марсе. Поэтому важно отличать шум от сигналов.
Static signals — это сигналы, которые можно получить без запуска продакшена и правок кода. Ниже — типичные случаи CashFlow Dashboard:
| Что заметили | Почему это важно |
|---|---|
| Низкое покрытие в mrr-engine | Это business-critical область, и любое изменение здесь трудно проверить локально без дополнительной safety net |
| legacy/OldBillingUtils.java используется в десятках мест | Зависимость на «старый универсальный класс» часто означает скрытую связанность и дорогую стоимость изменения |
| PaymentRetryService.java часто меняли разные авторы | Это признак файла с высоким churn: зона нестабильна, ответственность размыта, а интерфейс, возможно, выбран неудачно |
| Feature flags размазаны по application.yml и env | Конфигурационное поведение может различаться между средами, а значит, ошибка будет воспроизводиться не всегда |
Есть ещё несколько частых признаков, которые полезно держать в голове. Дублирование логики — про риск расхождения правил, а не про некрасивый код. Метод на 150 строк опасен не длиной, а тем, что в нём трудно изолировать один бизнес-сценарий от другого. Устаревшая зависимость в платежах страшнее такой же в экспорте CSV — разный радиус поражения. Хрупкий тест — слабый датчик: падает не тогда, когда система сломана, и молчит, когда она поплыла.
Отдельно запомните простую мысль: code smell — это приглашение проверить, а не окончательный диагноз. Если вы увидели большой класс, не пишите сразу «надо срочно дробить». Сначала посмотрите: насколько он связан с бизнес-критичными сценариями, как часто менялся, есть ли тесты. Красота кода вторична — важны стоимость и риск изменения.
5. Запрос к Claude: анализ вместо угадывания
Claude в таких задачах очень полезен, но только если вы задаёте ему рамку. Без неё он выдаёт красивый обзор про архитектурные недостатки, часть даже похожа на правду. Но «похоже на правду» и «подтверждено» — разные вещи.
Плохой запрос звучит так: «Найди весь технический долг в проекте». В таком виде задача слишком широкая — Claude приносит красивый, но рыхлый текст.
Гораздо лучше работает узкий запрос с форматом ответа и запретом на фантазию:
Проанализируй только `mrr-engine/MrrFormulas.java` и связанные тесты.
Не предлагай исправлений.
Верни не больше 5 сигналов в формате:
область -> evidence -> риск -> следующий шаг.
Используй только факты, которые можно подтвердить файлами
или выводом команд. Если уверенности нет, пометь это как гипотезу.
Важнее всего последнее — разрешение на неопределённость. Многие новички невольно подталкивают ИИ к ложной уверенности: им кажется, что ответ обязан быть решительным. В legacy наоборот — хороший ответ спокойно говорит «здесь у меня гипотеза, но доказательства слабые».
Полезен и двухпроходный режим работы. Сначала просите собрать факты: какие тесты, какой churn, есть ли TODO, какие методы вызываются чаще. Потом на этих фактах — 3–5 сигналов. Галлюцинаций меньше: Claude работает как аналитик с материалами дела.
И ещё одно практическое правило: на discovery держитесь read-only или plan-режима. Как только у модели появляется возможность «заодно поправить», она смешивает диагностику с операцией. А вам нужен именно диагноз.
6. Связка технического сигнала с бизнес-риском
Здесь происходит важный переход: от «я вижу странный код» к «я понимаю, почему это важно». Некрасивый код не всегда опасен, аккуратный класс не всегда безопасен — бизнес-риск и эстетика не обязаны идти рука об руку.
Представьте два файла. ReportCsvFormatter.java некрасивее — длинноват, дублирует форматирование. Но через MrrFormulas.java идёт расчёт MRR, попадающий в дашборд Commerce OS и в отчёты для финансового менеджера, и потому он важнее, хоть и короче.
| Область | Технический сигнал | Бизнес-вес | Практический вывод |
|---|---|---|---|
| mrr-engine/MrrFormulas.java | Низкое покрытие, высокий churn, спорная логика pause/resume | Высокий | Изменять только после фиксации текущего поведения |
| reporting/ReportCsvFormatter.java | Дублирование и длинный метод | Средний или низкий | Можно отложить, если сейчас нет задач в этой зоне |
| application.yml + env-флаги | Непрозрачный приоритет конфигурации | Высокий для воспроизводимости | Сначала задокументировать фактическое поведение сред |
| legacy/OldBillingUtils.java | Слишком много зависимых мест | Высокий по стоимости изменения | Любая правка требует оценки радиуса воздействия |
Именно здесь рождается хороший recommended next step — не «переписать всё», а действие, пропорциональное риску. Для business-critical no-test зоны — фиксация текущего поведения. Для high-churn файла — проверка ownership и карты вызовов. Для fragile config — документация приоритета значений. Для deprecated utility — оценка того, сколько мест от него зависит. Чем приземлённее, тем лучше.
Legacy не любит героизм. Legacy любит конкретику.
7. Фиксация сигналов в рабочей заметке
И ещё одна привычка, без которой signal log развалится: не держать сигналы только в голове или в чате с Claude. Память у человека дырявая, у длинной сессии с ИИ — тоже. Не зафиксировали — через два дня снова смотрите в тот же файл и мучительно вспоминаете, почему он казался опасным.
Такая заметка — не постоянный артефакт рядом с RISK_MAP.md, а промежуточный signal log. Он удерживает наблюдения между discovery-note и устойчивыми current-state- и decision-артефактами, пока вы не собрали их в ARCHITECTURE_CURRENT.md или запись risk map.
Фиксировать лучше коротко, но в одинаковом формате. Не нужен огромный отчёт с философией — нужны проверяемые записи, которые можно быстро пробежать глазами. Например, рабочая заметка в markdown может выглядеть так:
## Сигнал 1
Область: `mrr-engine/MrrFormulas.java`
Доказательства:
- low coverage (~25%)
- `paused` остаётся в active set
- нет теста на pause/resume
Риск: business-critical flow без safety net
Следующий шаг: сначала зафиксировать текущее поведение сценария
## Сигнал 2
Область: `legacy/OldBillingUtils.java`
Доказательства:
- используется в 30+ местах
- в файле есть `TODO` про старый PSP
Риск: высокая связанность, дорогая цена изменения
Следующий шаг: не менять точечно без карты зависимостей
Такой формат хорош тем, что переживает текущую сессию — он надёжнее фразы «ну мы же вроде вчера поняли, что там всё хрупко».
Если хотите, можно добавить ещё одно поле — Уверенность или Проверить вручную — там, где вывод построен на косвенных сигналах. Честная пометка «гипотеза» доверие к документу укрепляет.
В какой-то момент вы заметите, что legacy перестаёт выглядеть туманным болотом. Не потому, что код стал лучше — он не менялся. А потому, что появились наблюдаемые ориентиры: где мало тестов, где высокий churn, где конфиг врёт, где документация расходится с кодом. С этого момента разговор о техническом долге — не эмоциональный, а инженерный.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ