炮轰SDD:Spec驱动开发为何不适合绝大多数项目

13 阅读21分钟

本文讨论的是当下 AI 编程语境中的 Specification-Driven Development(SDD,规格驱动开发) :先把需求和约束写成 Spec,再让 Coding Agent 据此规划、实现和验证代码。

SDD 是什么,它为什么突然火了

早期使用 AI 编程时,人们习惯直接在聊天框里说一句“给登录页加个验证码”,然后让模型开始改代码。这种方式很快,也很刺激,因此被称为 Vibe Coding。但项目稍微复杂一点,问题就出现了:模型不知道哪些行为不能破坏,不记得十轮之前确认过什么,也分不清一句需求是在描述业务目标还是暗示技术方案。它可能飞快地交付一套完整实现,只是实现了错误的东西。

SDD 正是针对这种失控感出现的。它的基本主张并不复杂:不要让 Agent 收到一句模糊指令就立刻写代码,先把要做什么写清楚。 一套常见流程大致如下:

  1. 把产品目标、用户场景、需求边界和验收条件整理成 Spec。
  2. 让 Agent 根据 Spec 调研代码库并生成技术设计。
  3. 把设计拆成可执行的任务列表。
  4. 由 Agent 逐项实现,再根据 Spec 检查结果是否符合预期。
  5. 某些流派在交付后继续保留 Spec,后续功能变化时先更新 Spec,再修改代码。

不同工具给这些文件起了不同名字,但结构大同小异。Kiro 常见的是 requirements.md → design.md → tasks.md;GitHub spec-kit 会生成 Spec、计划、研究和任务等一组文件;OpenSpec 则用 proposal、delta spec、design、tasks 组织一次变更,并在归档时把增量需求合并进长期 Spec。

它的出发点是合理的。Coding Agent 的能力越强,理解偏差的破坏力越大。人手写错十行代码,评审时往往能看出来;Agent 在错误前提上一次生成几十个文件,表面上却可能结构完整、测试齐全。先明确目标、约束和验收条件,可以让人和模型在付出实现成本之前发现分歧。

SDD 也顺应了一个很有吸引力的想象:既然大模型已经能把自然语言翻译成代码,开发者是否可以从代码细节中进一步抽身,转而维护更高层的业务意图?Spec 成为人和 AI 的共同语言,代码退化为可生成的“最后一公里”。需求变了就改 Spec,Agent 自动传播到设计、任务、测试和实现。开发不再围绕代码展开,而是围绕意图展开。

这套叙事击中了 AI 编程的几个真实痛点:聊天上下文会丢失,模糊 Prompt 会产生随机结果,复杂任务需要拆解,多人协作需要评审,Agent 的实现需要验收。GitHub spec-kit、AWS Kiro、OpenSpec、Tessl 等工具相继出现后,SDD 很快从一种提示词组织方式,被包装成了 AI 时代的新开发范式。

问题也恰好出在这里。 “编码前把需求写清楚”是一个朴素而正确的建议,但它并不能自然推出“Spec 应该成为长期事实来源”,更不能推出“多数项目都需要一套 SDD 工作流”。 从一份任务说明,跨越到一套需要持续维护的平行知识系统,中间隔着所有传统软件工程都没能轻易解决的问题。

每隔一段时间,软件行业都会重新发明一次“只要把需求写得足够清楚,代码就会自然出现”。以前叫 CASE、MDD、DSL、低代码,现在叫 Spec-Driven Development。新故事里的关键角色从确定性代码生成器换成了大模型,旧问题却一个没少:谁来保证描述正确,谁来维护描述与实现的一致,需求变化后谁负责同步,以及这份描述究竟给谁看。

SDD 最诱人的承诺,是把 Spec 设为“单一事实来源”,再让 Coding Agent 按 Spec 生成代码。听起来像是消除了需求和实现之间的鸿沟。实际情况恰恰相反:自然语言经过一个非确定性模型变成代码,中间的解释过程更长、更隐蔽,也更难复现。SDD 没有消灭鸿沟,只是给鸿沟铺了一层 Markdown,让人误以为下面已经是路。

我的判断很直接:Spec-first 在部分任务中有价值;Spec-anchored 需要极强的治理能力;Spec-as-source 对多数业务项目仍接近一场昂贵的信仰实验。把三者统称为 SDD,再把最理想化的收益宣传给所有项目,是问题所在。

一、SDD 连自己是什么都没说清楚

Birgitta Böckeler 是 Thoughtworks 的杰出工程师,拥有二十多年的软件开发、架构与技术领导经验。2025 年 10 月,她在 Martin Fowler 的个人网站发表《Understanding Spec-Driven-Development: Kiro, spec-kit, and Tessl》,结合对 Kiro、spec-kit 和 Tessl 的实际试用,对 SDD 的定义与现实适用性提出了系统质疑。她把当前所谓 SDD 分成了三层:

  1. Spec-first:编码前先写一份当前任务的 Spec,任务结束后可以删除。
  2. Spec-anchored:Spec 在功能交付后继续保留,后续演进持续更新。
  3. Spec-as-source:Spec 才是主要源文件,人只修改 Spec,代码只是生成物。

这三层看起来只是程度不同,实际上是三种完全不同的工程契约。

Spec-first 是一种上下文组织技巧,类似一份结构清楚的长提示词。Spec-anchored 是一套长期文档治理制度。Spec-as-source 则接近模型驱动开发和生成式编程,要求团队改变代码所有权、调试方式、评审对象和发布流程。前者可以在一个下午试用,后两者却会引入长期治理成本。

可 SDD 的传播经常把它们揉在一起:用 Spec-first 的低门槛吸引团队,再用 Spec-as-source 的宏大收益证明价值。今天说“不过是先把需求写清楚”,明天就说“以后维护软件就是维护 Spec”。这不是渐进式采用,而是概念滑坡。

甚至 “Spec” 本身也没有稳定定义。它可能是用户故事、验收标准、PRD、技术设计、接口契约、任务清单、领域规则,或者这些东西的混合物。一个词同时装下“为什么做、做什么、怎么做、如何验证、实施到哪一步”,得到的不是统一语言,而是一只没有所有者的文档泥潭。

一个合格的工程产物至少要回答这些问题:

  • 谁编写,谁审批,谁维护?
  • 它描述意图、当前行为,还是目标行为?
  • 它对人、对模型,还是对自动化工具负责?
  • 与代码、测试、PRD、ADR 冲突时,谁有裁决权?
  • 哪类变更必须更新它,哪类变更不需要?
  • 它什么时候失效,什么时候应该删除?
  • 如何证明它是完整、最新且内部一致的?

如果这些问题没有答案,“Spec 是单一事实来源”就不是工程规则,只是一句品牌口号。

二、它卡在上下文工程与传统产出物之间,两边都不讨好

在 coding agent 出现之后,代码库中新增的内容大部分是为上下文工程服务的,之前的内容我称之为传统产出物。

如果 Spec 是上下文工程,它本质上仍是提示词

一份只服务于当前 Coding Agent、在任务完成后即可丢弃的 Spec,本质上就是经过模板化、版本化的 Prompt。结构化当然有价值:它能固定目标、约束、验收标准和未决问题,也比散落在十几轮对话中的要求更容易审查。

但请不要把格式上的庄重误认为方法论上的突破。给提示词加上 requirements.mddesign.mdtasks.md 三个文件,并不会自动提升其中事实的可信度;把 “prompt” 命名为 “spec”,也不会让模型突然获得业务判断力。

在这个层面,planning-with-files 往往更诚实,也更实用。task_plan.md 回答接下来做什么,findings.md 保存带证据的发现,progress.md 记录已经做了什么。它们明确是任务状态,不冒充系统的永久真相;任务结束后可以归档或删除,也不要求团队为整个代码库建立第二套模型。

这种做法尤其适合历史项目,因为调查结论会随着证据变化。计划可以改,候选假设可以被推翻,错误上下文可以丢弃。它承认模型正在工作,而不是假装模型已经掌握了需求。

当然,planning-with-files 也不是 SDD 的万能替代品。它解决的是 Agent 的工作记忆和长任务恢复,不负责跨团队需求确认、合规审计或稳定接口契约。计划文件同样可能漏更、过时甚至污染后续上下文;它胜出的地方不是天然更正确,也不是简单的“文件更少”,而是职责更窄、生命周期更清楚、退出机制更明确。即使腐化,影响半径通常也被限制在一个任务内。

如果 Spec 是传统研发产出物,它多半只是 PRD 的再包装

如果一份 Spec 要长期存在、供产品与研发共同评审、定义业务行为和验收标准,那么它已经进入 PRD 或需求规格说明书的职责范围。如果它记录技术方案和关键取舍,那是设计文档或 ADR;如果它约束接口,那是 OpenAPI、Protobuf、JSON Schema 或类型定义;如果它定义可验证行为,那是自动化测试或契约测试。

SDD 并没有凭空创造一种缺失的产物。它更常做的是把这些已有产物重新打包,再额外要求一份 Agent 友好的 Markdown 投影。

于是问题来了:这份 Spec 到底给谁看?

  • 给人看,就应该短、稳定、聚焦决策,不能堆满模型生成的实现细节。
  • 给模型看,就要显式、冗余、上下文自足,往往会变得又长又机械。
  • 给机器执行,就必须有形式化语法、校验器和确定性语义,自然语言又不够可靠。

试图用同一份 Markdown 同时服务三类读者,通常只能得到三边都将就的产物。没有明确读者的文档,不是共享知识,而是共享负担。

三、“单一事实来源”最容易制造多个事实来源

一个成熟代码库已经存在许多知识载体:PRD 说明产品意图,ADR 记录架构决策,代码体现当前实现,测试保护部分行为,Schema 约束边界,监控和日志反映运行事实,Issue 与 Git 历史解释变化原因。

SDD 再加入一套长期 Spec 后,并没有自动替代上述任何一项。现实更可能从六份材料变成七份材料。每次变更都要判断更新哪些文件,并承担漏更其中一个的风险。

所谓“一致性”也不能靠 Agent 自动维护来解决,因为这会形成一个循环论证:

  1. 我们需要 Spec,是因为代码和需求之间存在理解鸿沟。
  2. 手工维护的 Spec 容易与代码失同步,因此有人希望让 Agent 自动生成或维护它。
  3. 但如果 Agent 同时负责理解代码、维护 Spec、再依据 Spec 修改代码,那么 Spec 只是 Agent 自己判断的中间产物,不能作为独立证据。

一旦第一轮理解有误,错误就会在 Spec 与代码之间自我强化。文档未必是独立证据,也可能只是模型上一次推断的沉淀;下一轮模型却会把它当成高优先级指令。普通的过时文档可能被工程师怀疑,挂着“Source of Truth”牌子的过时 Spec 反而更危险。

SDD 的标准回答是“人要逐阶段审查”。这当然必要,却也恰好暴露了成本并未消失:模型负责批量生产文档,人负责逐条验证事实。生成变便宜后,审查反而成为瓶颈。如果团队原本就没有能力持续维护 PRD、设计文档和测试,再新增一个要求更高频同步的 Spec 层,不会凭空长出治理能力。

这就是上下文腐化:错误信息因为被反复引用而获得权威,模型在错误前提上生成越来越完整、越来越自洽的实现。代码至少还能被编译器、类型系统、测试和线上行为反驳;自然语言 Spec 往往连“完整”都无法被机器判定。

SDD 常把自然语言 Spec 称为“可执行规格”,这个说法也值得警惕。只要从 Spec 到代码仍要经过概率模型,它就不是传统意义上的可执行规格,而是可再次解释的规格。同一输入可能生成不同实现,遗漏同一约束,甚至在不同上下文中给出相反选择。真正可执行的部分,最终仍然来自测试、Schema、编译器、模型检查器和运行时验证,而不是 Markdown 的标题层级。

四、历史项目不是 SDD 的沃土,而是它最危险的压力测试

历史项目的难点从来不是“没有一份足够长的说明”,而是现存材料互相冲突。代码可能实现了旧需求,测试可能保护偶然行为,注释可能三年没更新,线上配置可能改变实际路径,老同事记得的则是某次事故后的例外规则。

这时让 Agent 反向生成 Spec,首先碰到一个无法靠语法解决的问题:它生成的是业务意图,还是当前行为

从代码反推,只能得到当前实现的近似描述,无法判断其中哪些是业务规则,哪些是 Bug,哪些是临时兼容,哪些是无人敢删的死分支。把这些内容整理成格式漂亮的 Spec,很容易完成一次“认知洗白”:原本值得怀疑的历史偶然,被重新命名为正式需求。

如果要求先补齐整个项目的 Spec,成本接近重新理解一遍系统。对于几十万行代码、多个上下游、复杂开关和隐含运营规则,这不是“补文档”,而是一场没有明确回报的数字考古。更糟的是,补齐那天就是它开始过期的第一天。

这里也需要公允地修正一个常见批评:当前 OpenSpec 明确不要求为历史代码一次性补齐全部 Spec。它主张从正在发生的变更开始写 delta,归档时再把增量合并到 openspec/specs/,让主 Spec 随改动逐步生长。它甚至直接警告,大规模回填会产生没人信任的过时 Spec。

这比“先给遗留系统写完百科全书再开工”现实得多,但没有消除核心矛盾,只是改变了矛盾的形态:

  • 主 Spec 被称为“当前行为的事实来源”,实际却只覆盖被变更触碰过的切片。
  • 同一领域中,一部分行为有 Spec,一部分只能读代码,使用者很难知道缺失意味着“未覆盖”还是“不允许”。
  • delta 合并可以减少编辑冲突,不能证明基线理解正确,更不能保证实现、测试和主 Spec 同步。
  • 随时间自然生长的 Spec 不是天然完整,只是天然不均匀;热点模块越来越详细,沉默但关键的旧逻辑仍然是黑箱。

所以,OpenSpec 的 delta-first 是一种不错的变更组织策略,却不足以证明长期统一 Spec 可以成为事实来源。局部增量解决了录入成本,没有解决权威性、覆盖率和真实性。

历史项目真正需要的是证据工程:先固定业务目标,再沿生产、传递、消费和用户可观察行为追链路;用运行结果、日志、Git 历史、现有测试与 Owner 确认交叉验证。调查结论在得到证据前应该保持临时身份,而不是急着晋升为 Spec。

五、新项目也未必适合:不确定性最高时,文档最容易贬值

SDD 的演示偏爱新项目,因为从零生成 Todo App 最容易显得流程完整。现实中的新项目往往处于另一种状态:问题尚未找准,用户反馈不足,技术路线未验证,原型随时推翻,产品决策每周都在变化。

此时最重要的是低成本试错,不是尽早生产一套看似完整的需求、设计与任务文档。模型尤其擅长把模糊想法扩写成完整文本,这种能力很容易制造“已经想清楚”的错觉。十六条验收标准不代表需求更成熟,可能只代表模型更能写。

如果一个决策下周大概率变化,今天为它写 proposal、spec、design、tasks,再在实现后同步主 Spec,得到的不是严谨,而是文档周转税。团队最终会走向两个结局之一:认真维护,速度被流程拖住;停止维护,Spec 迅速失去信用。

这也是 SDD 的尴尬区间:

  • 任务很小,完整流程是拿大锤砸坚果。
  • 任务很大但问题不清楚,Spec 只是在规模化记录猜测。
  • 任务清楚且边界稳定,传统 PRD、ADR、契约测试和实现计划通常已经够用。

SDD 当然存在适用区域,但这个区域远比宣传口径窄。

六、更多 Markdown 不等于更多控制

SDD 工具喜欢用模板、检查表、阶段和追踪关系制造秩序感。问题在于,文件数量增加的是可见产出,不一定是有效控制。

每一份 AI 生成的文档都需要人审。审查者不仅要判断它写得通不通,还要核对它是否忠于业务、是否重复已有能力、是否与代码事实一致。阅读一份流畅但暗含错误前提的长文,往往比审查一段可运行的代码更累,因为自然语言没有编译错误,只有被人漏掉的歧义。

Birgitta Böckeler 在试用 Kiro 与 spec-kit 后给出的评价相当直接:它们都不适合多数真实编码问题;小 Bug 被扩写成四个用户故事和十六条验收标准;一个普通功能产生大量重复 Markdown,审查这些文件的时间足以直接完成实现。她还观察到 Agent 把“现有类的调研记录”误当成新需求,再生成了一批重复类。

这正说明了一个反直觉事实:上下文越正式,不代表模型越听话;上下文越多,也不代表模型越理解。 大窗口提高了可输入的信息量,没有提高每条信息的真实性,也没有保证模型会正确处理冲突。模板可以规范输出形状,不能替代事实调查。

SDD 最危险的副作用不是写了几份没人看的文档,而是制造虚假的掌控感。团队看到了完整目录、勾选框和需求追踪矩阵,于是误以为风险已经被管理。实际上,风险可能只是从代码里搬进了更难验证的自然语言。

七、真正值得长期维护的,不是“统一 Spec”,而是少量高价值事实

软件项目确实存在一些不应随任务任意漂移的内容:架构边界、数据所有权、兼容承诺、安全约束、合规要求、不可逆技术选择。这些内容应该被有意维护,但不该混进一份无限膨胀的统一 Spec。

更稳妥的做法,是按生命周期和验证方式拆分知识:

内容合适载体原因
当前任务目标、计划、发现、进度planning-with-files 或同类任务文件服务当前执行,可恢复、可删除,不冒充永久真相
产品目标与验收边界PRD、Issue、用户故事有明确业务 Owner,允许随产品决策演进
重要且长期的技术决策ADR记录背景、选择、代价和替代方案,强调“为什么”
API、数据与跨服务契约OpenAPI、Protobuf、Schema、类型可被工具解析、校验和生成
稳定业务行为自动化测试、契约测试、示例能够执行,失败时给出明确反馈
当前实现与真实调用链代码、运行结果、日志、Tracing用于证明系统现在实际做了什么
临时假设与探索结论对话、草稿、findings.md在证据不足时保留可撤销性

ADR 尤其重要,因为大模型对业务的理解有限,而许多关键决策无法仅从代码恢复。代码可以告诉后来者“现在用了事件驱动”,却未必能告诉他“同步方案曾因跨地域超时被否决”;这类原因值得以高写入门槛长期保存。

反过来,能从代码、类型或生成配置稳定恢复的信息,不必再手抄一遍。文档的价值不在覆盖面积,而在保存那些重要、稳定、无法从现状可靠推导的知识。

八、什么时候可以用 SDD

批判 SDD 的滥用,不等于任何 Spec 都不该写。下面几类场景可能值得采用轻量或严格程度不同的 SDD:

  • 需求边界已经较稳定,失败代价高,必须在编码前对齐验收条件。
  • 多团队、跨仓库协作,需要共同审查某项行为契约。
  • 受监管、审计或安全要求约束,需要保存意图到验证结果的追踪链。
  • 同一稳定规格需要生成多种实现,并且拥有强测试、确定性校验和再生成机制。
  • 大型变更涉及大量步骤,任务上下文确实超出一次对话可可靠承载的范围。

即使在这些场景,也应该先回答三个问题:这份 Spec 的唯一职责是什么,谁负责维护,它如何被验证。回答不出来,就先不要创建。

一个简单的采用原则是:短期信息进入任务上下文,长期决策进入 ADR,可执行约束进入测试和 Schema,产品意图留给有 Owner 的需求系统。不要因为 AI 喜欢 Markdown,就让 Markdown 接管软件工程。

结论:SDD 最大的问题,是把“写清楚”偷换成了“写完整”

写清楚当然重要,但清楚不等于冗长,结构化不等于真实,版本化不等于有人维护,“Source of Truth”也不等于事实真的住在那里。

对多数项目,尤其是历史项目,SDD 很容易变成一项文档扩张运动:把 Prompt 写成 Spec,把 Spec 当成 PRD,把 PRD 当成代码的上级,再让 Agent 负责维护它们之间的一致。最后团队多维护了一套没人能说清读者、边界和权威性的文档,而 Agent 得到了一份比普通旧文档更有指令权的旧文档。

这不是上下文工程的胜利,而是上下文腐化的制度化。

好的上下文工程追求的是:当前任务需要什么事实,证据在哪里,哪些结论仍可撤销,什么时候应该忘掉。好的传统工程产出物追求的是:谁拥有它,服务什么决策,如何验证,何时失效。SDD 如果不能回答这些问题,就只是夹在两者之间的一层昂贵介质。

Spec 可以是工具,但不该成为宗教。对历史项目而言,先调查事实,再记录决策;先验证行为,再谈统一真相。否则你得到的不是 specification-driven development,而是 specification-driven delusion。

参考资料

  1. Birgitta Böckeler, Understanding Spec-Driven-Development: Kiro, spec-kit, and Tessl, 2025-10-15。
  2. GitHub spec-kit, Specification-Driven Development
  3. OpenSpec, Core Concepts
  4. OpenSpec, Using OpenSpec in an Existing Project