规范驱动开发:AI 时代的软件工程第一性原理

0 阅读11分钟

规范驱动开发: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.mdWHAT & WHY(需求规范,与技术解耦)
    ↓
design.mdHOW(技术方案、架构、接口、数据流)
    ↓
tasks.mdWHEN & 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 方法论要求你在动手之前,先精确定义:

  1. C(X) :什么输入/状态会触发这个 Bug?
  2. 期望行为:正确的系统应该产生什么输出?
  3. 保留行为:哪些现有行为不能被破坏?

以项目中的一个真实 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 返回 true2. 立即系统暂停当前正在上传的文件
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 CodingSpec-Driven Development
输入模糊的自然语言结构化的规范文档
AI 角色猜测意图,生成代码编译规范,执行任务
人类角色评判输出,反复纠正定义规范,审核结果
质量保证依赖 AI 的"运气"依赖规范的完整性
可维护性代码是真理,文档腐烂规范是真理,代码是派生
扩展性每次都从头开始规范可复用,可演进

这个转变的本质是:从"让 AI 帮我写代码",到"我定义规范,AI 编译规范"。

前者把 AI 当成一个更快的键盘;后者把 AI 当成一个更强的编译器。


八、落地建议

从小处开始

不需要一开始就建立完整的三层文档体系。可以从最简单的形式开始:

  1. 在开始写代码之前,先用 10 分钟写一段"验收标准"——这个功能在什么条件下算做好了?
  2. 把这段验收标准给 AI 看,让它先确认理解,再开始实现
  3. 实现完成后,用验收标准检验结果

这就是 SDD 的最小可行实践。

逐步结构化

随着项目复杂度增加,逐步引入更多结构:

  • 对重要需求,维护独立的 requirements.md
  • 对复杂功能,在动手前写 design.md
  • 对大型任务,拆分成 tasks.md 逐步执行

把规范当成资产

规范文档不是一次性的,它是可以复用、可以演进的资产:

  • 新功能可以引用已有的规范片段
  • Bug 修复可以更新对应的规范
  • 重构可以基于同一份规范生成新实现

规范的积累,就是团队 AI 协作能力的积累。


九、结语

SDD 提供的是人机在软件工程里的协作接口:人负责意图与规范的设计和演进,AI 负责把规范编译成实现并验证。

这个接口的质量,决定了 AI 能力的发挥上限。

模糊的输入,只能得到模糊的输出。结构化的规范,才能得到可预期的实现。

把"规范驱动"当成第一性原理,在需求澄清、技术设计和任务分解上持续沉淀可被机器理解的规范——这是从"AI 工具使用者"走向"AI 工作流指挥者"的一条可执行路径。


*参考资料: