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, решаете. Это режим по умолчанию для задач с обязательным ревью и тем более для чувствительных областей, которые мы разбираем дальше в курсе. Альтернатива — реализация по цели: тот же режим «по цели», который мы ввели в уровне 6, а на TDD подробно разбираем в следующих уровнях. В этом режиме в стратегию проверки кладут машинно-проверяемое условие финиша — например, «целевые tests проходят, lint зелёный, diff не выходит из src/orders/» — и Claude работает несколько ходов подряд до достижения условия или явного блокера. Конкретная реализация — интерактивный запрос, slash-команда вроде /goal, скриптованный цикл claude -p — зависит от версии и может меняться; важна именно ментальная модель.
Работа по цели уместна, когда готовность задачи формализована машинно (есть детерминированная проверка через tests, lint, build), когда границы по файлам ограничены и Claude может сам убедиться, что не вышел за них, и когда задача не относится к чувствительным областям вроде аутентификации, платежей, миграций или продакшн-состояния. И ещё — когда разработчик готов прочитать итоговый diff постфактум и разобраться с режимами отказа цикла. Если хотя бы одно из этих условий не выполняется, лучше остаться в пошаговом режиме. Работа по цели не «лучше» пошаговой реализации — это другой режим для другого класса задач.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ