1. Гарний план — ще не гарний результат
Коли план уже готовий, легко відчути, ніби найскладніше позаду. Але саме на етапі реалізації Claude найчастіше починає «старатися надто сильно». Він не злий і не ледачий — він любить бути корисним. Іноді небезпечно корисним.
Одна думка тримає всю лекцію:
Зміни Claude — це не готове рішення, а пропозиція, виражена через diff.
Якщо зовсім просто, diff — це список рядків, доданих, вилучених або змінених у проєкті. Не «версія істини», не «майже готова фіча». Ваша робота не аплодувати правці, а вирішити: прийняти її чи ні.
Звідси controlled implementation loop — керований цикл реалізації. Кожен крок проходить один ланцюжок:
Один крок плану → один зрозумілий diff → одна прицільна перевірка → одне рішення.
Ця схема корисна навіть новачку, який поки не дуже впевнено читає код: вона звужує область невідомого. Замість тривожного «що Claude переробив у всьому проєкті?» — спокійніше запитання: «Цей diff відповідає поточному кроку?»
flowchart TD
A[Беремо один крок із плану] --> B[Даємо Claude обмежений запит]
B --> C[Claude вносить локальні зміни]
C --> D[Запускаємо прицільну перевірку]
D --> E[Читаємо diff]
E --> F{Яке рішення?}
F -->|Прийняти| G[Фіксуємо результат]
F -->|Уточнити| H[Звужуємо крок і повторюємо]
F -->|Зупинитися| I[Повертаємося до плану]
Важливо помітити одну тонкість. Controlled loop не перетворює вас на людину, яка вручну диктує Claude кожен рядок, — це не мікроменеджмент. Claude й надалі працює автономно, але рамка тепер інженерна: він вільний усередині кроку, а не всередині всього завдання.
2. Один крок плану — одна одиниця роботи
Найчастіша проблема починається ще до першого запиту на реалізацію — у той момент, коли «маленьким кроком» називають будь-яке завдання, яке можна швидко вимовити. Фраза «виправити checkout» коротка. Маленьким кроком вона від цього не стає.
Гарний крок — одна змістовна одиниця роботи: її можна зрозуміти, перевірити й відкотити окремо. Достатньо маленька, щоб diff читався очима; достатньо змістовна, щоб після неї змінювалася поведінка або структура.
Уявімо, що в AI Commerce Growth OS у нас уже є затверджений план по issue: порожня корзина на checkout дає помилку 500.
# План реалізації ISSUE-432
1. Додати захист від порожньої корзини в `OrderController`.
2. Оновити сценарій перевірки порожньої корзини в `OrderControllerTest`.
3. Прогнати перевірки для модуля `orders` і перечитати diff.
Зверніть увагу: це вже не «полагодити checkout», а три окремі дії. Зараз нас цікавить лише перший крок. Не «раз почали, виправимо й тест». Із такої дисципліни народжуються акуратні PR.
| Слабке формулювання кроку | Робоче формулювання |
|---|---|
| «Виправити checkout» | «Додати захист від порожньої корзини в OrderController» |
| «Нормалізувати validation» | «Не чіпати інші endpoint’и, змінити лише поведінку checkout» |
| «Почистити код заодно» | «Не робити попутний refactoring поза поточним кроком» |
Якщо крок зачіпає одразу контролер, сервіс, mapper, SQL і заодно build.gradle, це, найімовірніше, не крок, а маленька катастрофа, яку акуратно назвали. Роздробіть план до реалізації. Скромність — не найнадійніша риса в старанного AI.
3. Запит на поточний крок: три обмежувачі
На етапі реалізації Claude особливо чутливий до формулювань. Розпливчастий запит дає розпливчастий diff — а diff, на відміну від пояснення, потім ще й розгрібати.
У хорошого запиту на реалізацію одного кроку є три обов’язкові обмежувачі: який саме крок зараз, які файли можна змінювати, яку прицільну перевірку запустити після правки. Решта — додатки; каркас — ці три.
Реалізуй лише крок 1 із затвердженого плану.
Не переходь до кроку 2.
Змінюй лише `orders/OrderController.java`.
Якщо знадобиться інший файл, спочатку переліч його й поясни навіщо.
Після змін запусти перевірку для поточного кроку
і коротко опиши отриманий diff.
Тут особливо важлива фраза «не переходь до кроку 2». Вона здається майже смішною — доки ви не побачите, як Claude бадьоро «допрацьовує все одразу», тому що йому здається, що так буде корисніше. Це як стажер, якого попросили замінити лампочку, а він заодно переставив меблі. Нам потрібен не ентузіазм, а передбачуваність.
| Слабкий запит | Що в ньому небезпечно |
|---|---|
| «Виправ checkout, щоб усе працювало» | неясно, де межі й що вважати завершенням |
| «Реалізуй увесь план» | ви втрачаєте проміжні точки контролю |
| «Полагодь баг і приведи код до ладу» | ви самі запросили scope creep |
Якщо ви працюєте з IDE, можна ще сильніше заземлити крок: відкрити потрібний файл, виділити поточний метод, прикласти поруч шматок плану й попросити Claude працювати лише в цій області. Але навіть без IDE три обмежувачі вже радикально змінюють якість змін. Claude перестає «плисти по всьому завданню» і починає рухатися чітким коридором.
4. Прицільна перевірка після кожного кроку
Після невеликої зміни в початківця зазвичай трапляється одна з двох крайнощів. Або він узагалі нічого не перевіряє й одразу просить Claude робити наступний крок. Або запускає все, що тільки можна: повний build, усі тести й ще кілька випадкових команд «про всяк випадок». Обидві крайнощі однаково заважають думати.
Тут нам потрібен targeted check, тобто прицільна перевірка. Її завдання — не довести, що весь проєкт ідеальний, а швидко відповісти на вужче запитання: «Поточний крок сам по собі не зламався?» Це перевірка масштабу поточної дії, а не всього продукту.
git diff --stat
# 1 file changed, 4 insertions(+)
./gradlew :orders:compileJava
# BUILD SUCCESSFUL
Якщо у вас уже є підхожий наявний тест, перевірка може бути ще кращою:
./gradlew :orders:test --tests OrderControllerTest.rejectsEmptyCart
# BUILD SUCCESSFUL
Чому не потрібно після кожної такої правки ганяти весь проєкт? Тому що ви втратите ритм. Маленький крок має отримувати швидкий зворотний зв’язок. Якщо після кожної локальної правки запускати величезний прогін на кілька хвилин, цикл перестає бути керованим. Ви або почнете пропускати перевірки, або будете накопичувати зміни пачками. А це вже майже гарантований шлях до «великого страшного diff».
Але є важливе уточнення: зелений targeted check ще не означає, що крок прийнято. Він означає лише, що локальна правка бодай не розвалилася одразу. Після цього все одно потрібно читати diff. Тест не замінює вам розуміння. Тест — це сигнал, а не індульгенція.
5. Читати diff як інженер, а не як людина
На цьому етапі багато новачків бачать зелену перевірку, видихають і механічно йдуть далі. Але саме тут розташований головний фільтр якості. Перевірка може бути зеленою, а diff при цьому — дивним, таким, що розповзається, або просто не відповідним поточному кроку плану.
Щоб читати diff спокійно, корисно пам’ятати просте правило: ви не оцінюєте «красу коду взагалі». Ви відповідаєте на кілька конкретних запитань. Ось мінімальний набір таких запитань:
| Запитання до diff | Що це запитання захищає |
|---|---|
| Це все ще поточний крок плану? | захищає від розповзання scope |
| Змінені лише очікувані файли? | захищає від випадкових побічних правок |
| Немає попутного refactoring? | захищає від «корисних бонусів» Claude |
| Я розумію, навіщо додано кожен рядок? | захищає від сліпого прийняття AI-виводу |
Для прикладу візьмімо вдалий локальний diff:
@PostMapping
public OrderResponse checkout(@RequestBody CheckoutRequest request) {
+ if (request.items() == null || request.items().isEmpty()) {
+ throw new BadRequestException("Корзина не може бути порожньою");
+ }
return orderService.checkout(request);
}
Це гарний diff не тому, що він просто маленький. Він хороший тому, що його легко пов’язати з поточним кроком плану. Видно, де змінилася поведінка. Видно, що правка локальна. Видно, що вона не зачепила сусідні endpoint’и, не переписала сервіс і не влаштувала «косметичний ремонт» дорогою.
Для порівняння уявіть, що після такого ж запиту git diff --stat показує вам ось це:
git diff --stat
# 4 files changed, 31 insertions(+), 12 deletions(-)
І серед файлів раптом з’являються CheckoutService.java, OrderMapper.java і ще якийсь конфіг. Це не той момент, коли потрібно захоплюватися ініціативністю. Це момент, коли потрібно зупинитися і запитати: «Чому один маленький крок тепер виглядає як міні-рефакторинг?»
Є дуже корисний побутовий критерій: якщо ви не можете пояснити поточний diff самому собі за одну хвилину, його не можна вважати прийнятим. Не тому, що ви зобов’язані знати все напам’ять, а тому, що незрозумілий diff майже завжди погано переживає review.
6. Після кроку — рішення, а не продовження
Найнеприємніший збій в AI-assisted розробці трапляється не тоді, коли Claude помиляється. Помилки бувають у всіх. Проблема починається тоді, коли після невдалого або мутного кроку розробник вдає, що «ну майже нормально, їдемо далі». Саме так і накопичується хаос: кожен наступний запит виправляє наслідки попереднього, а не рухає завдання вперед.
Після кожного кроку у вас має бути одне з трьох рішень. Не п’ять, не «подивимося за відчуттями», а цілком конкретний вибір:
| Що ви побачили після кроку | Яке рішення приймати |
|---|---|
| diff точний, файли очікувані, перевірка зелена | прийняти результат і зафіксувати як окрему точку |
| diff загалом правильний, але потрібна невелика правка формулювання або області | уточнити поточний крок і повторити лише його |
| diff поплив, файлів стало більше, зміст правок неясний | зупинитися й не продовжувати ланцюжок змін |
Ось тут і вступають у гру stop rules, тобто стоп-правила. Це не реакція на кшталт «ну гаразд, тепер уже точно час зупинитися». Це заздалегідь сформульовані умови, за яких ви взагалі не сперечаєтесь із собою і не вмовляєте себе, що «ще один запит усе врятує».
Такий блок зручно тримати поруч із планом, наприклад у робочому NOTES.md:
## Стоп-правила сесії
- Якщо змінилися файли поза поточним кроком — зупинитися
- Якщо одна й та сама перевірка падає двічі поспіль — припинити правки
- Якщо я не можу швидко пояснити diff — не приймати зміни
- Якщо Claude пропонує «заодно» рефакторинг — повернути його в межі кроку
Зверніть увагу: тут немає нічого героїчного. Навпаки, stop rules потрібні, щоб прибрати хибний героїзм. Коли ви заздалегідь зафіксували правила зупинки, вам не доводиться щоразу торгуватися із собою. Це сильно знижує шанс звалитися в патерн «зараз я ще трохи допатчу поверх уже дивного стану».
7. Міні-прогін на Commerce OS: від плану до diff
Щоб усе це не залишилося красивою схемою в повітрі, корисно пройти один невеликий сценарій цілком. Візьмемо знайому ситуацію з Commerce OS: порожня корзина на checkout раніше призводила до помилки 500. Аналіз уже зроблено, root cause уже зрозуміла, план затверджено. Наше поточне завдання — виконати лише перший крок і на цьому зупинитися.
Припустімо, затверджений шматок плану виглядає так:
1. Додати захист від порожньої корзини в `orders/OrderController.java`.
2. Оновити сценарій перевірки в `OrderControllerTest`.
3. Прогнати перевірки модуля `orders` і перечитати diff.
Тепер формулюємо запит Claude суворо в межах першого кроку:
Реалізуй лише крок 1 із затвердженого плану.
Не переходь до кроку 2.
Змінюй лише `orders/OrderController.java`.
Якщо знадобиться інший файл, спочатку переліч його й поясни причину.
Після змін запусти компіляцію модуля `orders`
і покажи короткий diff.
Якщо все пройшло добре, підсумкова локальна правка може виглядати так:
@PostMapping
public OrderResponse checkout(@RequestBody CheckoutRequest request) {
if (request.items() == null || request.items().isEmpty()) {
throw new BadRequestException("Корзина не може бути порожньою");
}
return orderService.checkout(request);
}
Після цього ви не поспішаєте до наступного кроку. Спочатку — розмір змін і прицільна перевірка:
git diff --stat
# 1 file changed, 4 insertions(+)
./gradlew :orders:compileJava
# BUILD SUCCESSFUL
Тепер ставимо собі правильні запитання. Змінився лише контролер? Так. Неочікувані правки в сервісі або мапері? Ні. Чи пояснюється diff одним реченням? Так: «Ми перестали пропускати порожню корзину далі в checkout». Крок укладається в план — приймаємо як окрему керовану точку.
А ось якби після того самого запиту правки опинилися ще й у CheckoutService.java та OrderMapper.java, правильною реакцією було б не «цікаво, що він там покращив», а «стоп, ми вийшли за межі кроку». Зупинка — не невдача. Це ознака того, що ви керуєте процесом, а не наздоганяєте його.
У хорошому циклі на цьому місці все гранично спокійно: крок виконано, diff зрозумілий, перевірку пройдено, рішення прийнято. Такий стан не страшно нести далі — ви розумієте не лише що змінилося, а й чому це залишилося в межах плану.
8. Реалізація за метою як альтернативний режим
Те, що ми розібрали вище, — це покрокова керована реалізація: ви підтверджуєте кожен хід, читаєте diff, ухвалюєте рішення. Це режим за замовчуванням для завдань з обов’язковим review і тим більше для чутливих областей, які ми розбираємо далі в курсі. Альтернатива — реалізація за метою: той самий режим «за метою», який ми ввели на рівні 6, а на TDD детально розбираємо в наступних рівнях. У цьому режимі в стратегію перевірки кладуть машинно перевірювану умову фінішу — наприклад, «цільові tests проходять, lint зелений, diff не виходить із src/orders/» — і Claude працює кілька ходів поспіль до досягнення умови або явного блокера. Конкретна реалізація — інтерактивний запит, slash-команда на кшталт /goal, скриптований цикл claude -p — залежить від версії й може змінюватися; важлива саме ментальна модель.
Робота за метою доречна, коли готовність завдання формалізована машинно (є детермінована перевірка через tests, lint, build), коли межі за файлами обмежені й Claude може сам переконатися, що не вийшов за них, і коли завдання не належить до чутливих областей на кшталт автентифікації, платежів, міграцій або продакшн-стану. І ще — коли розробник готовий прочитати підсумковий diff постфактум і розібратися з режимами відмови циклу. Якщо хоча б одна з цих умов не виконується, краще залишитися в покроковому режимі. Робота за метою не «краща» за покрокову реалізацію — це інший режим для іншого класу завдань.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ