1. Цена фразы «сделай лучше» — лишний diff
Опасен не короткий промпт, а расплывчатый. Фраза «лучше» для агента — это почти что угодно: от точечной правки до ремонта соседней комнаты.
Задача в Commerce OS: оператор жалуется, что форма возврата заказа ведёт себя странно. Напишете «почини форму возврата» — Claude добавит проверку причины (хорошо), перепишет тексты ошибок (допустимо), заодно тронет кнопку, ответ API, стили и «подчистит» соседний код. Вроде помог, но нет.
Причина не в инициативности агента. Вы не сказали, по каким признакам работа завершена, — и он оптимизирует задачу по своему пониманию. Поэтому критерии нужны до работы: они не дают задаче расползтись и превращают «сделай лучше» в «вот что должно стать правдой после изменения».
2. Критерии приёмки на практике
Критерии приёмки — короткий список условий, которые должны выполняться после изменения. Выполняются — принимаем, нет — продолжаем. Это ваш договор с Claude и с собой: вы заранее фиксируете, что считается успехом. Не «код стал аккуратнее», а конкретное наблюдаемое условие: если причина возврата пустая, форма не отправляется и пользователь видит сообщение «Укажите причину возврата».
Критерий описывает поведение, а не реализацию: вас интересует не trim() или новый хук, а то, что увидит пользователь или система. Поэтому он понятен даже тому, кто ещё не открывал код.
Хорошо видно различие в такой таблице:
| Расплывчатая формулировка | Проверяемый критерий |
|---|---|
| «Сделай форму возврата удобнее» | «Если причина возврата пустая, форма не отправляется и показывает сообщение об ошибке под полем причины» |
| «Почини поиск заказов» | «Поиск по email не показывает дубликаты заказов, а пробелы по краям запроса игнорируются» |
| «Улучши экран тикетов» | «После применения фильтра по статусу список тикетов обновляется без сброса выбранного фильтра» |
Хороший критерий проверяется глазами, руками, логом или тестом. Плохой можно только обсудить — и всё равно поспорить. А спорить с собственным task spec — это уже редкий жанр командного саморазвлечения.
3. Признаки хорошего критерия
Сильный критерий выглядит скучновато — и это хорошо. Его задача не вдохновлять, а убирать двусмысленность. Чем меньше тумана, тем меньше шанс, что Claude и человек проверят разные задачи и оба решат, что всё прошло отлично.
У хорошего критерия есть несколько свойств, и все они очень практичные.
| Свойство | Что это значит | Пример |
|---|---|---|
| Наблюдаемость | Можно увидеть результат в UI, API, логе или поведении | «Под полем появляется текст ошибки» |
| Конкретность | Понятно, что именно должно произойти | «Отправка не выполняется», а не «валидация стала лучше» |
| Одна мысль на строку | Один критерий — одно основное условие | Отдельно про ошибку, отдельно про отправку |
| Независимость от реализации | Критерий не диктует кодовое решение | Не «использовать trim()», а «пробелы считаются пустым значением» |
| Сохранность существующего поведения | Зафиксировано, что не должно сломаться | «Успешный сценарий возврата остаётся прежним» |
Допустим, у вас в коде потом появится такая функция:
function canSendRefund(reason: string) {
return reason.trim().length > 0;
}
console.log(canSendRefund(" ")); // false
console.log(canSendRefund("Брак товара")); // true
Это вполне может быть хорошей реализацией. Но критерий приёмки звучит иначе: «если поле причины пустое или состоит только из пробелов, отправка запроса не происходит». Почему это важно? Потому что реализация может поменяться. Сегодня это trim(), завтра проверка уйдёт на сервер, послезавтра добавится отдельный валидатор. А критерий останется верным.
Ещё одна полезная привычка — не запихивать в одну строку сразу три результата. Формулировка «если причина пустая, показываем ошибку, блокируем кнопку и не вызываем API» звучит мощно, но в ней уже три разные проверки. Если задача важная, лучше разделить их. Тогда при проверке будет ясно, что именно сломалось: текст ошибки, блокировка кнопки или сетевой вызов.
4. Виды критериев — не только функциональные
Новички почти всегда пишут только один тип критериев — функциональные. То есть описывают, что должно начать работать. Это уже хороший шаг, но его недостаточно. Claude вообще умеет старательно чинить одну вещь и случайно задевать соседнюю, если вы заранее не зафиксировали, что соседнюю трогать нельзя.
На одной и той же задаче в Commerce OS можно увидеть сразу несколько типов критериев. Возьмём всё ту же форму возврата заказа.
| Вид критерия | На какой вопрос отвечает | Пример для Commerce OS |
|---|---|---|
| Функциональный | Что должно заработать | «При непустой причине возврата запрос создаётся успешно» |
| Негативный или краевой | Что не должно происходить | «Если причина пустая или состоит только из пробелов, форма не отправляется» |
| На совместимость | Что должно остаться прежним | «Формат ответа POST /api/orders/{id}/refund не меняется» |
| На вывод или UI | Что именно увидит пользователь | «Под полем причины отображается текст Укажите причину возврата» |
| Технический | Какой технический сигнал должен остаться корректным | «Существующие проверки на успешный возврат продолжают проходить» |
| Документационный | Нужно ли обновить сопровождающий текст | «Подсказка для оператора на странице возврата соответствует новому правилу» |
Здесь важен не сам факт существования шести коробочек, а привычка мыслить шире одного вопроса «что заработало?». На практике вам чаще всего достаточно мысленно пройти хотя бы через три фильтра. Первое: что должно заработать. Второе: что не должно ломаться. Третье: что именно увидит пользователь или другой сервис.
Если вы забудете критерии на совместимость, Claude может «чинить» UI так, что изменится ответ API. Если забудете негативные сценарии, то счастливо заработает позитивный путь, но форма всё ещё пропустит пустое поле. Если забудете про вывод, логика станет корректной, а пользователь так и не поймёт, почему кнопка вдруг не работает.
И нет, не каждая задача требует шести разделов на полстраницы. Если вы исправляете опечатку в кнопке, это будет перебор. Но как только затрагивается реальное поведение формы, фильтра, API или бизнес-правила, продуманные типы критериев резко снижают шанс на неприятный сюрприз.
5. Извлечение критериев из фактов задачи
Хорошие критерии почти никогда не рождаются из вдохновения. Их вытаскивают из уже собранных фактов: из описания бага, из текущего поведения, из пакета доказательств, из ограничений проекта. То есть вы не фантазируете, а почти механически превращаете факты в условия приёмки. Это успокаивает: магии становится меньше, инженерии — больше.
Предположим, по задаче про возвраты у вас уже есть такой фрагмент пакета доказательств:
## Пакет доказательств: форма возврата
- затронутые файлы: `apps/web/refunds/RefundForm.tsx`, `api/refunds/RefundController.java`
- воспроизведение: открыть заказ #521, очистить поле причины, нажать `Отправить`
- фактический результат: запрос уходит, создаётся `refund_request` со статусом `PENDING`
- желаемый результат: форма блокирует отправку и показывает понятную ошибку
- ограничение: не менять публичный контракт `POST /api/orders/{id}/refund`
Теперь из этого фрагмента можно почти автоматически достать критерии. Удобно задать себе четыре вопроса.
| Вопрос | Во что превращается ответ |
|---|---|
| Какое неправильное поведение должно исчезнуть? | «Пустая причина больше не приводит к отправке запроса» |
| Какое правильное поведение должно появиться? | «Пользователь видит сообщение об ошибке под полем причины» |
| Что обязано остаться прежним? | «Публичный контракт API не меняется» |
| Какой позитивный сценарий нельзя сломать? | «При корректной причине возврат отправляется как раньше» |
Вот это и есть важный навык: смотреть на факты и извлекать из них условия приёмки. Если на одном из вопросов вы застряли, это хороший сигнал. Значит, проблема пока не в критериях, а в том, что сама задача сформулирована мутно. И в такой момент лучше не просить Claude писать код, а сначала уточнить постановку.
В этом смысле критерии приёмки — отличный детектор качества задачи. Если вы не можете их сформулировать, то и агент, скорее всего, не сможет стабильно сделать правильное изменение.
6. Разбор формулировок — от фразы к контракту
Сейчас самый практический кусок лекции. Здесь полезно буквально переписывать мутные просьбы в нормальные критерии. Навык кажется мелким, но окупается очень быстро: у вас резко уменьшается число задач, которые «вроде сделали», потом снова открыли, а затем в третий раз объясняли, что вообще имелось в виду.
Посмотрите, как одна фраза меняется, если довести её до инженерного состояния.
| Как часто пишут в жизни | Во что это стоит превратить |
|---|---|
| «Сделай форму возврата удобнее» | «Если причина возврата пустая, заявка не отправляется. Пользователь видит сообщение Укажите причину возврата. Успешный сценарий с корректной причиной остаётся прежним» |
| «Почини поиск заказов» | «Поиск по email не показывает дубликаты заказов. Пробелы по краям запроса игнорируются. Пустой запрос возвращает исходный список заказов» |
| «Улучши экран тикетов» | «После выбора фильтра по статусу список тикетов обновляется без сброса выбранного фильтра. Сортировка по времени последнего ответа сохраняется» |
| «Разберись с ошибкой возвратов» | «Двойной клик по кнопке отправки не создаёт две заявки на возврат. Пользователь получает один понятный результат отправки» |
Особенно важно не подменять критерии техническим решением. Например, формулировка «добавь debounce в поиск заказов» — это не критерий приёмки. Это предположение о способе реализации. Может быть, debounce действительно нужен. А может, проблема вообще в серверной пагинации или повторной склейке ответа на фронтенде. Критерий должен звучать про результат: «при поиске по email список не содержит дубликатов».
Ещё одна типичная ловушка — писать слишком общо даже после попытки уточнить. Например: «Показывать понятную ошибку». Понятную кому? Где? Когда? Лучше чуть сухо, но однозначно: «под полем причины отображается текст Укажите причину возврата». Да, это звучит менее поэтично. Зато проверяется за три секунды.
Хороший способ самопроверки очень простой. Прочитайте критерий вслух и представьте, что его получил другой разработчик, не сидевший с вами в комнате. Если он может без дополнительных вопросов сказать, что именно нужно увидеть после изменения, формулировка уже близка к рабочей.
7. Критерии в TASK_SPEC.md без слепой веры
Когда критерии у вас уже в голове, их нужно положить в тот же TASK_SPEC.md, где уже есть goal, scope, constraints и краткая выжимка из пакета доказательств. Полный EVIDENCE_LOG.md при этом можно оставить отдельным вспомогательным артефактом: в TASK_SPEC.md обычно хватает ссылки или короткой выжимки. Иначе через час вы сами начнёте помнить задачу чуть иначе. TASK_SPEC.md здесь работает как страховка и от человеческой памяти, и от излишней инициативности Claude.
Ниже — короткий пример того, как может выглядеть раздел с критериями приёмки в вашем TASK_SPEC.md:
## Критерии приёмки
### Функциональные
- При непустой причине возврата и сумме до 100 USD заявка создаётся успешно.
### Негативные сценарии
- Если причина пустая или состоит только из пробелов, отправка не происходит.
### Совместимость
- Формат ответа `POST /api/orders/{id}/refund` не меняется.
### UI
- Под полем причины отображается сообщение `Укажите причину возврата`.
Такой блок уже можно дать Claude как часть полноценной постановки задачи. И Claude действительно хорошо помогает с черновиком критериев, если вы просите его не «сделать задачу», а сначала помочь сформулировать условия приёмки. Например, так:
На основе описания задачи и пакета доказательств предложи критерии приёмки.
Раздели их на функциональные, негативные сценарии, совместимость, UI и технические.
Не предлагай реализацию.
Если данных не хватает, сначала задай уточняющие вопросы.
Обратите внимание на последнюю строчку. Она очень важна. Вы не просите Claude немедленно кодить. Вы просите его сначала выступить аккуратным помощником по формулировке контракта. Это хороший режим работы: Claude может напомнить про совместимость, про негативные сценарии, про забытый пользовательский текст. Но утверждаете критерии всё равно вы.
Иногда Claude предложит слишком техническую формулировку. Иногда забудет критерий на сохранение существующего поведения. Иногда, наоборот, притащит пять лишних пунктов «на всякий случай». И это нормально. Его задача здесь не заменить вашу приёмку, а ускорить черновик. Финальная версия должна пройти через ваше инженерное чувство реальности.
Если в конце вы можете открыть TASK_SPEC.md, прочитать раздел критериев и без кода понять, что именно должно стать правдой, что не должно сломаться и где проходит граница изменений, значит спецификация уже начинает работать как настоящий инженерный контракт. А с таким контрактом Claude обычно ведёт себя заметно лучше — почти как аккуратный коллега, а не как энтузиаст, которому случайно дали слишком широкую свободу.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ