Когда спека работает — и когда ломается

Предыдущий урок дал инструменты: requirements.md с нотацией EARS, design.md с архитектурными решениями, tasks.md с трассировкой. Теперь посмотрим, как эти инструменты работают на практике — и где ломаются.

Разберём четыре кейса: один удачный и три типичных провала.

flowchart TD A[Спека] --> B[Рабочая: EARS + AC + edge-кейсы] A --> C[Пустая: нет деталей] A --> D[Противоречивая: конфликты] A --> E[Псевдокод: реализация вместо поведения] style B fill:#d4edda,stroke:#28a745 style C fill:#f8d7da,stroke:#dc3545 style D fill:#f8d7da,stroke:#dc3545 style E fill:#f8d7da,stroke:#dc3545
flowchart TD
    A[Спека] --> B[Рабочая: EARS + AC + edge-кейсы]
    A --> C[Пустая: нет деталей]
    A --> D[Противоречивая: конфликты]
    A --> E[Псевдокод: реализация вместо поведения]
    style B fill:#d4edda,stroke:#28a745
    style C fill:#f8d7da,stroke:#dc3545
    style D fill:#f8d7da,stroke:#dc3545
    style E fill:#f8d7da,stroke:#dc3545
Четыре варианта спеки: один рабочий, три нерабочих

Случай 1 (правильно): поиск по каталогу

Команда добавляет поиск в интернет-магазин. Вот фрагмент requirements.md:

## FR-03 Поиск по каталогу

### User story
Как покупатель, я хочу вводить запрос в строку поиска и получать
релевантные товары, чтобы быстро находить нужное.

### Requirements

1. WHEN пользователь вводит запрос длиной >= 2 символов
   THE SYSTEM SHALL вернуть до 20 результатов, отсортированных по релевантности,
   за время не более 300 мс (p95).

2. WHEN запрос содержит менее 2 символов
   THE SYSTEM SHALL не отправлять запрос к бэкенду и показать подсказку.

3. IF поиск не вернул результатов
   THEN THE SYSTEM SHALL показать экран «Ничего не найдено».

4. WHILE выполняется запрос
   THE SYSTEM SHALL показывать skeleton-индикатор вместо списка товаров.

### Acceptance criteria
- [ ] Запрос < 2 символов -> нет вызова API, есть подсказка пользователю
- [ ] Запрос >= 2 символов -> список до 20 товаров, p95 <= 300 мс
- [ ] Нет результатов -> экран «Ничего не найдено»
- [ ] Skeleton виден во время ожидания

Агент получает ровно то, что нужно: четыре различимых поведения, каждое с конкретным триггером. Ни одного вопроса «что делать при пустой строке?» — ответ в требовании 2. Каждый acceptance criterion станет тестом: агент пишет не «что-то похожее на поиск», а реализацию четырёх конкретных сценариев.

Check yourself
Взгляните на acceptance criterion «Запрос < 2 символов → нет вызова API, есть подсказка». Как именно выглядел бы тест, который агент генерирует из этого критерия?

Случай 2 (неправильно): пустая спека

Та же задача, написанная «на ходу»:


## Поиск

Нужно добавить поиск по товарам. Быстро и удобно.

Агент начинает угадывать. Минимальная длина запроса? Неизвестно. Порядок сортировки? Неизвестно. Что показывать при пустом результате? Неизвестно. Первый PR вызовет десять вопросов от ревьюера — и все они должны были войти в спеку до старта.

Пустая спека — это не спека. Это промпт в формате Markdown.

Check yourself
Чем «нужно добавить поиск. Быстро и удобно» отличается от спеки из случая 1 с точки зрения агента?

Случай 3 (неправильно): противоречивая спека

Спека написана подробно, но внутри живёт тихое противоречие:

### Requirements

1. WHEN пользователь вводит запрос >= 2 символов
   THE SYSTEM SHALL искать по полю name товара.

2. WHEN пользователь вводит запрос >= 3 символов
   THE SYSTEM SHALL искать по полям name и description.

Что делать агенту при запросе ровно из двух символов? Требование 1 говорит «ищи по name», требование 2 молчит. Агент разрешит конфликт — но не обязательно так, как вы ожидали.

Противоречия чаще всего появляются, когда спеку писали в несколько итераций без единой проверки на консистентность. Простое правило: перед стартом реализации прочитать спеку за один раз, как связный текст, а не как список пунктов.

Quick recall
В Случае 3 требование 1 говорит 'ищи по name при >= 2 сим', требование 2 говорит 'ищи по name и description при >= 3 сим'. Какой конфликт возникает?

Случай 4 (неправильно): спека-псевдокод

Самый коварный анти-паттерн. Выглядит как тщательная работа — на деле убивает разделение «что / как»:

### Requirements

1. WHEN пользователь вводит запрос
   THE SYSTEM SHALL вызвать searchProducts(query: string),
   сделать GET /api/v1/products/search?q={query},
   десериализовать ответ в Product[] и записать в useState-хук.

Это не требование — черновик реализации. Если завтра вы перейдёте с REST на GraphQL или с useState на Zustand, спеку придётся переписывать. Она устарела раньше, чем написан первый тест.

Рабочая спека описывает поведение. Как его реализовать — задача design.md и агента.

Check yourself
Команда написала спеку из случая 4 и реализовала фичу. Потом решила заменить REST на GraphQL. Что происходит со спекой?

Что отличает рабочую спеку от нерабочей

ПризнакРабочая спекаНерабочая спека
ЯзыкWHEN / IF / WHILE + поведение«нужно», «быстро», «удобно»
ПолнотаВсе edge-кейсы явныАгент угадывает
КонсистентностьНет пересеченийТихие конфликты
Уровень описанияЧТО (поведение)КАК (реализация)
Acceptance criteriaКонкретны и проверяемыРасплывчаты или отсутствуют

Проблема не в объёме. Пустая спека плохо, но спека-псевдокод тоже плохо. Хорошая спека содержит ровно столько информации, сколько нужно для однозначного понимания поведения, — и не больше.

В следующем уроке разберём анти-паттерны на уровне процесса: spec rot, over-specification и когда SDD вообще не стоит применять.