JavaRush /Курси /Claude code /Технічний борг і static s...

Технічний борг і static signals

Claude code
Рівень 26 , Лекція 1
Відкрита

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, які методи викликаються найчастіше. Потім на цих фактах — 35 сигналів. Галюцинацій менше: 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, де конфіг бреше, де документація розходиться з кодом. Від цього моменту розмова про технічний борг — не емоційна, а інженерна.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ