Требования и критерии приёмки
Из чего состоит функциональная спецификация — контекст, пользовательские истории, требования с идентификаторами, сценарии «Дано / Когда / Тогда», нефункциональные требования, границы и открытые вопросы; шаблоны EARS, признаки плохого требования, маркеры неясности и цикл уточнений с агентом — на примере записи на потоки онлайн-школы.
Зачем это нужно
Заголовок раздела «Зачем это нужно»В уроке SDD.1.2 мы видели: чего нет в тексте, то агент додумает. Самая важная часть спецификации — требования и критерии приёмки: первые агент реализует, по вторым проверяется результат. Плохое требование агент выполнит «как-нибудь», а без критерия приёмки вы не узнаете, как именно.
Требования писали задолго до ИИ, и у этого ремесла есть проверенные приёмы: идентификаторы, шаблоны фраз, сценарии с конкретными данными, стоп-список размытых слов. В этом уроке мы соберём их в формат, которым будем пользоваться всю программу, разберём признаки плохих требований, научимся вести цикл уточнений с агентом и напишем линтер, который сам находит проблемы в спецификации. Пример — запись на потоки онлайн-школы, правила которой вы знаете по уроку DDD.1.2.
Из чего состоит функциональная спецификация
Заголовок раздела «Из чего состоит функциональная спецификация»Функциональная спецификация отвечает на вопросы «что» и «зачем» и ничего не говорит о «как». OpenSpec формулирует это так: спецификация — контракт поведения, а не план реализации; если реализацию можно поменять, не меняя видимого снаружи поведения, этой детали в спецификации не место.
В курсе у файла spec.md шесть разделов. Тот же формат проверяет домашняя работа sdd-hw01-spec, которую выдаёт
урок SDD.2.3:
| Раздел | Что в нём | Пример для записи на поток |
|---|---|---|
| Контекст | цель, пользователи, ссылка на глоссарий, важные определения | студент и менеджер набора; «действующая запись» — со статусом «ожидает оплаты» или «подтверждена», только она занимает место |
| Пользовательские истории | кто и зачем, с приоритетом | «Как студент, я хочу записаться на поток, чтобы занять место до старта» |
| Требования | проверяемые утверждения с ID REQ-001: функциональные и нефункциональные | «Если студент записывается в момент старта потока или позже, то система должна отказать…» |
| Сценарии | критерии приёмки «Дано / Когда / Тогда» с ID SC-001 и ссылкой на требования | Глеб записывается 2 ноября в 10:00 — отказ EnrollmentClosed |
| Вне рамок | что сознательно не делаем | оплата, лист ожидания, уведомления |
| Открытые вопросы | неясности с маркером [УТОЧНИТЬ] и принятые решения со ссылкой | поток перенесли на другую дату — что с правилом 7 суток? |
flowchart LR G["Цель<br/>и пользователи"] --> U["Истории<br/>US-1, US-2"] U --> R["Требования<br/>REQ-001…"] R --> S["Сценарии<br/>SC-001…"] S --> T["Приёмочные тесты<br/>test_sc_001_…"] T --> C["Код"] R -.->|"задачи ссылаются<br/>на требования"| K["Задачи<br/>T-001…"] K -.-> C
Идентификаторы — не формальность. По ним сценарий ссылается на требование, задача — на требования, тест — на сценарий
(test_sc_005_...), а вы — разговариваете с агентом: «REQ-004 нарушен, перечитай его». Эта цепочка называется
трассируемостью; к ней мы вернёмся в уроках о задачах и о проверке.
Контекст и пользовательские истории
Заголовок раздела «Контекст и пользовательские истории»Контекст — несколько предложений о цели и пользователях плюс определения, без которых требования двусмысленны. Слова — из глоссария домена; определение «действующей записи» лучше дать один раз здесь, чем повторять в каждом требовании.
Пользовательская историяпользовательская историяEN user storyКороткое описание потребности от лица пользователя: «Как <роль>, я хочу <действие>, чтобы <ценность>». Отвечает на вопросы «кто» и «зачем»; детали — в требованиях и сценариях. отвечает на вопросы «кто» и «зачем» в формате «Как роль, я хочу действие, чтобы ценность»:
- US-1 (P1): Как студент, я хочу записаться на поток, чтобы занять место до старта.
- US-2 (P1): Как менеджер набора, я хочу, чтобы неоплаченные записи освобождали места, чтобы поток заполнялся теми, кто готов платить.
- US-3 (P2): Как студент, я хочу отменить запись и понимать, сколько денег мне вернут.
Приоритеты P1, P2 — приём из шаблона Spec Kit: каждая история должна давать ценность сама по себе, и агент может реализовывать их по очереди. Историю не проверяют напрямую — проверяют требования и сценарии, которые из неё выросли.
Требования
Заголовок раздела «Требования»Одно требование — одно проверяемое обязательство
Заголовок раздела «Одно требование — одно проверяемое обязательство»ТребованиетребованиеEN requirementОдно проверяемое утверждение о том, что система должна делать (функциональное) или каким свойством обладать (нефункциональное). В спецификации у требования есть идентификатор, например REQ-001. — одно проверяемое утверждение о поведении системы. В курсе требование записывается одной строкой списка в разделе «Требования», с ID и словом «должна»:
## Требования
### Функциональные- **REQ-001**: Когда студент записывается на поток со свободными местами до старта потока, система должна создать запись на поток со статусом «ожидает оплаты».- **REQ-002**: Если число действующих записей равно вместимости потока, то система должна отказать в записи с ошибкой NoFreeSeats.
### Нефункциональные- **REQ-009**: Система должна хранить суммы денег целым числом копеек вместе с валютой.Слово «должна» — русский аналог MUST и SHALL из RFC 2119, на который опираются OpenSpec («The system SHALL…») и Spec Kit («System MUST…»). «Следует» (SHOULD) и «может» (MAY) в требованиях лучше не использовать: «система следует отправить письмо» нельзя ни выполнить наполовину, ни проверить.
Шаблоны EARS
Заголовок раздела «Шаблоны EARS»В 2009 году Алистер Мавин и соавторы из Rolls-Royce опубликовали на конференции по инженерии требований (IEEE RE’09) статью «Easy Approach to Requirements Syntax» — EARSEARS (шаблоны формулировок требований)EN Easy Approach to Requirements SyntaxНесколько шаблонов предложения для требования: «Система должна …», «Когда <событие>, система должна …», «Пока <состояние>, …», «Если <нежелательное событие>, то …». Предложены Алистером Мавином и соавторами (Rolls-Royce, 2009).. Они разбирали требования к системам управления авиадвигателями и заметили, что почти любое хорошее требование укладывается в несколько шаблонов предложения: условие или триггер, затем система, затем её реакция. Шаблон заставляет назвать, когда правило срабатывает, — то есть ровно то, что агент иначе додумает.
| Шаблон | Ключевое слово | Формулировка | Пример для школы |
|---|---|---|---|
| Повсеместное | — | Система должна … | Система должна хранить суммы денег целым числом копеек вместе с валютой. |
| По событию | When | Когда событие, система должна … | Когда студент отменяет запись за 7 суток или более до старта потока, система должна вернуть ему полную стоимость. |
| По состоянию | While | Пока состояние, система должна … | Пока студенту из листа ожидания предложено место, система должна держать это место закреплённым за ним. |
| Нежелательное поведение | If … then | Если условие, то система должна … | Если свободных мест на потоке нет, то система должна отказать в записи с ошибкой NoFreeSeats. |
| Опциональная функция | Where | При наличии функции система должна … | При включённом листе ожидания система должна предлагать освободившееся место первому студенту в листе ожидания. |
| Комплексное | сочетание | Пока …, когда …, система должна … | Пока поток не стартовал, когда запись на поток отменяется, система должна опубликовать событие МестоОсвободилось (SeatReleased). |
EARS используют и в инструментах SDD. Например, в Kiro, по описанию в его документации, требования спецификации
записываются как истории с критериями приёмки в нотации EARS («WHEN … THE SYSTEM SHALL …»); открытый проект cc-sdd,
совместимый со спецификациями Kiro, прямо пишет в README, что его requirements.md — «требования в формате EARS
с критериями приёмки». Проверяйте актуальное состояние по документации: инструменты меняются быстро.
Учтите совпадение слов: «Когда» в EARS — триггер в формулировке правила, а «Когда» в Gherkin — действие в примере. Требование — общее правило, сценарий — конкретный случай с датами и именами.
Нефункциональные требования
Заголовок раздела «Нефункциональные требования»Нефункциональные требованиянефункциональное требованиеEN non-functional requirementТребование к свойствам системы, а не к её поведению: время ответа, доступность, безопасность, формат хранения данных. Записывается с измеримым порогом: не «быстро», а «95% запросов за 300 мс». описывают свойства системы: время ответа, доступность, безопасность, формат хранения данных. Главное правило — измеримый порог. Числа ниже условные:
| Размыто | Измеримо |
|---|---|
| система должна работать быстро | система должна отвечать на запрос записи на поток не дольше 500 мс для 95% запросов при нагрузке до 20 запросов в секунду |
| деньги должны считаться надёжно | система должна хранить суммы денег целым числом копеек вместе с валютой |
| запись должна быть удобной | от страницы курса до созданной записи — не больше двух действий студента |
Требования к устройству кода — «время в модели только параметром now», «без новых зависимостей», «сначала тест» —
это не поведение системы, а правила разработки. Им место в плане и в конституции проекта (уроки
SDD.2.2 и SDD.2.3) или в файле памяти агента.
Вне рамок
Заголовок раздела «Вне рамок»Раздел «Вне рамок»вне рамок (что не делаем)EN out of scope, non-goalsРаздел спецификации с тем, что сознательно не входит в задачу. Защищает от «полезных» дополнений агента и от споров на приёмке. кажется лишним, пока не поработаешь с агентом. Агенты любят быть полезными: попросите запись на поток — и получите заодно отправку писем, лист ожидания и скидки. Каждое такое дополнение — непроверенный код и решения о продукте, которых никто не принимал. Явный список «что не делаем» снимает это и заодно споры на приёмке. Хорошая запись говорит не только «нет», но и «где»: «письма — контекст «Уведомления»», «лист ожидания — отдельная функция».
Критерии приёмки: сценарии «Дано / Когда / Тогда»
Заголовок раздела «Критерии приёмки: сценарии «Дано / Когда / Тогда»»Критерий приёмкикритерий приёмкиEN acceptance criterionПроверяемое условие, при котором требование или история считаются выполненными. Удобная форма — сценарий «Дано / Когда / Тогда» с конкретными данными: из него прямо пишется приёмочный тест. — проверяемое условие, при котором требование считается
выполненным. Лучшая его форма — сценарий «Дано / Когда / Тогда»сценарий «Дано / Когда / Тогда»EN Given / When / Then (Gherkin)Структура примера поведения: исходное состояние (Дано), действие или событие (Когда), ожидаемый результат (Тогда). Пришла из BDD; язык Gherkin инструмента Cucumber понимает и русские ключевые слова: Сценарий, Дано, Когда, Тогда, И, Но.: исходное состояние,
действие или событие, наблюдаемый результат. Форма пришла из разработка через поведениеразработка через поведение (BDD)EN Behavior-Driven DevelopmentПрактика, которую предложил Дэн Норт в середине 2000-х: описывать поведение системы примерами «Дано / Когда / Тогда» на языке бизнеса и превращать их в автоматические тесты (инструмент Cucumber, язык Gherkin). (Дэн Норт) и стала языком Gherkin
инструмента Cucumber. Gherkin понимает русские ключевые слова — это видно в его словаре gherkin-languages.json:
# language: ruФункция: Запись на поток
Сценарий: Отмена ровно за 7 суток до старта — полный возврат Дано поток «Python для аналитиков» со стартом 2 ноября 2026 года в 10:00 и ценой 30 000 руб. И у Анны запись на поток со статусом «подтверждена» Когда Анна отменяет запись 26 октября 2026 года в 10:00 Тогда Анне возвращается 30 000 руб. И запись Анны получает статус «отменена»В спецификации курса мы пишем сценарии в markdown — так их читают и люди, и агент, и наш линтер. Каждый сценарий —
заголовок ### SC-…, строка «Требования:» со ссылками и шаги списком:
## Сценарии
### SC-006: Отмена ровно за 7 суток — полный возвратТребования: REQ-006, REQ-009- **Дано** у Анны запись на поток со статусом «подтверждена»- **Когда** Анна отменяет запись 26 октября в 10:00- **Тогда** Анне возвращается 3 000 000 копеек в валюте RUBЧто отличает хороший сценарий:
- Конкретные данные. Имена, даты, суммы, вместимость. «Студент отменяет запись заранее» — не сценарий.
- Одно поведение. Если в сценарии два «Когда», это два сценария.
- Границы. Для правила с числом нужен случай ровно на границе и случай сразу за ней: отмена в 10:00 (ровно
7 суток — возврат) и в 10:01 (уже нет). Именно на границе агент чаще всего пишет
>вместо>=. - Отрицательные случаи. Мест нет, запись закрыта, повторная запись — ошибки тоже поведение.
- Наблюдаемый результат в «Тогда». Статус, сумма, ошибка, событие — то, что можно проверить снаружи, а не «вызван
метод
_recalc». - ID в имени теста. Сценарий SC-006 станет тестом
test_sc_006_full_refund_exactly_7_days; так по отчёту pytest видно, какое правило нарушено (подробно — в уроке SDD.4.2).
Шаблон Spec Kit тоже содержит сценарии Given / When / Then внутри каждой истории. Не путайте нумерацию: в Spec Kit
функциональные требования — FR-001, а SC-001 — измеримые критерии успеха (Success Criteria). В нашем курсе
требования — REQ-001, а SC-001 — сценарии.
Признаки плохого требования
Заголовок раздела «Признаки плохого требования»| Признак | Пример | Чем плохо | Как исправить |
|---|---|---|---|
| размытые слова | «Система должна быстро и удобно записывать студентов» | проверить нельзя; агент выберет «быстро» сам | измеримый порог или отдельное поведение |
| составное | «…должна отменить запись, вернуть деньги и уведомить студента» | выполнено наполовину — это выполнено? сценарий проверит только часть | разбить на REQ по одному обязательству |
| решение вместо проблемы | «Система должна хранить записи в таблице enrollments в PostgreSQL» | это «как», оно связывает руки плану и не говорит, зачем | «Когда запись отменяется, система должна сохранить её со статусом «отменена», не удаляя» |
| нет числа или границы | «Неоплаченные записи должны отменяться через некоторое время» | агент подставит любое «разумное» число | «…48 часов с момента записи» и сценарий ровно на 48-м часу |
| чужие слова | «Если группа заполнена, заказ отклоняется» | синонимы вместо глоссария — агент назовёт классы Group и Order | слова глоссария: поток, запись на поток |
| запрет без поведения | «Система не должна допускать ошибок при оплате» | непонятно, что делать, чтобы выполнить | что именно происходит при ошибке оплаты |
Для первого признака удобен стоп-список. В курсе в нём: «быстро», «удобно», «интуитивно», «оптимально», «эффективно», «гибко», «своевременно» и «вовремя», «современный», «надёжно», а также выражения «по возможности», «при необходимости», «в разумные сроки», «как правило», «и т. д.», «и др.». Каждое из них — место, где агент примет решение за вас. Стоп-список домашней работы sdd-hw01-spec задан в её README.
Составные требования ловятся союзами «и», «или», «и/или» после слова «должна» и двумя «должна» в одной фразе. Это эвристика: «должна сохранить запись и её историю» — скорее одно обязательство. Линтер подсвечивает кандидатов, решает человек.
Неясности и цикл уточнений с агентом
Заголовок раздела «Неясности и цикл уточнений с агентом»Самое ценное в спецификации — не ответы, а честно записанные вопросы. Если правило неизвестно, его нельзя угадывать:
ставьте маркер неясностимаркер неясностиEN clarification markerПометка прямо в спецификации, что решение ещё не принято: в курсе — [УТОЧНИТЬ: вопрос], в Spec Kit — [NEEDS CLARIFICATION: …]. Запрещает агенту додумывать и собирает вопросы для эксперта. [УТОЧНИТЬ: вопрос] прямо в текст и копию — в раздел
«Открытые вопросы». Это русский аналог маркера [NEEDS CLARIFICATION: …] из шаблонов Spec Kit, которые прямо
запрещают модели догадываться: «если в описании чего-то нет — пометь».
Цикл уточнений выглядит так:
- Вы пишете черновик — или просите агента набросать его по вашему описанию и глоссарию.
- Агент ищет неясности и задаёт немного вопросов с вариантами ответа. В Spec Kit этим занимается команда
/speckit.clarify: до пяти точечных вопросов за проход, ответы записываются обратно вspec.md. - Вы или эксперт отвечаете; агент обновляет требования и сценарии и закрывает маркеры.
- Повторяйте, пока маркеров не останется или оставшиеся явно не вынесены за рамки задачи.
Промпт для шага 2 в любом агенте:
Прочитай specs/enrollment/spec.md и docs/glossary.md. Код не пиши и ничего не правь.Найди места, где требование можно понять двумя способами, где нет числа или границы,где сценарий не покрывает требование. Задай мне не больше пяти вопросов, у каждого —2–3 варианта ответа и твоя рекомендация. Неясное не додумывай: для вопросов, на которыея не отвечу, предложи маркер [УТОЧНИТЬ: …] и место в спецификации.Как это выглядит на спецификации записи на поток:
Агент: 1. REQ-005: 48 часов отсчитываются от момента записи или от выставления счёта? А) от записи (рекомендую: так в глоссарии); Б) от счёта. 2. REQ-005: ровно через 48 часов запись ещё действует или уже отменена? А) отменена (записалась 1 октября в 10:00 — 3 октября в 10:00 отменена); Б) ещё действует. 3. Что делать с платежом, который пришёл после автоматической отмены записи? А) вернуть деньги; Б) восстановить запись, если место свободно. 4. Что, если поток перенесли на другую дату: записи сохраняются? От какой даты считать 7 суток?Вы: 1 — А, 2 — А. Третий решили эксперты на Event Storming (урок DDD.2.3): деньги вернуть полностью, запись не восстанавливать; это делает контекст «Оплата», у нас — вне рамок, запиши решение со ссылкой. Четвёртый эксперты пока не решили: поставь [УТОЧНИТЬ] в «Открытые вопросы» и вынеси перенос потока за рамки этой функции.Обратите внимание на третий и четвёртый ответы. Это вопросы о правилах предметной области, и ни агент, ни разработчик не вправе закрыть их догадкой. Третий уже решён с экспертами — в спецификацию попадает решение и ссылка на него. Четвёртый не решён — он остаётся открытым, а решение «пока вне рамок» тоже принято явно.
Ещё один полезный приём Spec Kit — команда /speckit.checklist, которую документация называет «юнит-тестами для
требований»: чек-лист вопросов о полноте и однозначности спецификации («для каждого ли статуса описано поведение?»).
Часть таких проверок механическая — их можно автоматизировать, что мы и сделаем в практике.
Пример: спецификация записи на поток
Заголовок раздела «Пример: спецификация записи на поток»Соберём всё вместе. Это полная спецификация в формате курса; её проходит без замечаний линтер из практики.
# Спецификация: запись на поток
## КонтекстКонтекст «Набор» онлайн-школы. Студент записывается на поток курса до его старта; запись ожидает оплатыи после оплаты подтверждается. Пользователи: студент и менеджер набора. Термины — из docs/glossary.md.Действующая запись — запись на поток со статусом «ожидает оплаты» или «подтверждена»; только действующие записи занимают места.Числа (48 часов, 7 суток) — условия учебного примера.
## Пользовательские истории- **US-1 (P1)**: Как студент, я хочу записаться на поток, чтобы занять место до старта.- **US-2 (P1)**: Как менеджер набора, я хочу, чтобы неоплаченные записи освобождали места, чтобы поток заполнялся теми, кто готов платить.- **US-3 (P2)**: Как студент, я хочу отменить запись и понимать, сколько денег мне вернут.
## Требования
### Функциональные- **REQ-001**: Когда студент записывается на поток со свободными местами до старта потока, система должна создать запись на поток со статусом «ожидает оплаты».- **REQ-002**: Если число действующих записей равно вместимости потока, то система должна отказать в записи с ошибкой NoFreeSeats.- **REQ-003**: Если у студента уже есть действующая запись на этот поток, то система должна отказать в повторной записи с ошибкой AlreadyEnrolled.- **REQ-004**: Если студент записывается на поток в момент старта потока или позже, то система должна отказать в записи с ошибкой EnrollmentClosed.- **REQ-005**: Если запись на поток остаётся в статусе «ожидает оплаты» 48 часов с момента записи, то система должна перевести её в статус «отменена».- **REQ-006**: Когда студент отменяет запись за 7 суток (7 × 24 часа) или более до старта потока, система должна вернуть ему полную стоимость.- **REQ-007**: Когда студент отменяет запись позже, чем за 7 суток до старта потока, система должна перевести запись в статус «отменена» без возврата денег.- **REQ-008**: Когда запись на поток переходит в статус «отменена», система должна опубликовать событие ЗаписьОтменена (EnrollmentCancelled) с причиной отмены.
### Нефункциональные- **REQ-009**: Система должна хранить суммы денег целым числом копеек вместе с валютой.- **REQ-010**: Система должна отвечать на запрос записи на поток не дольше 500 мс для 95% запросов при нагрузке до 20 запросов в секунду.
## СценарииОбщие данные: поток «Python для аналитиков», старт 2 ноября 2026 года в 10:00, вместимость 2, цена 30 000 руб.
### SC-001: Свободное место естьТребования: REQ-001- **Дано** на потоке одна запись Бориса со статусом «подтверждена»- **Когда** Анна записывается на поток 1 октября в 10:00- **Тогда** у Анны появляется запись со статусом «ожидает оплаты»
### SC-002: Мест нетТребования: REQ-002- **Дано** у Анны запись «ожидает оплаты», у Бориса — «подтверждена»- **Когда** Вера записывается на поток- **Тогда** система отказывает с ошибкой NoFreeSeats
### SC-003: Повторная записьТребования: REQ-003- **Дано** у Анны запись на поток со статусом «ожидает оплаты»- **Когда** Анна снова записывается на этот поток- **Тогда** система отказывает с ошибкой AlreadyEnrolled
### SC-004: Запись в момент стартаТребования: REQ-004- **Дано** на потоке есть свободные места- **Когда** Глеб записывается на поток 2 ноября в 10:00- **Тогда** система отказывает с ошибкой EnrollmentClosed
### SC-005: Ровно 48 часов без оплатыТребования: REQ-005, REQ-008- **Дано** Анна записалась на поток 1 октября в 10:00 и не оплатила- **Когда** наступает 3 октября 10:00- **Тогда** запись Анны получает статус «отменена»- **И** публикуется событие ЗаписьОтменена (EnrollmentCancelled) с причиной «истёк срок оплаты»
### SC-006: Отмена ровно за 7 суток — полный возвратТребования: REQ-006, REQ-009- **Дано** у Анны запись со статусом «подтверждена»- **Когда** Анна отменяет запись 26 октября в 10:00- **Тогда** Анне возвращается 3 000 000 копеек в валюте RUB
### SC-007: Отмена на минуту позже — без возвратаТребования: REQ-007- **Дано** у Анны запись со статусом «подтверждена»- **Когда** Анна отменяет запись 26 октября в 10:01- **Тогда** запись Анны получает статус «отменена»- **И** возврат равен 0
### SC-008: Повторная запись после отменыТребования: REQ-001, REQ-003- **Дано** у Анны отменённая запись на поток, свободное место есть- **Когда** Анна снова записывается на поток 5 октября в 10:00- **Тогда** у Анны появляется новая запись со статусом «ожидает оплаты»- **И** старая запись остаётся в истории со статусом «отменена»
### SC-009: НагрузкаТребования: REQ-010- **Дано** 1000 потоков и 20 запросов записи в секунду в течение 10 минут- **Когда** измеряем время ответа на каждый запрос- **Тогда** 95% ответов приходят не дольше чем за 500 мс
## Вне рамок- Оплата и выставление счёта — контекст «Оплата».- Лист ожидания и предложение освободившегося места — отдельная функция.- Письма и пуши — контекст «Уведомления».- Перенос записи на другой поток.
## Открытые вопросы- Решено (Event Storming, урок DDD.2.3): повторная запись после отмены разрешена — запрет касается только действующих записей (REQ-003, SC-008).- Решено (там же): платёж, пришедший после автоматической отмены, возвращается полностью, запись не восстанавливается. Реализует контекст «Оплата» — вне рамок.- [УТОЧНИТЬ] Поток перенесли на другую дату — что с записями и правилом 7 суток? До ответа экспертов — вне рамок.- [УТОЧНИТЬ] Возможен ли частичный возврат? До ответа действует REQ-007: позже чем за 7 суток — без возврата.Обратите внимание на несколько деталей. Каждое требование — одна строка и одно обязательство. Границы чисел покрыты
сценариями ровно на границе (SC-005, SC-006) и сразу за ней (SC-007). Определение «действующей записи» дано один
раз в контексте. В «Открытых вопросах» два вида записей: решения, которые эксперты уже приняли на Event Storming
(урок DDD.2.3), — со ссылкой; и две горячие точки, которые там
остались без ответа, — с маркером [УТОЧНИТЬ] и явным решением «пока вне рамок». Ни один вопрос не закрыт догадкой.
Практика
Заголовок раздела «Практика»Напишите линтер требований — программу, которая разбирает spec.md в формате курса и находит механические проблемы:
требования без идентификатора, без сценария, с размытыми словами из стоп-списка, составные требования и ссылки
сценариев на несуществующие требования. Готовы стоп-список, константы правил и функция format_report, которая
печатает отчёт. Вам нужно написать четыре функции:
parse_spec(text)— разобрать markdown: требования (ID илиNone, текст, номер строки) и сценарии со ссылками;find_vague(text)— найти размытые слова и выражения;is_compound(text)— решить, похоже ли требование на составное;lint(text)— собрать список проблем в заданном порядке.
Кнопка «Запустить» печатает отчёт по учебной спецификации EXAMPLE_SPEC. Когда тесты пройдут, должно получиться так:
Требований: 4, сценариев: 2, проблем: 8 REQ-002 размытые слова: быстро, удобно REQ-002 составное требование REQ-002 нет сценария строка 7 нет идентификатора: Система должна закрывать запись на поток вовремя. строка 7 размытые слова: вовремя REQ-003 составное требование REQ-003 нет сценария SC-002 ссылка на несуществующее требование: REQ-004import re
# Стоп-список размытых слов (урок SDD.2.1). Основы слов сравниваем с началом слова:
# «быстр» ловит «быстро», «быстрый», «быстрого». Буква «ё» заменена на «е» —
# текст требования перед поиском нормализуем так же.
VAGUE_STEMS = ["быстр", "удобн", "интуитивн", "оптимальн", "эффективн", "гибк",
"своевременн", "вовремя", "современн", "надежн"]
# Размытые выражения из нескольких слов ищем как подстроку, начиная с границы слова
VAGUE_PHRASES = ["по возможности", "при необходимости", "в разумные сроки", "как правило",
"и т. д.", "и т.д.", "и др."]
# Модальные слова требования: «система должна …»
MODAL_WORDS = ["должен", "должна", "должно", "должны"]
RULE_NO_ID = "нет идентификатора"
RULE_VAGUE = "размытые слова"
RULE_COMPOUND = "составное требование"
RULE_NO_SCENARIO = "нет сценария"
RULE_UNKNOWN_REF = "ссылка на несуществующее требование"
REQ_ID = re.compile(r"REQ-\d{3}")
SC_ID = re.compile(r"SC-\d{3}")
EXAMPLE_SPEC = """# Спецификация: запись на поток
## Требования
- **REQ-001**: Когда студент записывается на поток со свободными местами, система должна создать запись на поток со статусом «ожидает оплаты».
- **REQ-002**: Система должна быстро и удобно показывать студенту его записи.
- Система должна закрывать запись на поток вовремя.
- **REQ-003**: Если запись остаётся в статусе «ожидает оплаты» 48 часов с момента записи, то система должна отменить запись и/или уведомить студента.
## Сценарии
### SC-001: Свободное место есть
Требования: REQ-001
- **Дано** поток с вместимостью 2 и одной записью «подтверждена»
- **Когда** Вера записывается на поток
- **Тогда** у Веры появляется запись «ожидает оплаты»
### SC-002: Повторная запись
Требования: REQ-004
- **Дано** у Анны есть запись «ожидает оплаты» на поток
- **Когда** Анна снова записывается на этот поток
- **Тогда** система отказывает: AlreadyEnrolled
"""
def _normalize(text):
"""Нижний регистр и «е» вместо «ё»: «Надёжно» → «надежно». Длина строки не меняется."""
return text.lower().replace("ё", "е")
def parse_spec(text):
"""Разобрать спецификацию в markdown на требования и сценарии.
Требования — пункты списка («- …» или «* …») в разделе «## Требования» (подразделы «### …»
внутри него тоже считаются). У пункта с идентификатором текст начинается с REQ-001
(можно жирным: **REQ-001**), дальше двоеточие или точка и формулировка.
Сценарии — заголовки «### SC-001: …» в разделе «## Сценарии»; ссылки на требования —
в строке «Требования: REQ-001, REQ-002» под заголовком сценария.
Возвращает (requirements, scenarios):
requirements — список словарей {"id": "REQ-001" или None, "text": формулировка, "line": номер строки с 1};
scenarios — словарь {"SC-001": ["REQ-001", ...]} в порядке появления сценариев.
"""
# TODO: ваш код
...
def find_vague(text):
"""Размытые слова и выражения из стоп-списка в тексте требования.
Текст нормализуем: нижний регистр, «ё» → «е». Выражения из VAGUE_PHRASES ищем как подстроку,
перед которой нет буквы; слово считается размытым, если начинается с основы из VAGUE_STEMS.
Возвращает найденные фрагменты в нормализованном виде (слово целиком: «быстро», «удобного»;
выражение — как в списке), без повторов, в порядке появления в тексте.
"""
# TODO: ваш код
...
def is_compound(text):
"""Похоже ли требование на составное — несколько обязательств в одном.
Правила (по порядку; текст нормализован, как в find_vague):
1) в тексте есть «и/или» → да;
2) модальных слов из MODAL_WORDS («должна» и т. п.) два и больше → да;
3) в части после первого модального слова (если его нет — во всём тексте), выбросив фрагменты
в кавычках «…» и выражения из VAGUE_PHRASES, есть отдельное слово «и» или «или» → да;
иначе → нет.
Это эвристика: она подсвечивает кандидатов, а решает человек.
"""
# TODO: ваш код
...
def lint(text):
"""Проверить спецификацию и вернуть список проблем — кортежей (где, правило, подробности).
«Где» — идентификатор требования, а для требования без него — «строка N».
Для каждого требования по порядку проверьте правила в таком порядке:
RULE_NO_ID — нет идентификатора (подробности — текст требования);
RULE_VAGUE — есть размытые слова (подробности — find_vague через «, »);
RULE_COMPOUND — составное требование (подробности — пустая строка);
RULE_NO_SCENARIO — у требования с идентификатором нет ни одного сценария (подробности — "").
После всех требований — сценарии по порядку: каждая ссылка на требование, которого нет
в спецификации, даёт (id сценария, RULE_UNKNOWN_REF, id требования).
"""
# TODO: ваш код
...
def format_report(text):
"""Отчёт линтера для человека (готовая функция)."""
requirements, scenarios = parse_spec(text) or ([], {})
problems = lint(text) or []
lines = [f"Требований: {len(requirements)}, сценариев: {len(scenarios)}, проблем: {len(problems)}"]
for where, rule, details in problems:
lines.append(f" {where:10} {rule}" + (f": {details}" if details else ""))
return "\n".join(lines)
print(format_report(EXAMPLE_SPEC))
Дополнительно. Сохраните решение как lint_spec.py и прогоните его на спецификации записи на поток из этого урока
(должно быть 0 проблем), а потом на черновике, который напишет ваш агент по подсказке из блока «Как это у ИИ-агентов».
Сколько проблем нашёл линтер? Какие из них настоящие, а какие — ложные срабатывания эвристики?
Проверь себя
Заголовок раздела «Проверь себя»- Спецификация функции в курсе: Контекст, Пользовательские истории, Требования (
REQ-001), Сценарии (SC-001, «Дано / Когда / Тогда», строка «Требования: REQ-…»), Вне рамок, Открытые вопросы. Тот же формат — в ДЗ sdd-hw01-spec. - Требование — одно проверяемое обязательство со словом «должна». Шаблоны EARS (Мавин и др., 2009) заставляют назвать триггер или состояние; их используют и инструменты SDD, например Kiro. Нефункциональные требования — с измеримым порогом.
- Критерий приёмки — сценарий с конкретными данными, границами и наблюдаемым результатом; ID сценария переходит в имя теста.
- Плохие требования: размытые слова, составные, решение вместо проблемы, без числа, чужие слова, запрет без поведения. Механическую часть ловит линтер, смысл проверяет человек.
- Неясное не угадывают:
[УТОЧНИТЬ: вопрос], цикл из нескольких вопросов агента с вариантами ответа, решения эксперта записываются обратно в спецификацию.
Дальше: урок SDD.2.2 — технический план, модель данных и контракты: как перейти от «что» к «как», где здесь контексты и агрегаты DDD и сколько деталей давать агенту, чтобы он не додумывал и не утонул.