explainer · methodology

Спецификация - это не PRD, это контрольный вкус: чем намерение отличается от поведения и почему разница огромна на практике

Рецепт может звучать вкусно и быть невыполнимым. Финальный вкус блюда проверяется только едой. PRD - это рецепт. Спецификация - это контрольный вкус: проверяемое поведение системы, дельта-формат изменений как у сообщений коммитов, граф зависимостей между смежными спецификациями.

Симптом: «по документу всё сделано, а пользователь говорит - не работает»

Разработчик заходит в обсуждение с менеджером проекта и говорит: «функция готова, по PRD всё сделано». Менеджер кивает, открывает приложение, пробует - и через минуту хмурится: «но это не то, что мы хотели».

Кто прав?

С одной стороны, разработчик. Он сделал ровно то, что было написано в PRD. Если бы кто-то проверил каждую строку требований - каждая выполнена.

С другой, менеджер. Финальный продукт ощущается иначе, чем должен. Где-то между «требование» и «реализация» произошёл сдвиг, и обе стороны его не заметили, потому что обе смотрели на разные документы.

Эту ситуацию я видел в десятке команд. Она почти всегда возникает по одной причине: PRD говорит, что мы хотим, а не как система должна себя вести. Эти две вещи звучат похоже, но различие огромно. Хочу разобрать его на примерах, показать формат спецификации, который закрывает разрыв, и объяснить, почему граф зависимостей между спецификациями работает похоже на сообщения коммитов в репозитории.

Намерение и поведение

PRD говорит на языке намерений. «Добавить теги к заметкам». «Сделать поиск быстрее». «Дать пользователю возможность отменить действие». Это полезно как ориентир - все в команде понимают цель.

Но «добавить теги к заметкам» - это не описание поведения системы. Это описание того, что у пользователя должно появиться. Как именно теги добавляются, что происходит при разных входах, где граница нормального поведения и ошибки - PRD не отвечает.

Спецификация - отвечает.

Сравните две формулировки. PRD: «Добавить теги к заметкам». Спецификация: «Конечная точка POST /notes/:id/tags принимает массив строк длиной от 1 до 10 элементов, каждая строка от 1 до 32 символов. Возвращает 200 и обновлённый массив тегов заметки. Пустой массив возвращает 400 с сообщением “tags array cannot be empty”. Если хотя бы одна строка длиннее 32 символов - 400 с сообщением “tag exceeds max length 32”. Дубликаты в массиве молча удаляются».

PRD - три слова. Спецификация - три абзаца. Но именно эти три абзаца отделяют «разработчик угадал, что хотел менеджер» от «разработчик имеет контракт».

Разработчик с PRD-only делает реализацию, опираясь на свой здравый смысл. Он может допустить пустой массив, считая, что это «значит удалить все теги». Или может разрешить любую длину строки, забыв, что в базе колонка ограничена 64 символами. Или может не подумать о дубликатах. Каждое из этих решений потом всплывёт как «но это не то, что мы хотели».

Со спецификацией - таких сюрпризов нет. Если она написана до кода, разработчик не угадывает - он реализует. Если написана после - она становится тестом: реализация либо проходит по контракту, либо не проходит.

Раздел «что не входит» обязателен

Самая важная секция в спецификации - та, которой почти никогда нет в PRD. Это явное описание что не входит в эту спецификацию.

Пример. Спецификация конечной точки POST /notes/:id/tags явно говорит:

  • Не входит: переименование существующих тегов (отдельная спецификация для PATCH-операции).
  • Не входит: удаление тега у всех заметок одновременно (отдельная спецификация для административной операции).
  • Не входит: поиск заметок по тегу (отдельная спецификация для GET-операции на коллекции).
  • Не входит: цвета и иерархия тегов (не поддерживаются в текущей версии).

Эти четыре строки делают две вещи. Первая - фиксируют рамки: разработчик знает, что код должен решать ровно эту задачу и не больше. Вторая - указывают на смежные спецификации, которые вместе образуют полную систему работы с тегами.

Без раздела «что не входит» спецификация склонна к разрастанию. Через две недели в неё дописывают «а ещё надо переименовать», через месяц - «а ещё удалить тег у всех». Через квартал - это одна гигантская спецификация на 40 страниц, которую никто не читает. С разделом «не входит» каждая спецификация остаётся ответственной за одну границу поведения, а соседние спецификации существуют отдельно и связаны через граф.

Дельта-формат для требований

Когда спецификация меняется, изменение можно записать двумя способами.

Первый - переписать спецификацию заново. Удалить старую версию, написать новую. Так делают команды, которые относятся к требованиям как к свободному тексту в вики.

Второй - записать изменение как дельту: что добавилось, что изменилось (с разделением «было» / «стало»), что убрано (и по какой причине). Так делают команды, которые относятся к требованиям как к коду в репозитории.

Дельта-формат заимствован у git diff. Изменение спецификации описывается как набор операций:

  • ADDED - что появилось. Новое требование, новая конечная точка, новое ограничение.
  • MODIFIED - что изменилось. С разделением: «было такое поведение, стало такое». Без разделения непонятно, что именно сдвинулось.
  • REMOVED - что убрано. Обязательно с причиной: «убрано требование к минимальной длине тега, потому что появилось требование поддержки эмодзи-тегов».

Этот формат даёт две вещи. Первая - историю требований, которую можно прочитать так же, как читают сообщения коммитов в репозитории кода. «Когда мы добавили это ограничение? Зачем?». Открываешь дельту, видишь дату и причину. Вторая - возможность для смежных команд узнать о значимом изменении, не перечитывая всю спецификацию заново.

В команде, которая использует дельта-формат, изменение спецификации становится таким же дисциплинированным актом, как изменение публичного API: ты не можешь просто переписать, ты должен объяснить, что и зачем.

Граф зависимостей между спецификациями

Спецификации редко существуют изолированно. Спецификация конечной точки POST /notes/:id/tags зависит от спецификации схемы базы данных заметок (потому что теги хранятся там) и от спецификации общих ошибок API (потому что 400-ответы должны быть в общем формате). От неё, в свою очередь, зависит спецификация поиска заметок по тегу (она использует ту же модель данных) и спецификация миграции старых заметок (она должна корректно обработать заметки без тегов).

Если у вас десять спецификаций - у них десятки таких связей. Если у вас сто - тысячи.

Без графа зависимостей изменение одной спецификации запускает невидимую цепную реакцию. Сегодня меняют схему хранения тегов - а через две недели в спецификации поиска обнаруживается, что она опирается на старую схему. Ещё через месяц в спецификации миграции - то же. Команда ловит эти расхождения по мере того, как они вылазят в продукте.

С графом зависимостей - изменение спецификации автоматически открывает связанные на разбор. Сменили схему - система говорит «нужно проверить спецификации поиска и миграции, они зависели от старой схемы». Команда смотрит за 15 минут, видит, что миграция работает, а поиск - нет. Открывают спецификацию поиска, обновляют, фиксируют дельтой. Через два часа все три спецификации согласованы.

Граф - это не «ещё один документ для поддержки». Это автоматическая защита от рассогласования, без которой система спецификаций превращается в десять параллельных правд.

R_eff внутри спецификации

В предыдущем посте серии я разбирал формулу R_eff - оценку надёжности решения через самое слабое звено доказательств с учётом уровня контекстного соответствия. Та же логика применяется внутри спецификации.

Каждое требование в спецификации опирается на доказательство. «Поиск по тегу за 200 мс» опирается либо на свой замер (CL3), либо на бенчмарк коллеги (CL2), либо на документацию базы данных (CL1), либо на интуицию автора (CL0).

Спецификация, в которой требования опираются только на интуицию, имеет R_eff близкий к нулю - её можно публиковать как контракт, но реализация почти наверняка не выдержит требования. Спецификация со свежими замерами на собственной нагрузке имеет R_eff под единицу - её можно использовать как обещание.

Разница важна на этапе утверждения спецификации. Если R_eff низкий - спецификацию рано утверждать. Сначала надо собрать доказательства, поднять R_eff до приемлемого уровня, потом утверждать. Это защита от ситуации «утвердили спецификацию, начали реализовывать, через две недели поняли, что требование невыполнимо». Лучше две недели потратить на замеры заранее, чем две недели - на переделку реализации.

Конкретный пример: спецификация тегов для заметок

Расскажу один кейс, изменю детали.

Команда Vault, та самая, что в предыдущем посте проектировала функцию тегов через четыре фазы BMAD. Документ-контракт получился - PRD на 13 разделов. Дальше команда садится писать спецификацию.

Назову её notes-tagging-spec. Четыре раздела:

  • Поведение: четыре конечные точки (POST для добавления, PATCH для переименования, DELETE для удаления, GET для поиска), каждая с детальным описанием входов, выходов и ошибок.
  • Инварианты: тег уникален в пределах пользователя; имя тега хранится с учётом регистра, но сравнивается без учёта; максимум 100 тегов на заметку, 10 000 уникальных тегов на пользователя.
  • Триггеры разбора: спецификация должна быть пересмотрена, если максимум тегов на заметку достигает 80 у более чем 5% пользователей; или если время поиска по тегу превышает 200 мс в 95-м процентиле.
  • Что не входит: иерархия тегов, цвета, синхронизация между устройствами, поделиться тегом с другим пользователем.

Спецификация - две страницы. Через неё разработчик знает, что писать. Менеджер проекта знает, что принимать. Через шесть месяцев, когда нагрузка растёт, триггеры разбора срабатывают сами - система говорит «триггер 5% превышен, нужен разбор спецификации». Команда садится, смотрит - да, действительно пора добавлять индексы. Обновляют дельтой.

Это пример того, как спецификация работает живым контрактом, а не «документом, который написали и забыли».

Что отдать AI-агенту, что - себе

AI-агент хорошо генерирует первую версию спецификации из PRD + примеров. Дать ему PRD-документ функции тегов плюс одну существующую спецификацию похожей конечной точки - за минуту он сделает черновик с четырьмя разделами и примерами входов-выходов.

Финальные две вещи стоят за человеком.

Раздел «что не входит». Агент не знает, какие соседние спецификации существуют в вашем проекте и что осознанно вынесено в отдельные документы. Без этого знания он либо забывает раздел «что не входит», либо пишет в нём очевидные вещи («не входит: автоматическая категоризация на основе ИИ»). Полезный список «не входит» - это всегда вынос конкретных связанных спецификаций.

Триггеры разбора. Агент не знает, какие метрики у вас уже выставлены под наблюдение и какие пороги осмысленны для вашего бизнеса. Он напишет «пересмотреть, если время поиска превышает 100 мс», а у вас может быть допустимо 500. Триггеры - это всегда контекст команды.

Базовое правило: агент - структура и черновик, человек - границы и пороги. Если AI заполняет «не входит» и «триггеры разбора» на 100% - у вас не спецификация, у вас красивый шаблон с двумя дырами.

Что почитать дальше

Это шестой пост в серии. Предыдущие:

Дальше:

  • Что такое Forgeplan и как он держит четыре методологии в одной плоскости.
  • Полный цикл на одной задаче - от строки в чате до записи решения с условием пересмотра.

К этому посту прилагается интерактивный разбор: график влияния изменений спецификации на смежные документы. Лежит в /guides.