Константин Рядов Инженер JVM, ушедший в инженерию ИИ. То, что я сделал.

Рубрика бэк ту скул: почему мы всегда писали спецификации и что изменилось, когда их начала читать машина?

Когда говорят о spec-driven development, часто можно услышать такую историю:

Spec-driven development: новая практика, придуманная под ИИ. До появления кодовых агентов никто не писал спецификацию первой, все писали код.

Звучит логично, но исторически это не совсем так.

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

Вот эта часть и изменилась.

Статья, которую все считают истоком waterfall

В 1970 году Уинстон Ройс опубликовал небольшую работу об управлении разработкой больших программных систем. Её помнят как исток водопадной модели, и это досадно: слова waterfall в тексте нет вообще.

Зато в тексте есть та самая последовательная диаграмма, которую все воспроизводят, и сразу за ней собственный вердикт Ройса: сама концепция ему нравится, но такая её реализация рискованна и напрашивается на провал.

А ещё там есть вот это, про раннюю фазу проекта:

На ранней фазе разработки документация и есть спецификация, и есть дизайн.

Не «описывает» дизайн. Является дизайном. Тезис о том, что спецификация является первичным артефактом, придуман не в 2025 году. Это едва ли не старейший тезис в профессии, и лежит он в той самой статье, которую цитируют, когда хотят посмеяться над привычкой записывать.

Жизненный цикл, выросший из неё, узнает любой инженер:

    SOFTWARE DEVELOPMENT LIFECYCLE        "SDLC"

            ┌──────────────────────────────┐
    PRD ───►│  PLANNING & DESIGN           │
            └───────────────┬──────────────┘
                            │
     ╭╌╌ cycle ╌╌╌╌╌╌╌╌╌╌╌╌╌│╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╮
     ╎                      ▼                  ╎
     ╎      ┌──────────────────────────────┐   ╎
     ╎   ┌─►│  IMPLEMENT                   │   ╎
     ╎   │  └───────────────┬──────────────┘   ╎
     ╎   │                  │                  ╎
     ╎   │                  ▼                  ╎
     ╎   │  ┌──────────────────────────────┐   ╎
     ╎   │  │  TESTING / QA                │   ╎
     ╎   │  └───────────────┬──────────────┘   ╎
     ╎   └──────────────────┤ tests fail       ╎
     ╰╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌│╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╯
                            ▼
            ┌──────────────────────────────┐
            │  DEPLOYMENT                  │
            │  dev → staging → production  │
            └───────────────┬──────────────┘
                            │
                            ▼
            ┌──────────────────────────────┐
            │  MAINTENANCE                 │
            └──────────────────────────────┘

Почему документ всегда проигрывал

Если идея была верна ещё в 1970-м, возникает очевидный вопрос: почему документы получались настолько плохими.

Честный ответ дали Дэвид Парнас и Пол Клементс в 1986 году, в работе с говорящим названием A Rational Design Process: How and Why to Fake It. Аргумент ровно такой, как в заголовке. Рационального процесса проектирования вы не выдержите никогда: информация приходит не в том порядке, а на середине пути вы меняете решение. Что вы можете, так это выпустить постфактум ту документацию, которую выпустили бы, если бы выдержали.

Парнас же задал критерий приёмки для документа требований, и он строже, чем принято думать:

всё, что нужно знать, чтобы написать программу, устраивающую заказчика, и ничего сверх того

Организованное как справочник, а не как текст для чтения от корки до корки.

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

Как спецификацию пытались сделать принудительной

Дальше появились два семейства решений, и разошлись они в противоположные стороны.

Первое занесло спецификацию внутрь программы. Design by Contract Бертрана Мейера, опубликованный в 1992 году, описывает предусловия и постусловия процедуры как контракт между вызывающим и поставщиком, причём вина распределяется механически: нарушено предусловие, значит, виноват вызывающий. Мейер прямо возражал против документации как отдельного продукта, который сопровождают отдельно от кода.

Второе сделало точной саму прозу. RFC 2119 в 1997 году зафиксировал значения MUST, SHOULD и MAY так, чтобы два независимых разработчика прочитали протокол одинаково, и тут же, в собственном заключительном разделе, предупредил, что пользоваться этими словами следует осторожно и экономно. Двадцать лет спустя RFC 8174 пришлось сузить правило ещё раз: слова нормативны, только если написаны заглавными.

Оба решения работают. Ни одно не сделало спецификацию исполняемой целиком. Контракт покрывает интерфейс, а не замысел. Нормативное слово делает однозначным предложение, а не систему.

Сначала тест, потом поведение

Следующей попыткой было заставить спецификацию исполняться.

Test-driven development Кента Бека обычно пересказывают как «сначала тест, потом код». Его собственное каноническое описание начинается на шаг раньше: с записанного списка сценариев, которым должно удовлетворять изменение, ещё до того, как появился хоть один тест. Спецификация предшествует тесту, а не только коду.

Дэн Норт переименовал словарь в 2006 году по конкретной причине: слово тест и было тем, что мешало людям понять практику. Стоило назвать это поведением, и те же вопросы начали отвечать на себя сами. Гойко Аджич позже упаковал коллаборативную версию как Specification by Example, целясь в документацию, которая остаётся живой, потому что исполняется.

Каждый шаг сдвигает первичный артефакт на позицию выше по течению:

step      TRAD            TDD             SDD
     ╔════════════╗  ┌────────────┐  ┌────────────┐
1st  ║    CODE    ║  │    TEST    │  │    SPEC    │
     ╚═════╤══════╝  └─────┬──────┘  └─────┬──────┘
           ▼               ▼               ▼
     ┌────────────┐  ╔════════════╗  ┌────────────┐
2nd  │    DOCS    │  ║    CODE    ║  │  DESIGN /  │
     └────────────┘  ╚════════════╝  │REQUIREMENTS│
                                     └─────┬──────┘
                                           ▼
                                     ┌────────────┐
3rd                                  │ IMPLEMENT  │
                                     └─────┬──────┘
                                           ▼
                                     ╔════════════╗
4th                                  ║    CODE    ║
                                     ╚════════════╝

╔══╗ = CODE:  первый в TRAD, второй в TDD, ПОСЛЕДНИЙ в SDD.

Но тест остаётся примером, а не правилом. Он говорит, что произойдёт на этих входных данных. Он не может запретить дизайн, которого ещё не существует, и не хранит в себе ответа, почему ожидаемое значение именно такое.

Пресс-релиз вперёд продукта

У той же идеи есть нетехническая ветка. Практика working backwards в Amazon, публично описанная Вернером Фогельсом в 2006 году, требует написать пресс-релиз и FAQ до того, как продукт появится.

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

Запомните этот механизм: именно на нём и держится сегодняшний инструментарий.

Промпт как спецификация, которую выбрасывают

Теперь современная часть.

Vibe coding, то есть разговор с моделью до тех пор, пока результат не начнёт выглядеть правильно, по-настоящему хороший способ работать. Он быстрый, приятный, и для одноразового скрипта или разведочного изменения его трудно чем-то перебить.

                    VIBE CODING
                    ───────────

        ┌─────────────────────────────────┐
        │         User w/ prompt          │
        └────────────────┬────────────────┘
                         │
                         ▼
        ┌─────────────────────────────────┐
        │        AI generates code        │
        └────────────────┬────────────────┘
                         │
                         ▼
        ┌─────────────────────────────────┐
   ┌───▶│           EDIT PROMPT           │   ?
   │    └────────────────┬────────────────┘
   │                     │  regenerate
   │                     ▼
   │    ┌─────────────────────────────────┐
   │    │             AI CODE             │   ?
   │    └───┬────────────┬────────────────┘
   │        │            │
   └────────┘ not right  │  looks right
                         ▼
        ┌─────────────────────────────────┐
        │          Desired State          │
        └─────────────────────────────────┘

   ?  непонятно, почему модель решила именно так
   ?  непонятно, сколько витков займёт цикл

Но посмотрите, что этот цикл сохраняет, а что выбрасывает. Код коммитится. Промпт, который его породил, не коммитится. Рассуждение тоже. Прогоните один и тот же промпт сто раз и получите сто защитимых программ, без всякой записи о том, какое ограничение заставило модель остановиться именно на этой. Замысел до репозитория не доезжает.

Читатель сменился

Spec-driven development возвращает стадии ревью на место и ставит их до того, как что-либо построено.

                      SPEC CODING
                      ───────────

       ┌────────────────────────────────┐
       │          Prompt Spec           │
       └────────────────┬───────────────┘
                        │
                        ▼
       ┌────────────────────────────────┐
       │          Requirements          │◀────┐
       └────────────────┬───────────────┘     │
                        │                     │
              happy? ───┴─── no ──▶ EDIT ─────┘
                        │
                       yes
                        ▼
       ┌────────────────────────────────┐
       │             Design             │◀────┐
       └────────────────┬───────────────┘     │
                        │                     │
              happy? ───┴─── no ──▶ EDIT ─────┘
                        │
                       yes
                        ▼
       ┌────────────────────────────────┐
       │           Implement            │
       └────────────────────────────────┘
              code · tests · docs

Промпт описывает поведение и ограничения, а не реализацию. Из него получается документ требований, который ревьюят и либо утверждают, либо правят. Утверждённые требования становятся дизайном, который проходит ту же процедуру. И только потом агент пишет код.

Это механизм Amazon (отказаться дёшево, на бумаге), применённый к генератору вместо продуктового комитета. Ворота посильны ровно потому, что ещё ничего не построено.

И вот в чём настоящая новизна, и она не в том, что мы начали писать спецификации.

Пятьдесят лет единственным читателем спецификации был коллега, который мог тихо её проигнорировать. Теперь читатель заново порождает из неё код.

Устаревание перестаёт быть проблемой документации и становится проблемой сборки. Впервые за всю историю практики держать документ правдивым стало кому-то непосредственно выгодно.

Сколько спецификация стоит

Это самый сильный довод «за». Честный разбор обязан включать и счёт.

Spec-driven development не убирает неоднозначность. Он её переносит. Неоднозначность в коде находима: машина его исполняет и сообщает вам. Неоднозначность в прозе находит только внимательный читатель, а спецификация, достаточно точная, чтобы неоднозначности не осталось, становится программой, написанная на наименее пригодном для этого языке.

Мартин Фаулер прошёл этот путь ещё в 2003 году под другим именем, UML as programming language, и пришёл к выводу, что подъём уровня абстракции не освобождает от работы программирования.

Ещё три статьи расходов, и ни одна не гипотетическая:

  • ворота, которые нельзя исполнить: сорок страниц, сгенерированных за двадцать секунд и прочитанных один раз одним усталым человеком. Генерация подешевела до нуля, чтение не подешевело;
  • гниение спецификации стало активным, а не пассивным. Устаревшую вики игнорируют. Устаревшую спецификацию скармливают агенту, и она инструктирует следующую генерацию;
  • спецификация сама превращается в источник дефектов. Команда одного из ведущих инструментов публикует ровно это и обращается с неоднозначностью в требованиях как с классом багов, у которого есть собственный проход анализа.

И наконец: первичный артефакт

Если убрать инструментарий, останется простое утверждение о первичности. Первичный артефакт это файл, который вы правите, когда хотите изменить поведение; файл, о котором спорит ревьюер; файл, чью историю кто-то будет читать через год. Spec Kit от GitHub формулирует амбицию без обиняков: спецификация является первичным артефактом, а код лишь её выражением.

На практике это выглядит скромнее риторики:

┌────────────────────────────────┬────────────────────────────────┐
│ VIBE CODING                    │ SPEC CODING                    │
├────────────────────────────────┼────────────────────────────────┤
│ "need /login for users auth"   │ Feature: User Auth             │
│                                │  ├─ Endpoint: /login POST      │
│ одно предложение замысла       │  ├─ Accepts: {user, pass}      │
│ → ~30 законных реализаций      │  ├─ Fails: 400 if missing user │
│                                │  └─ Tests: valid creds → 200   │
├────────────────────────────────┼────────────────────────────────┤
│  ┌─→ AI generates code ──┐     │  spec ─┬─→ implementation      │
│  │                       │     │        ├─→ tests               │
│  └──── edit the prompt ←─┘     │        └─→ docs                │
│                                │                                │
│  цикл, пока не выглядит верно  │  одно прочтение, не тридцать   │
└────────────────────────────────┴────────────────────────────────┘

Где индустрия оказалась на самом деле, тоже стоит заметить. Форма из трёх документов (requirements, design, tasks) реальна, и к ней действительно сходятся; а EARS, нотация требований, которую взял один из популярных инструментов, придумана в Rolls-Royce в 2009 году и к ИИ отношения не имеет. При этом Thoughtworks по-прежнему держит spec-driven development в кольце Assess, отмечая, что определение не устоялось. Первая академическая попытка это определение дать, в 2026 году, начинается с признания: общего словаря пока нет. А формат, на котором экосистема действительно сошлась, это AGENTS.md, файл инструкций, а не спецификация.

Итог

Если сильно упростить историю развития идей, получается такая цепочка:

Документация как спецификация
↓
Гниение документации
↓
Имитация рационального процесса
↓
Design by Contract
↓
Нормативная проза
↓
Исполняемые примеры
↓
Утверждение до реализации
↓
Машина как второй реализатор
↓
Повторное порождение из текста
↓
Spec-Driven Development

Поэтому наиболее корректно рассказывать эту историю так:

Spec-driven development появился не потому, что инженеры заново открыли пользу записывать. Записывать рекомендовали всегда, и записанное всегда протухало, потому что единственный читатель мог себе позволить его игнорировать. Изменилось то, что у спецификации появился читатель, который строит из неё систему. А документ, который исполняют, это документ, который наконец кому-то выгодно держать правдивым. Переживёт ли эта выгода встречу с сорока страницами, которые никому не успеть прочитать, остаётся открытым вопросом.

Полезные ссылки

Все записи