1. «Не бачу помилки» — це ще не «полагодив»
Коли ви тільки починаєте працювати з кодом, дуже легко сплутати дві різні речі: «помилка більше не спливає» і «дефект виправлено». З AI їх особливо легко сплутати. Claude уміє переконливо маскувати проблему: поставить null-перевірку не там, надто широко перехопить виняток, поверне гарне повідомлення. Відчуття перемоги є, причина лишилася. Баг просто переодягнувся і чекає на наступний реліз.
Простіше кажучи, regression test — маленька перевірка, яка ловить саме цей баг, щоб він не повернувся після чергового «невеликого поліпшення». Regression evidence — докази, що ви закріпили виправлення як перевірювану поведінку, а не заглушили симптом. Яскрава мітка на тріщині в стіні: знову поповзе — побачите відразу.
У Commerce OS це добре видно. Надіслали вручну порожню корзину, побачили 400 — замало. Через два тижні хтось змінить валідацію замовлення, не знаючи про старий інцидент, і система знову почне відповідати 500. З regression-тестом відкат станеться не тихо, а з червоною лампочкою. А червона лампочка корисніша за оптимізм.
2. Bugfix-loop: дисциплінований цикл
Коли баг уже зрозумілий і root cause знайдено, дуже хочеться пришвидшитися: «Claude, усе, просто полагодь». Але багфікс — не імпровізація. Набагато надійніше сприймати його як короткий цикл, де кожен крок дає конкретний артефакт: такий цикл легше перевірити, пояснити і покласти в PR.
flowchart TD
A[Відтворили баг] --> B[Зафіксували root cause]
B --> C[Написали падаючий regression test]
C --> D[Зробили мінімальний fix]
D --> E[Запустили прицільні перевірки]
E --> F[Перевірили сусідні перевірки]
F --> G[Прочитали diff]
G --> H[Зібрали regression evidence]
Нижче зручно тримати перед очима просту карту цього циклу:
| Крок | Що ви робите | Що отримуєте на виході |
|---|---|---|
| 1 | Повторюєте баг | Зрозумілий сценарій відтворення |
| 2 | Формулюєте root cause | Коротке твердження про причину дефекту |
| 3 | Пишете regression-тест до фіксу | Падаючу перевірку, яка ловить баг |
| 4 | Вносите мінімальний fix | Невеликий і зрозумілий diff |
| 5 | Запускаєте targeted checks | Підтвердження, що конкретний дефект виправлено |
| 6 | Запускаєте сусідні перевірки | Базову впевненість, що поряд нічого не зламали |
| 7 | Читаєте diff очима | Контроль scope і відсутність зайвих змін |
| 8 | Фіксуєте evidence | Матеріал для EVIDENCE_LOG.md і PR walkthrough |
Зверніть увагу на одну тонкість. У цьому циклі тест іде до фіксу, а не після. Змінили код, а потім написали тест під уже зміненою поведінкою — ви не довели, що тест ловить старий баг. Лише новий код узгоджений сам із собою. А узгодженість буває і в помилки. Код узагалі дуже терпимий до чужих хибних уявлень.
3. Спочатку падаючий regression test
На цьому кроці зазвичай відбувається маленька внутрішня боротьба: вам уже дуже хочеться виправляти код, а я ніби спеціально прошу спочатку тест. Поки він не падає на старій поведінці, у вас немає точки відліку — а без неї виправлення легко сплутати із самонавіюванням.
Оскільки баг проявлявся на рівні HTTP-відповіді, то й тест тут дивиться на HTTP. Розбиратися в тестовій інфраструктурі Spring зараз не потрібно. Тримайте одну думку: mockMvc — маленький вбудований клієнт, який надсилає запит у застосунок без реального браузера.
import org.junit.jupiter.api.Test;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Test
void rejectsEmptyCart() throws Exception {
mockMvc.perform(post("/api/orders")
.contentType("application/json")
.content("{\"items\":[]}"))
.andExpect(status().isBadRequest()); // до фіксу: очікували 400, отримали 500
}
Якщо ви запускаєте лише цей тест до виправлення, він і має впасти. Саме цього ми й хочемо. Падаючий тест тут не проблема, а доказ, що ви зловили потрібний баг. Став би одразу зеленим — отже, він узагалі не перевіряє цей дефект. Локальний запуск:
./gradlew :orders:test --tests OrderControllerTest.rejectsEmptyCart
# До фіксу тест падає: endpoint повертає 500 замість очікуваного 400
З Claude Code тут корисно працювати буквально. Не «виправ баг» — спочатку лише тест, потім production-код:
Спочатку напиши regression test для бага з порожньою корзиною.
Не змінюй production-код.
Покажи, що тест падає на поточній поведінці, і коротко поясни, чому це пов’язано з знайденою root cause.
Такий порядок дисциплінує і вас, і AI. Claude перестає бути поспішним виправлячем усього на світі та стає акуратним помічником на одному кроці.
4. Мінімальний fix: виправляємо причину, а не магазин
Після падаючого regression-тесту з’являється дуже приємне відчуття: тепер можна виправляти з зрозумілою метою. І саме в цьому місці AI любить влаштувати маленьке свято ініціативи: замість локального fix заодно винесе загальну валідацію, уніфікує обробку винятків, перейменує DTO, причеше архітектуру. Красиво — і для багфіксового PR майже завжди шкідливо.
Мінімальний fix бʼє рівно по причині і майже не торкається решти системи. У нашому випадку порожня корзина проходить надто далеко — зловімо її раніше і повернімо коректний 400.
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
if (request.items() == null || request.items().isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Корзина порожня");
}
return checkoutService.checkout(request.items());
Цей шматок коду добрий не геніальністю, а тим, що його легко пояснити: не змінює контракт успішного замовлення, не чіпає інші endpoint’и, не тягне залежності, не перетворює багфікс на «архітектурне переосмислення чекауту». Іноді найкращий багфікс — нудний багфікс. Нудьгу тут сильно недооцінюють.
Якщо хочете спрямувати Claude у потрібне русло, формулюйте задачу дуже жорстко:
Зроби мінімальний fix лише для бага з порожньою корзиною.
Не змінюй інші endpoint'и.
Не винось загальну валідацію в нові класи.
Не додавай залежності.
Після зміни покажи diff і запусти тільки пов’язані перевірки.
Зверніть увагу: ми не просимо «зроби красивіше». У bugfix-задачах «красивіше» — запрошення до scope creep: одна стрічка перевірки обростає новим класом валідації, уніфікацією винятків і перейменуванням DTO, і багфікс тихо перетворюється на рефакторинг чекауту.
5. Одного зеленого тесту замало
Коли regression-тест став зеленим, виникає наступна спокуса: вирішити, що робота завершена. Один зелений тест — добре, але ще не вся картина. Ревʼюер, тімлід або ви самі через тиждень захочете зрозуміти не лише «що стало зеленим», а й «чому цьому можна вірити». Потрібен пакет доказів — кілька незалежних ознак, що виправлення варто прийняти.
Для Commerce OS такий bundle зручно представити так:
| Доказ | Що він підтверджує | Наш приклад |
|---|---|---|
| Тест, що падав до фіксу | Баг справді існував | падав на 500 |
| Той самий тест після фіксу | Конкретний дефект виправлено | Після правки тест чекає 400 і зелений |
| Ручна перевірка сценарію | Користувач бачить правильну поведінку | Порожня корзина більше не валить замовлення |
| Суміжні перевірки зелені | Поряд нічого очевидно не зламали | Перевірки модуля orders проходять |
| Маленький diff | Не було зайвого scope | Змінено лише controller і test |
| Root cause note | Fix пов’язаний із причиною, а не із симптомом | Рання валідація порожньої корзини |
Ручну перевірку можна зробити дуже просто. Навіть якщо ви не шанувальник консолі, тут корисно один раз побачити поведінку очима HTTP:
curl -i -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{"items":[]}'
# Після фіксу очікуємо: HTTP/1.1 400 Bad Request
Якщо у вас тепер є і зелений тест, і коректний ручний 400, і маленький diff, картина стає набагато переконливішою. Це вже не «ну, здається, запрацювало», а «ось докази, що ми зловили старий дефект і виправили без попутного хаосу».
На практиці такі нотатки зручно спочатку тримати в EVIDENCE_LOG.md, а потім переносити коротку версію в PR walkthrough:
## Regression evidence
- До фіксу тест `OrderControllerTest#rejectsEmptyCart` падав: endpoint відповідав 500
- Після фіксу той самий тест зелений
- Ручна перевірка `POST /api/orders` з `{"items":[]}` повертає 400
- Додаткові перевірки модуля `orders` зелені
- Diff обмежено двома файлами: controller + test
Зверніть увагу: жодної довгої повісті. Evidence — коротко, перевірювано, по фактах. Ревʼюер не повинен гадати, де закінчуються спостереження і починаються добрі наміри.
6. Claude Code у bugfix-loop без приховування
На цьому етапі Claude Code особливо корисний — але тільки якщо не змушувати його грати всі ролі одразу. Одна сесія пише тест, пише fix, а потім бадьоро запевняє, що усе ідеально: самовпевнений колега, який сам собі виписав премію. З AI це трапляється ще частіше, ніж з людьми — надто вже він любить звучати переконливо.
Набагато надійніше розділити роботу хоча б на три ролі.
| Роль Claude | Що ви у нього просите | Навіщо це потрібно |
|---|---|---|
| Автор regression-тесту | Написати тест і показати, що він падає | Зафіксувати стару зламану поведінку |
| Автор мінімального fix | Змінити лише потрібну ділянку коду | Утримати diff маленьким і зрозумілим |
| Збирач evidence | Коротко перелічити, чим підтверджено багфікс | Підготувати матеріал для EVIDENCE_LOG.md і PR |
На практиці це можуть бути три короткі запити, а не один довгий. Спочатку:
Напиши regression test для бага з порожньою корзиною.
Не змінюй production-код.
Покажи, на чому саме тест падає.
Потім, після підтвердження тесту:
Зроби мінімальний fix під цей test.
Не чіпай інші endpoint'и і не роби рефакторинг.
Після зміни запусти тільки пов’язані перевірки.
І лише наприкінці:
Збери короткий regression evidence:
який test падав до фіксу, що стало після фіксу,
які додаткові перевірки запущено,
які файли змінено.
Не оцінюй якість рішення, просто переліч факти.
Остання фраза тут особливо важлива: «не оцінюй якість рішення, просто переліч факти». Вона захищає від режиму, де Claude не збирає evidence, а продає вам власний код. Продавець з AI виходить іноді навіть кращий, ніж програміст. Тримайте його подалі від каси.
Тест, що падав до фіксу, мінімальний diff, зелений тест після виправлення, короткий набір доказів — і багфікс перестає бути крихкою надією «ніби більше не спливає». Regression test залишає на дефекті яскраву мітку: знову поповзе — червона лампочка засвітиться сама, без вас. Саме цим закритий баг відрізняється від заглушеного: не «AI перестав помилятися», а поведінку зафіксовано як перевірювану й захищено від відкату.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ