规范驱动开发:AI 时代的软件工程第一性原理
从 Vibe Coding 到 Spec-Driven Development,从"让 AI 猜"到"让 AI 编译"
一、问题的起点:AI 为什么会"猜错"
大模型写代码的能力已经毋庸置疑。但在实际工程中,我们经常遇到一个令人沮丧的现象:
你给 AI 一段模糊的需求描述,它生成了一大段"看起来合理"的代码,但跑起来不对,或者方向完全偏了。你再补充说明,它又生成了另一段,依然差一点。来回几轮,你花在"纠正 AI"上的时间,比自己写还多。
这不是模型能力的问题,而是信息结构的问题。
大模型本质上是一个条件概率机器:给定上下文,预测最可能的下一个 token。当你的输入是模糊的自然语言时,它只能用统计概率去"猜"你的意图——而猜,就意味着不确定性。
规范驱动开发(Spec-Driven Development,SDD) 要解决的,正是这个问题的根源:如何把人类模糊的意图,转化为 AI 可以精确执行的结构化规范。
二、SDD 的核心思想:权力反转
传统软件开发里,意图和实现之间有一条漫长的翻译链:
产品构想 → PRD → 人脑编译 → 技术设计 → 人脑编译 → 代码
每一跳都有信息损耗。文档写完就开始腐烂,代码才是被默认的"真理"。
SDD 做的是权力反转:
- 规范(Specification)成为唯一的真理之源(Single Source of Truth)
- 代码降级为"规范在某一技术栈下的编译产物"
- 维护软件 = 演进规范;修 Bug = 修正产生错误行为的规范或方案;重构 = 基于同一份规范生成另一套实现
不是文档服务代码,而是代码服务规范。
AI 在这个框架里扮演的角色,是多阶段编译器——把高层、模糊的人类意图,一步步编译成低层、精确、可执行的指令(含代码)。
三、三层文档结构:意图的逐步具象化
SDD 的核心工作流,是把一个粗糙的想法,通过三个层次的文档逐步具象化:
模糊意图
↓
requirements.md ← WHAT & WHY(需求规范,与技术解耦)
↓
design.md ← HOW(技术方案、架构、接口、数据流)
↓
tasks.md ← WHEN & WHO(原子任务列表,带依赖关系)
↓
代码 / 测试 / 报告 ← 规范的编译产物
每一层都有明确的职责边界:
| 层次 | 核心问题 | 典型内容 | 受众 |
|---|---|---|---|
| requirements.md | 要做什么,为什么做 | 用户故事、验收标准、边界条件 | 产品、开发、AI |
| design.md | 怎么做,用什么做 | 架构图、接口定义、数据流、正确性属性 | 开发、AI |
| tasks.md | 分几步做,先做什么 | 原子任务、依赖关系、完成标准 | 开发、AI |
这个结构不是新发明——它和传统的 PRD + 技术方案 + 排期在形式上相似。SDD 的真正创新在于:这些文档是为 AI 设计的,而不只是为人类设计的。
真实案例:视频缩略图悬停效果
以项目中的一个真实需求为例。需求是"把缩略图条改成胶片卷轴式悬停效果",requirements.md 里的一条验收标准是这样写的:
WHEN 鼠标悬停在某个 Thumbnail_Item 上超过 150ms 时,
THE Frame_Picker_Dialog SHALL 加载该缩略图对应时间戳的高清帧
并更新 Cropper_Preview
注意这里的语言模式:WHEN ... THE ... SHALL ...。这不是随意的写法,而是刻意的结构化——它让 AI 能够精确理解"触发条件"、"作用对象"和"期望行为",而不需要去猜。
四、Bug 修复的 SDD 视角:Bug Condition 方法论
SDD 在 Bug 修复场景下有一个特别有价值的思维框架:Bug Condition(C) 。
传统的 Bug 修复流程往往是:复现 → 猜原因 → 改代码 → 测试。这个流程的问题在于,"猜原因"这一步非常依赖个人经验,而且容易引入新的问题(regression)。
Bug Condition 方法论要求你在动手之前,先精确定义:
- C(X) :什么输入/状态会触发这个 Bug?
- 期望行为:正确的系统应该产生什么输出?
- 保留行为:哪些现有行为不能被破坏?
以项目中的一个真实 Bug 为例——批量视频上传时,用户手动暂停某个稿件后点击恢复,恢复按钮是灰色的,无法点击。
Bug Condition 定义:
FUNCTION isBugCondition(input)
INPUT: input = { action: 'resume', targetUploadId: string }
OUTPUT: boolean
RETURN UploadVideoGlobal.getCurrentUploadingId() !== null
AND targetUploadId 对应的 upload-card 处于 isPaused=true 状态
END FUNCTION
这个定义的价值在于:它把一个模糊的"恢复按钮有时不能点",变成了一个精确的、可测试的条件。AI 拿到这个定义,就能:
- 精确定位受影响的代码路径
- 生成针对 Bug Condition 的探索性测试(先验证 Bug 确实存在)
- 生成修复后的验证测试(Fix Checking)
- 生成回归测试(Preservation Checking)
规范先行,代码后行。 这是 SDD 在 Bug 修复场景下的核心价值。
五、正确性属性:给 AI 一个可验证的目标
SDD 里有一个概念值得单独讨论:Correctness Properties(正确性属性) 。
传统测试往往是"举例子"——给定输入 A,期望输出 B。这种方式的覆盖率天花板很低,因为你只能测到你想到的情况。
正确性属性是另一种思路:描述系统在所有可能输入下都应该满足的不变量。
还是以上传 Bug 为例,design.md 里的正确性属性是这样写的:
Property 1: Bug Condition - 恢复操作立即抢占当前上传
For any 处于 isPaused=true 状态的稿件,当用户点击恢复时
(无论 currentUploadingId 是否为 null),固定后的系统 SHALL:
1. 允许恢复按钮可点击(canInteract 返回 true)
2. 立即系统暂停当前正在上传的文件
3. 将被系统暂停的文件重新插入 pendingQueue 队首
4. 立即开始上传被恢复的稿件(不经过队列等待)
Property 2: Preservation - 非恢复路径行为不变
For any 不满足 bug condition 的输入,固定后的系统 SHALL
产生与原系统完全相同的行为。
这两条属性,直接对应了属性测试(Property-Based Testing,PBT) 的测试逻辑:
- Property 1 → 生成所有满足 Bug Condition 的随机输入,验证修复后行为正确
- Property 2 → 生成所有不满足 Bug Condition 的随机输入,验证行为与原系统一致
PBT 的威力在于:它不是你手写几个用例,而是让测试框架自动生成成百上千个边界情况,系统性地探索你没想到的角落。规范定义了"什么是正确",PBT 验证了"实现是否正确"。
六、AI 层面的深层思考
6.1 规范是给 AI 的"机器语言"
自然语言对人类友好,但对 AI 不友好——它充满歧义、隐含假设和上下文依赖。
SDD 的三层文档结构,本质上是在把自然语言逐步编译成"AI 可以精确执行的指令集":
- requirements.md 消除了业务歧义(WHAT 是确定的)
- design.md 消除了技术歧义(HOW 是确定的)
- tasks.md 消除了执行歧义(步骤和顺序是确定的)
当 AI 拿到一个 task,它不需要"猜"背后的意图,因为意图已经在 requirements 里;它不需要"猜"技术方案,因为方案已经在 design 里。它只需要执行。
规范即是在对 AI 编程,而不是在跟 AI 聊天。
6.2 上下文窗口的经济学
LLM 有一个物理限制:上下文窗口(Context Window)。即使是最新的模型,当上下文超过某个阈值后,注意力会衰减,"遗忘"早期信息,输出质量下降。
SDD 的分层结构天然解决了这个问题:
- 执行某个 task 时,只需要把该 task 的描述 + 相关 design 片段 + 当前涉及文件喂给 AI
- 不需要把整份 requirements + design + tasks 全部塞进上下文
- 每个 task 都是一个独立的、上下文受控的执行单元
这是一种上下文的经济学:用结构化规范替代冗长的自然语言描述,在有限的 token 预算内,最大化 AI 的执行精度。
6.3 损耗左移:用思考成本置换返工成本
SDD 并不能从物理上消灭"失落的翻译"——从意图到实现,信息损耗是不可避免的。
它做的是把发现和修正损耗的时机前移:
- 在 requirements 阶段发现歧义,改的是几段 Markdown
- 在 design 阶段发现方案问题,改的是技术文档
- 等到代码写完再发现,改的是实现、测试和协作成本
- 等到上线后再发现,改的是用户体验和业务损失
用"思考的成本"置换"返工的成本" ——这是 SDD 在 AI 时代仍然成立的根本原因。
6.4 AI 会"作弊":规范与实现的隔离
有一个反直觉的风险值得警惕:让同一个 AI 既写实现又写测试,它会学会"通过测试"而不是"满足规范"。
这不是 AI 的恶意,而是优化目标的偏差——如果测试是 AI 自己写的,它可以生成一个"恰好通过自己测试"的实现,而不是一个"真正满足规范"的实现。
SDD 的解法是:规范(requirements + design)由人类主导定义,测试由规范派生,实现由测试约束。 三者之间有明确的单向依赖关系,而不是循环依赖。
正确性属性 + PBT 的组合,让测试的覆盖范围超出了 AI 能"预见"的范围,从而有效防止这种"作弊"。
6.5 单一真理源与活文档
当规范是驱动测试和代码的唯一源头时,一个长期困扰工程团队的问题被自然解决了:文档与代码不一致。
在 SDD 框架下:
- 规范即文档,文档即可执行清单
- 实现和测试都是规范的派生品
- 迭代时优先改规范,再让 AI 重新生成或回归测试
这形成了一个 Spec → Generate → Validate 的闭环,而不是在代码里到处打补丁。
七、从"工具使用者"到"工作流指挥者"
SDD 代表的,是一种对 AI 协作方式的根本性转变。
| 维度 | Vibe Coding | Spec-Driven Development |
|---|---|---|
| 输入 | 模糊的自然语言 | 结构化的规范文档 |
| AI 角色 | 猜测意图,生成代码 | 编译规范,执行任务 |
| 人类角色 | 评判输出,反复纠正 | 定义规范,审核结果 |
| 质量保证 | 依赖 AI 的"运气" | 依赖规范的完整性 |
| 可维护性 | 代码是真理,文档腐烂 | 规范是真理,代码是派生 |
| 扩展性 | 每次都从头开始 | 规范可复用,可演进 |
这个转变的本质是:从"让 AI 帮我写代码",到"我定义规范,AI 编译规范"。
前者把 AI 当成一个更快的键盘;后者把 AI 当成一个更强的编译器。
八、落地建议
从小处开始
不需要一开始就建立完整的三层文档体系。可以从最简单的形式开始:
- 在开始写代码之前,先用 10 分钟写一段"验收标准"——这个功能在什么条件下算做好了?
- 把这段验收标准给 AI 看,让它先确认理解,再开始实现
- 实现完成后,用验收标准检验结果
这就是 SDD 的最小可行实践。
逐步结构化
随着项目复杂度增加,逐步引入更多结构:
- 对重要需求,维护独立的 requirements.md
- 对复杂功能,在动手前写 design.md
- 对大型任务,拆分成 tasks.md 逐步执行
把规范当成资产
规范文档不是一次性的,它是可以复用、可以演进的资产:
- 新功能可以引用已有的规范片段
- Bug 修复可以更新对应的规范
- 重构可以基于同一份规范生成新实现
规范的积累,就是团队 AI 协作能力的积累。
九、结语
SDD 提供的是人机在软件工程里的协作接口:人负责意图与规范的设计和演进,AI 负责把规范编译成实现并验证。
这个接口的质量,决定了 AI 能力的发挥上限。
模糊的输入,只能得到模糊的输出。结构化的规范,才能得到可预期的实现。
把"规范驱动"当成第一性原理,在需求澄清、技术设计和任务分解上持续沉淀可被机器理解的规范——这是从"AI 工具使用者"走向"AI 工作流指挥者"的一条可执行路径。
*参考资料: