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. Статичні сигнали, що заслуговують на увагу
Коли ви дивитеся на legacy, спокуса велика: довгий метод, старий клас, дивний коментар — усе в список. Через пів години у вас двадцять п’ять пунктів, і користі як від прогнозу погоди на Марсі. Тому важливо відрізняти шум від сигналів.
Статичні сигнали — це сигнали, які можна отримати без запуску продакшена і правок коду. Нижче — типові випадки 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 | Занадто багато залежних місць | Висока за вартістю зміни | Будь-яка правка потребує оцінки радіуса впливу |
Саме тут народжується хороший рекомендований наступний крок — не «переписати все», а дія, пропорційна ризику. Для 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, де конфіг бреше, де документація розходиться з кодом. Від цього моменту розмова про технічний борг — не емоційна, а інженерна.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ