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 зазвичай поводиться помітно краще — майже як акуратний колега, а не як ентузіаст, якому випадково дали занадто широку свободу.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ