Традиционная парадигма: код -- это истина
На протяжении десятилетий в индустрии разработки доминировала одна идея: код -- это единственный источник истины. Документация устаревает, комментарии врут, архитектурные диаграммы не обновляются. Но код всегда точно описывает, что система делает.
Традиционная иерархия:
1. Код (truth)
2. Тесты (verification of truth)
3. Документация (описание truth, часто устаревшее)
4. Комментарии (intent, часто неточный)
5. Устные знания (tribal knowledge)
Эта парадигма работала, потому что код был дорогим в создании. Разработчик тратил дни на реализацию, тщательно обдумывал каждое решение, и код действительно содержал его intent -- потому что он вложил в этот код своё время и мышление.
Новая парадигма: intent -- это истина
С AI стоимость генерации кода упала почти до нуля. Код перестал быть дорогим артефактом, несущим intent автора. Теперь код -- это один из многих возможных вариантов реализации intent-а. И если intent может быть реализован разными способами, то именно intent становится первичным.
Новая иерархия:
1. Intent / Specification (truth)
2. Тесты (verification that code matches intent)
3. Код (implementation of intent, regenerable)
4. Документация (auto-generated from spec)
Ключевой сдвиг: Код можно перегенерировать из спецификации. Спецификацию нельзя восстановить из кода. Поэтому спецификация ценнее кода.
Почему этот сдвиг неизбежен
1. Код можно перегенерировать.
Спецификация "POST /api/orders с валидацией полей"
→ Может быть реализована на PHP, Go, Python, TypeScript
→ Может использовать разные фреймворки
→ Может быть перегенерирована при обновлении AI
→ Код меняется, intent остаётся
2. Спецификация переживает смену технологий.
2020: Спецификация → Symfony 5 implementation
2023: Спецификация → Symfony 6 implementation (та же спецификация!)
2026: Спецификация → Symfony 8 implementation (та же спецификация!)
Intent не зависит от фреймворка. Код зависит.
3. Множественные реализации одной спецификации.
Одна спецификация OrderService:
→ PHP implementation (для web-приложения)
→ Go implementation (для высоконагруженного API)
→ TypeScript implementation (для serverless functions)
Все три реализации удовлетворяют одну спецификацию.
4. AI может верифицировать код против спецификации.
Spec: "Endpoint должен возвращать 409 при попытке отменить заказ старше 30 минут"
Code: проверяет время → возвращает 409
AI: ✅ Код соответствует спецификации
Spec: "Endpoint должен валидировать email"
Code: нет валидации email
AI: ❌ Код НЕ соответствует спецификации
Что такое "intent" на практике
Intent -- это не абстрактная философская концепция. Это конкретный набор артефактов, описывающих ЧТО система должна делать, ПОЧЕМУ, и КАКИЕ ограничения она должна соблюдать.
Компоненты intent
intent_components:
business_requirements:
description: "ЧТО система должна делать для пользователя"
format: "Structured user stories with acceptance criteria"
example:
as_a: "customer"
i_want: "to cancel my order within 30 minutes"
so_that: "I can change my mind without penalties"
acceptance_criteria:
- "Given an order placed 20 minutes ago, when I cancel, then order status = cancelled"
- "Given an order placed 40 minutes ago, when I try to cancel, then I see error message"
api_contracts:
description: "КАК система общается с внешним миром"
format: "OpenAPI 3.1 / GraphQL Schema"
example: "See OpenAPI spec below"
data_invariants:
description: "КАКИЕ правила данные должны ВСЕГДА соблюдать"
format: "Constraints and rules"
example:
- "Order total must equal sum of line items"
- "Cancelled order must have cancellation_reason"
- "Refund amount must not exceed order total"
non_functional:
description: "КАКИЕ характеристики система должна иметь"
format: "Measurable criteria"
example:
performance: "p95 response time < 200ms"
security: "All endpoints require authentication"
scalability: "Handle 1000 concurrent cancellations"
business_rules:
description: "КАКУЮ логику система реализует"
format: "Decision tables, state machines"
example: "See state machine and decision table below"
Бизнес-требования в структурированном формате
# User stories with acceptance criteria
stories:
- id: US-001
title: "Customer cancels order"
as_a: "authenticated customer"
i_want: "to cancel my recent order"
so_that: "I can get a refund"
acceptance_criteria:
- id: AC-001-1
given: "Order placed less than 30 minutes ago"
when: "Customer requests cancellation"
then: "Order status changes to 'cancelled'"
- id: AC-001-2
given: "Order placed less than 30 minutes ago"
when: "Customer requests cancellation"
then: "Refund is initiated automatically"
- id: AC-001-3
given: "Order placed more than 30 minutes ago"
when: "Customer requests cancellation"
then: "Error message displayed: 'Cancellation window has passed'"
- id: AC-001-4
given: "Order already cancelled"
when: "Customer requests cancellation again"
then: "Current cancelled state returned (idempotent)"
API-контракты
# OpenAPI 3.1 specification (partial)
openapi: "3.1.0"
paths:
/api/v1/orders/{orderId}/cancel:
post:
summary: "Cancel an order"
operationId: "cancelOrder"
security:
- bearerAuth: []
parameters:
- name: orderId
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
type: object
properties:
reason:
type: string
maxLength: 500
description: "Optional cancellation reason"
example:
reason: "Changed my mind about the purchase"
responses:
"200":
description: "Order successfully cancelled"
content:
application/json:
schema:
$ref: "#/components/schemas/CancelledOrder"
"403":
description: "Order belongs to another user"
"404":
description: "Order not found"
"409":
description: "Order cannot be cancelled (window passed or already cancelled)"
State machine для сложных workflow
State Machine: Order Lifecycle
┌─────────┐ payment ┌───────────┐
│ created │ ──────────────→ │ confirmed │
└─────────┘ └───────────┘
│
┌──────────────┤
│ │
▼ ▼ (30 min timer)
┌───────────┐ ┌────────────┐
│ cancelled │ │ processing │
└───────────┘ └────────────┘
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│ refunded │ │ shipped │
└───────────┘ └───────────┘
│
▼
┌───────────┐
│ delivered │
└───────────┘
Transitions:
created → confirmed: payment_received event
confirmed → cancelled: cancel_request (within 30 min of confirmed_at)
confirmed → processing: 30_min_timer_expired event
cancelled → refunded: refund_completed event
processing → shipped: shipment_created event
shipped → delivered: delivery_confirmed event
Guards:
confirmed → cancelled: now() - confirmed_at <= 30 minutes
confirmed → processing: now() - confirmed_at > 30 minutes
Decision table для бизнес-правил
Decision Table: Can Order Be Cancelled?
| Order Status | Time Since Confirmation | User Role | Result |
|-------------|------------------------|------------|------------|
| created | N/A | owner | YES |
| created | N/A | admin | YES |
| confirmed | <= 30 min | owner | YES |
| confirmed | <= 30 min | admin | YES |
| confirmed | > 30 min | owner | NO (409) |
| confirmed | > 30 min | admin | YES (override) |
| processing | any | owner | NO (409) |
| processing | any | admin | YES (manual) |
| shipped | any | any | NO (409) |
| cancelled | any | any | IDEMPOTENT |
| refunded | any | any | NO (409) |
Примеры и контрпримеры
Мощный инструмент для фиксации intent -- показать и правильное, и неправильное поведение:
examples:
valid:
- name: "Normal cancellation"
input:
order_id: "550e8400-e29b-41d4-a716-446655440000"
reason: "Changed my mind"
preconditions:
order_status: "confirmed"
time_since_confirmation: "15 minutes"
requesting_user: "order owner"
expected:
status_code: 200
order_status: "cancelled"
refund_initiated: true
- name: "Cancellation without reason"
input:
order_id: "550e8400-e29b-41d4-a716-446655440000"
reason: null
preconditions:
order_status: "confirmed"
time_since_confirmation: "5 minutes"
expected:
status_code: 200
order_status: "cancelled"
cancellation_reason: null
invalid:
- name: "Too late to cancel"
input:
order_id: "550e8400-e29b-41d4-a716-446655440000"
preconditions:
order_status: "confirmed"
time_since_confirmation: "45 minutes"
expected:
status_code: 409
error: "Cancellation window has passed"
- name: "Not owner"
input:
order_id: "550e8400-e29b-41d4-a716-446655440000"
preconditions:
requesting_user: "different user"
expected:
status_code: 403
error: "Access denied"
Как эффективно фиксировать intent
Спецификация как диалог между человеком и AI
Intent не рождается мгновенно. Это итеративный процесс уточнения:
Итерация 1: Человек → начальный intent
"Нужна отмена заказов в течение 30 минут"
Итерация 2: AI → уточняющие вопросы
- "30 минут от создания заказа или от оплаты?"
- "Что делать с частично оплаченными заказами?"
- "Возврат на ту же карту или на баланс?"
- "Что если refund fail?"
Итерация 3: Человек → уточнения
- "От подтверждения оплаты"
- "Частичная оплата невозможна в нашей системе"
- "На ту же карту"
- "Повторить 3 раза, потом alert админу"
Итерация 4: AI → детальная спецификация
[Полный документ с учётом всех уточнений]
Итерация 5: Человек → ревью и одобрение
"Всё верно. Добавь rate limiting: max 10 отмен/час на пользователя"
Итерация 6: AI → финальная спецификация + реализация
Ключевой инсайт: AI не просто кодит -- он помогает уточнить intent. Его вопросы выявляют edge cases, о которых вы не подумали.
Шесть шагов фиксации intent
1. Человек пишет начальный intent
↓
2. AI задаёт уточняющие вопросы
↓
3. Человек уточняет
↓
4. AI составляет детальную спецификацию
↓
5. Человек ревьюит и одобряет
↓
6. AI реализует по одобренной спецификации
Каждый шаг добавляет precision к intent:
| Шаг | Precision | Пример |
|---|---|---|
| 1. Начальный intent | ~30% | "Отмена заказов" |
| 2. После вопросов AI | ~60% | "+ 30 мин окно, + refund, + edge cases" |
| 3. После уточнений | ~80% | "+ конкретные бизнес-правила" |
| 4. Детальная спецификация | ~90% | "+ API contract, data model, state machine" |
| 5. После ревью | ~95% | "+ пропущенные детали" |
| 6. Реализация + тесты | ~99% | "Код + тесты подтверждают intent" |
Версионирование и трассировка
Связь spec → code → tests → deployment
Одна из самых важных практик SDD -- трассировка от intent до production:
traceability_matrix:
spec: "specs/order-cancellation/v3.md"
requirements:
- id: "FR-1"
text: "Customer can cancel within 30 minutes"
implemented_in:
- "src/Handler/CancelOrderHandler.php"
- "src/Service/CancellationWindowChecker.php"
tested_by:
- "tests/Unit/CancelOrderHandlerTest.php::testCancellationWithin30Minutes"
- "tests/Unit/CancelOrderHandlerTest.php::testCancellationAtExactly30Minutes"
- "tests/Unit/CancelOrderHandlerTest.php::testCancellationAfter30Minutes"
deployed: "2026-03-07 (release v2.15.0)"
- id: "FR-2"
text: "Refund initiated automatically"
implemented_in:
- "src/EventListener/OrderCancelledListener.php"
- "src/Service/RefundService.php"
tested_by:
- "tests/Integration/RefundServiceTest.php::testRefundInitiatedOnCancellation"
- "tests/Integration/RefundServiceTest.php::testRefundRetryOnFailure"
deployed: "2026-03-07 (release v2.15.0)"
- id: "EC-3"
text: "Concurrent cancel + fulfill uses optimistic locking"
implemented_in:
- "src/Handler/CancelOrderHandler.php (version check)"
tested_by:
- "tests/Integration/ConcurrentCancellationTest.php"
deployed: "2026-03-08 (release v2.15.1)"
Практические инструменты для поддержки intent
Markdown-спецификации в репозитории:
project/
├── specs/
│ ├── README.md # Index of all specs
│ ├── template.md # Spec template
│ ├── order-cancellation/
│ │ ├── overview.md # Human-readable spec
│ │ ├── api.yaml # OpenAPI contract
│ │ ├── state-machine.md # State diagram
│ │ └── CHANGELOG.md
│ └── ...
├── adr/ # Architecture Decision Records
│ ├── 001-cqrs-pattern.md
│ ├── 002-async-refunds.md
│ └── template.md
├── src/
├── tests/
└── CLAUDE.md # AI context with guardrails
ADR (Architecture Decision Records):
# ADR-002: Async Refunds via Messenger
## Status
Accepted (2026-03-05)
## Context
Order cancellation requires refund processing.
Refund API calls to payment provider can take 5-30 seconds.
Synchronous processing would degrade API response time.
## Decision
Process refunds asynchronously via Symfony Messenger.
On OrderCancelled event → dispatch RefundOrderMessage → handler calls PaymentService.
## Consequences
- API response time remains < 200ms (refund not blocking)
- Need to handle refund failures asynchronously (retry + dead letter)
- Customer sees "refund initiated" immediately, actual refund may take time
- Need webhook from payment provider for refund completion status
## Alternatives Considered
1. Synchronous refund (rejected: too slow)
2. Cron-based batch refund (rejected: too much delay for customer)
JSON Schema для данных:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "CancelOrderRequest",
"description": "Request body for order cancellation",
"type": "object",
"properties": {
"reason": {
"type": "string",
"maxLength": 500,
"description": "Optional cancellation reason provided by customer"
}
},
"additionalProperties": false
}
Spec Gap: когда код расходится с intent
Что такое spec gap
Spec gap -- ситуация, когда реальное поведение кода не совпадает с тем, что написано в спецификации. В традиционной разработке это было нормой (документация устаревала). В SDD это -- баг.
Как возникает spec gap
Причина 1: Забыли обновить spec
Код изменён → Тесты обновлены → Spec не обновлена
Результат: spec описывает старое поведение
Причина 2: Hotfix без spec
Production баг → Быстрый фикс → Нет времени на spec
Результат: spec не знает о новом edge case
Причина 3: "Тихая" модификация AI
AI изменил поведение при рефакторинге → Тесты прошли
→ Но семантика изменилась (другая обработка edge case)
Результат: spec и код описывают разные вещи
Причина 4: Множественные участники
Developer A обновил spec → Developer B изменил код по старой spec
Результат: spec и код в разных ветках разошлись
Как обнаруживать и предотвращать spec gap
1. Contract tests (автоматическое обнаружение):
<?php
declare(strict_types=1);
/**
* These tests are auto-generated from OpenAPI spec.
* If spec changes → tests change → code must be updated.
* If code changes without spec → these tests catch the gap.
*/
final class OrderCancellationContractTest extends ApiTestCase
{
public function testSuccessfulCancellationMatchesSpec(): void
{
// Arrange: create order confirmed 10 minutes ago
$order = $this->createConfirmedOrder(minutesAgo: 10);
// Act
$response = $this->client->request('POST', "/api/v1/orders/{$order->getId()}/cancel", [
'json' => ['reason' => 'Test cancellation'],
]);
// Assert: response matches OpenAPI spec
$this->assertResponseStatusCodeSame(200);
$this->assertResponseMatchesJsonSchema('schemas/CancelledOrder.json');
// Assert: specific fields from spec
$data = $response->toArray();
$this->assertEquals('cancelled', $data['status']);
$this->assertArrayHasKey('cancelled_at', $data);
$this->assertArrayHasKey('refund_status', $data);
$this->assertEquals('initiated', $data['refund_status']);
}
public function testExpiredWindowMatchesSpec(): void
{
$order = $this->createConfirmedOrder(minutesAgo: 45);
$response = $this->client->request('POST', "/api/v1/orders/{$order->getId()}/cancel");
// Spec says: 409 with specific error message
$this->assertResponseStatusCodeSame(409);
$data = $response->toArray(false);
$this->assertStringContainsString('Cancellation window has passed', $data['error']);
}
}
2. PR checklist (процессное предотвращение):
## PR Checklist
- [ ] Tests pass
- [ ] Linter clean
- [ ] **Spec updated if behavior changed**
- [ ] **Contract tests pass**
- [ ] Security review (if auth/data changes)
3. Spec audit (периодическое обнаружение):
spec_audit:
frequency: "Monthly"
process:
1: "List all specs in specs/ directory"
2: "For each spec, verify code matches current behavior"
3: "Run contract tests"
4: "Flag any discrepancies"
5: "Create tasks to fix spec gaps"
owner: "Tech Lead"
output: "Spec Gap Report in monthly engineering review"
Будущее: intent-first development
Парадигма "intent as source of truth" -- это не конечная точка, а начало более глубоких изменений:
Сегодня (2026):
Человек пишет spec → AI генерирует код → Человек ревьюит
Завтра (2027-2028):
Человек описывает intent → AI генерирует spec + код + тесты →
Человек ревьюит spec (не код!)
Послезавтра (2029+):
Человек описывает бизнес-цель →
AI декомпозирует на features → генерирует specs →
генерирует код → запускает тесты → деплоит →
Человек мониторит бизнес-метрики
На каждом этапе роль человека поднимается всё выше по уровню абстракции. Но intent всегда остаётся за человеком -- потому что только человек знает, какую бизнес-проблему нужно решить.
Ключевые выводы
-
Код -- это артефакт реализации, а не источник истины. Intent (спецификация) первичен, код вторичен. Код можно перегенерировать, intent -- нет.
-
Intent состоит из конкретных артефактов. User stories с acceptance criteria, API contracts, state machines, decision tables, примеры и контрпримеры -- это не абстракция, а практические документы.
-
Спецификация -- это диалог. Человек начинает, AI уточняет, человек подтверждает. Каждая итерация повышает precision intent-а.
-
Трассировка обязательна. Каждое требование должно быть связано с кодом и тестами. Без трассировки spec gap неизбежен.
-
Spec gap -- это баг. В SDD расхождение кода и спецификации -- такой же серьёзный баг, как падение теста. Contract tests помогают его обнаружить автоматически.
-
Роль разработчика повышается, а не понижается. Вместо реализации деталей разработчик формулирует intent, проектирует архитектуру и принимает решения. Это более ценная и более сложная работа.
"Specification is a love letter to the future developer." Спецификация -- это послание будущему разработчику (или AI), который будет поддерживать этот код. В отличие от кода, спецификация написана на языке намерений, а не на языке машины.