聊一个最近在技术社区里出现频率越来越高的词:Spec-Driven Development,中文一般叫规格驱动开发。我第一次看到这个概念时,第一反应是“这不就是 TDD 换了个马甲?”毕竟测试先行、行为驱动这些提法已经够多了。但真正在一个跨团队项目里把它当成一种工作流程去跑之后,我才发现这玩意儿和 TDD、BDD 的差别,比字面上看起来要大得多。它解决的已经不是“代码怎么写”的问题,而是“需求怎么传导、边界怎么对齐、验收怎么定义”的问题。
这篇文章不打算写成一版理论科普,更像是我自己从理解到落地的一次复盘。我会直接讲规格驱动到底在解决什么,一条完整的规格闭环该怎么跑,哪几种规格形态在实践中最常见,以及真正把它塞进日常开发流程时,哪些地方最容易翻车。
1. 先别急着写代码:规格驱动到底在解决什么问题
1.1 需求到代码之间的“断层”是怎么产生的
做过几年项目的人基本都见过这个现象:产品经理脑子里想的是一套规则,写进 PRD 的时候简了一部分,开发理解的时候又丢了一部分,测试写用例的时候再猜了一部分,最后上线后用户操作了一个没被任何人讨论过的边界场景,系统直接给出错误结果。
这不是某个人的态度问题,而是信息在多次转述中天然会发生衰减。传统的开发流程里,需求、设计、编码、测试分别由不同角色在不同时间点完成,每个环节的产物又是天然“不可执行”的文字描述。PRD 里写“库存扣减要以订单提交结果为准”,这行字不会被任何自动化工具验证。它只能靠人反复开会、反复问,最终能不能对齐,很大程度依赖运气。
规格驱动开发想改变的,就是这种靠默契和口头共识维持的协作方式。它的核心思路是:在需求和代码之间插入一层“规格层”。这层规格既不是纯自然语言文档,也不是散落的单元测试,而是用结构化方式描述的、可被自动化验证的契约。规格在代码编写之前先被团队评审,然后在编码后被机器检查。这样做的直接结果,是把“大家觉得应该是一样的”变成“大家确认过确实是一样的”。
1.2 Spec-Driven Development 和 TDD、BDD、契约测试的边界在哪
很多人会把规格驱动和这几个相邻概念搞混,我一开始也分不太清。为了讲清楚,我做了个比较表,直接说明它们关注的层级和产物有什么不同。
| 方法论 | 核心关注点 | 主要产物 | 验证时机 |
|---|---|---|---|
| TDD | 代码单元的输入输出与行为 | 单元测试用例 | 写实现之前先写测试,测试驱动实现 |
| BDD | 业务行为与用户可见价值 | Given-When-Then 场景 | 从行为描述驱动实现与验收 |
| 契约测试 | 服务/模块之间数据交换的一致性 | 消费者与提供者之间的契约文件 | 契约先定,双方按契约开发与验证 |
| Spec-Driven Development | 需求规格从描述到执行的全链路共识 | 可执行规格文件 | 规格先行,实现后持续回归验证 |
可以这样理解:TDD 回答的是“这段代码在某个输入下应该返回什么”;BDD 把关注点从代码单元上移到用户故事,但它更多是一种协作和表达方式;契约测试覆盖的又只是系统间接口这个局部。而规格驱动更像一个容纳层,它把行为要求、接口结构、业务不变量都收拢到一份份规格里,并让这些规格成为开发前评审的对象、开发后验证的依据。
一个更直观的区别是:在 TDD 里,测试写坏了可以改,因为它服务的是代码质量;在规格驱动里,规格是上下游共同确认过的“合同”,一旦发现实现和规格不一致,第一反应不应是让规格迁就代码,而是先判断规格本身是否已经被业务确认过。如果确认过,说明实现有问题;如果没有确认过,说明规格该补流程,而不是偷偷绕过。
1.3 “规格”到底是什么
说到规格,很多人第一反应是“接口文档”或者“需求文档”。这有一点接近,但不完全一样。真正在规格驱动中起作用的规格,至少要满足三个特点:
- 结构化。它不能被写成一篇大散文,而是按功能、场景、规则、边界条件拆成块。人和机器都能比较清晰地定位某条规则在哪里。
- 可验证。规格里描述的内容要么能映射成自动化断言,要么能被评审者明确地判定通过或不通过。“建议友好提示”这种话不叫规格。
- 有唯一归属。每一条规格只能由一个团队或系统负责,否则跨团队时会出现“你以为是对方负责,对方以为是你负责”的真空地带。
同时满足这三点的规格,才有资格当需求到实现之间的“中间语言”。它不会替团队决定用什么数据库、什么框架,但它能把业务规则和系统边界钉死。代码可以在这些规则之内自由演进,但不能越过边界。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 一条完整的 Spec-Driven 闭环怎么跑起来
2.1 第一步:把需求拆成可讨论的规格,而不是写完 PRD 就传话
规格驱动落地的第一个动作,发生在代码仓库出现任何新提交之前。产品或业务方提出一个需求后,负责的开发者不是直接开始搭接口、建表,而是先和产品、测试一起把需求转成一份结构化规格草案。
我自己的习惯是先用 Given-When-Then 这种句式来描述用户可见的行为,因为它的结构能逼着我们把前置条件、触发动作、预期结果这三件事说清楚。很多需求之所以到后来扯皮,就是在“预期结果”上模棱两可。
比如一个典型的订单提交场景,可以初步写成这样:
text复制功能:提交订单后扣减库存
场景:下单成功且库存充足
当 买家提交订单
那么 系统返回下单成功
并且 库存数量减少对应商品数量
场景:下单成功但库存不足
当 买家提交订单,且商品库存小于购买数量
那么 系统返回库存不足的明确提示
并且 不生成有效订单
并且 库存数量不发生变化
这份描述的意义不在语法,而在于它把“库存不足时要不要生成订单”“提示信息是什么形态”“库存会不会被扣成负数”这些关键决策提前摆上了桌面。如果产品最初设想的是“库存不足也先生成订单,后台再处理”,那么在这个评审阶段就会暴露,而不是等代码写完才返工。
2.2 第二步:规格评审,要在编码之前把分歧全部清掉
规格草案出来后,先不要写实现,也不要直接用 Cucumber 之类框架生成自动化脚本就去跑。先组织一场规格评审会。注意,不是那种产品念文档、开发低头听的需求宣讲会,而是针对规格逐条过:前置条件全不全?分支覆盖得够不够?跨系统边界时,哪一方负责什么?异常路径的预期是不是业务真正想要的?
我在实践中发现,规格评审最容易爆出问题的地方,往往是那些“正常路径之外”的场景。正常流程大家都懂,但一旦进入“用户重复提交”“网络超时但服务端已处理”“上游返回了文档里没写的错误码”这些情况,团队往往会发现,这份规格其实还没想清楚。而这些问题在传统开发流程里,通常是测试在提测前才发现,甚至在线上用户投诉后才被发现。
规格评审结束后,这份规格才算有了“合同效力”。后续的实现、前后端联调、QA 用例编写,都围绕这份已经达成共识的规格展开。
2.3 第三步:让规格先“红”,实现再让它“绿”
规格评审通过后,才开始进入工程实现环节。这时候有一个关键步骤:把规格描述转成可执行验证。不同形态的规格对应不同工具,行为类可以用 Cucumber、SpecFlow 这类框架,契约类可以用 OpenAPI 加契约测试工具,规则类可以直接写成状态机或业务不变量断言。
转换过程中,刻意让测试先跑一遍,确认它们处于失败状态。这一步不是形式主义,它有实际价值:可以验证规格描述真的能被测试框架执行,而不是一句空话;同时也能避免后续出现“测试永远通过,但没人知道测试有没有真的断言”的假绿问题。测试先红再绿,我们才能确定驱动实现的那个用例确实有约束力。
之后就进入实现阶段。开发的任务不是把功能做出来再把测试凑绿,而是“实现到规格描述的行为为止”。规格之外的需求不主动做,规格覆盖不到的分支要在实现过程中发现并反馈回规格层,由相关人员重新讨论,而不是开发者自己拍脑袋决定。
2.4 第四步:合并与回归,让规格成为持续验证的基线
实现通过后,规格文件会和代码一起提交到仓库。后续的每次改动,只要涉及这块功能,CI 都会重新跑一遍全量规格。这样一来,规格和代码是同一个版本库里的产物,不会出现“代码已经改了三个月,规格文档还停在去年的状态”这种失衡。
我自己会把规格文件当作和源码同等重要的资产看待。代码在演进,规格也会被修正。修改规格不应该被禁止,但必须走和最初创建规格一样的评审流程。只有这样才能让规格始终保持“当前系统真正的行为”这一身份。如果哪天规格和代码不一致了,第一反应永远是去查哪一边脱离了团队共识,而不是默默改个文件把两边勉强对齐。
3. 规格不是一种:三类规格的形态与适用边界
3.1 行为规格:给用户故事加上“验收刻度”
最常用的一类规格是行为规格,它描述的是系统在某种条件下对外呈现的结果。之前订单提交的例子,就是典型的行为规格。它适合用来覆盖用户故事、业务规则、存在明确前置条件和分支判断的功能点。
行为规格在自动化层面通常落到端到端测试或集成测试上。但要注意,驱动开发时别把动作和断言写得过于具体,否则会变成 UI 测试,点击某个按钮、等待几秒、检查某段文案。这种规格维护成本高,而且很容易因为页面改版就大面积碎裂。更合适的方式是描述系统行为和状态变化,让实现层去决定通过 API 调用还是页面操作来触发。规格描述的是“什么条件下要发生什么”,而不是“用户在哪个按钮上点了多少下”。
3.2 数据契约:让两个服务之间不再靠“猜”
跨团队、跨服务协作中的大部分冲突,都出在数据结构上。后端觉得我返回 created_at 就行,前端以为是 createTime,联调时才发现,两边都觉得自己没做错。数据契约类规格,就是把请求和响应的格式先钉死,再让两端各自开发。
这种规格落地时,最典型的是 OpenAPI 描述文件。每个接口的路径、方法、参数、请求结构、响应结构都写在里面。JSON Schema 可以单独定义复杂对象结构,契约测试工具则用来保证消费者和服务提供者对同一份契约的理解不会漂移。
数据契约规格的独特之处在于,它不像单元测试那样只被一端持有。契约是两端共同认可的文件。消费者端基于契约做桩测试,提供者端基于契约做提供者验证。当一边想改接口结构时,他必须先改契约,再运行契约测试,看看是否会破坏另一边的预期。这样就把“接口变更”这个非常容易导致线上故障的动作,纳入了自动化的安全网。
3.3 不变量规格:把业务规则变成系统里不可破坏的底线
还有一类规则,很难用“用户输入后返回什么”来描述,因为它们横跨多个操作、多个实体。比如“已发货的订单不能再次修改收货地址”“取消订单后,优惠资格必须返还”“一个商品在特定时间段内最多只能参与一场促销”。这些规则分散在代码的不同地方,任何一个写入入口没做校验,都可能造成数据异常。
这种很适合写成不变量规格,表述为“无论通过哪个入口触发,系统的某种属性在所有时刻都必须成立”。落地时可以用状态机约束,也可以用业务领域层的校验函数来承载,再通过大量随机或边界测试来验证。
需要注意的是,不变量规格比行为规格更难设计,因为它要求你从一系列动作中抽象出稳定的属性。做得好的话价值巨大,能把大量“业务规则只存在于开发脑海里”的风险一次性消除。做得不好则会变成冗长的重复校验清单,维护起来特别痛苦。所以不要把系统的每一件事都塞进不变量,优先挑那些一旦被破坏就会产生严重连锁反应的核心规则。
| 规格类型 | 关注问题 | 典型承载 | 主要自动化方式 |
|---|---|---|---|
| 行为规格 | 某种条件下系统是否表现出预期行为 | Gherkin 场景、用户故事验收标准 | 集成测试、端到端测试 |
| 数据契约 | 服务间传递的数据结构是否一致 | OpenAPI、JSON Schema、契约文件 | 消费者驱动契约测试 |
| 不变量规格 | 核心业务规则是否在所有操作中都被遵守 | 状态机、业务规则对象、约束断言 | 单元测试、属性测试、审计日志核验 |
4. 把规格驱动真正放进工作流,而不是文档上打勾
4.1 规格文件放哪里,决定了它会不会腐烂
规格驱动落地时,第一个会被争论的问题通常是:规格写在哪里?是单独放在 Wiki、Confluence,还是做成独立文档站点?我的经验是,不要和代码仓库分开。规格文件最好直接放进与模块对应的源码目录中,和代码一起提交、一起评审、一起发布。
原因很简单:和代码分开的文档,天然会和代码失去同步。一旦规格存放在需要额外登录、需要手动操作才能更新的系统里,它很快就会变成没人维护的历史档案。而规格与代码同仓,能让每次变更都通过同一个 Pull Request 流程暴露出来。评审者在看代码改动时,也能看到对应规格是否同步更新。这会形成一种强制力:你改了行为,就必须改规格,否则评审这一关就过不去。
目录结构不用设计得太复杂,按业务模块而不是技术分层去组织就很有效果。比如 specs/order/、specs/inventory/、specs/shipping/,而不是 specs/backend/、specs/frontend/。规格描述的是业务边界和系统行为,按模块组织更便于团队找东西,也能让新人在进入项目时通过浏览 specs 快速了解这个系统到底规定了哪些行为。
4.2 评审“规格”和评审“代码”时,关注点完全不同
规格评审时,我建议重点看这几类问题:
- 分支覆盖是否完整。除了正常路径,有没有把异常路径、重复触发、部分失败的情况写清楚?
- 是否有关键概念没有定义。比如“下单成功”的定义是什么?是持久化成功就算,还是要等外部系统确认?
- 描述有没有过度约束实现。规格不应该规定用哪个缓存中间件,也不应该规定变量命名风格;它约束的是行为和结构。
- 跨系统边界时责任是否明确。团队 A 负责保证什么、团队 B 负责确认什么,责任必须落在具体一方。
审查规格时容易被忽视的一点是语言的精确性。开发者写代码时习惯把分支写得很明确,但一到规格描述里就容易变得模糊,像“系统应合理处理错误”这种话,机器根本没法验证,更没法作为自动化的基线。规格里每个断言都应具备可观察、可测量的特征,否则这条规格实际没有约束力。
4.3 别一刀切:不是所有模块都适合规格驱动
刚开始推广 Spec-Driven Development 时,最容易犯的错误就是希望所有项目立刻转型,所有功能都先写规格再写代码。这种全量铺开的做法,通常会在三个月内让团队累垮,规格文件变成沉重的文档负担,大家开始用模板套模板,反而磨灭了这套方法的真正价值。
更稳妥的路径是选取一小块边界清晰、跨团队协作明显、错误代价较高的区域做试点。什么样的模块最适合?我总结过几个特征:
- 涉及多个系统或团队,比如前端、后端、外部服务同时需要对齐;
- 业务规则复杂,分支多,之前的 Bug 或返工集中在正常路径以外的场景;
- 变更频率高,经常需要同时修改实现和验证逻辑;
- 一旦出错,影响范围很大,比如核心流程、资金相关、权限相关。
挑选出这样一块区域后,带着规格驱动的方式完整跑几个迭代,让团队成员真实感受到规格评审带来的沟通效率提升,再逐步扩大到更多模块。反过来,一些纯内部工具、原型验证、一次性脚本,真的不需要强制写规格,给这类代码加规格只是徒增成本。
4.4 规格驱动开发失败的五个常见信号
在实际项目中,规格驱动不能带来预期收益,一般不是方法本身的问题,而是执行方式变形了。我见过和经历过不少失败场景,这里总结几个高频信号:
- 规格写完就封存。需求评审时做了规格,但代码实现过程中再没回过这份规格,实现完也没有跑任何验证。规格成了一份提前写好的普通文档。
- 全篇是期望和建议,没有断言。通篇都是“应该”“可以考虑”“尽量保证”,无法被自动化验证,评审时大家也不会产生共识,因为每句话在不同人脑海里都有解释空间。
- 先写代码后补规格。虽然最后仓库里看起来既有代码又有规格,但规格实际是照着代码“翻译”出来的,根本没法暴露需求分歧,更没起到驱动作用。
- 规格越写越厚,没人愿意看。一个模块动辄上百条规格,其中大量在后文重复或没落到实现。规格一旦失去重点,就会变成噪音。
- 规格变更没有评审通道。开发过程中发现规格写错了,开发者直接改掉,没有同步给产品和测试。这个动作如果只发生一次,危害也许不大,但一旦成为习惯,规格就又重新变成了代码的附属品,失去了团队契约的意义。
5. 踩坑实录:关于规格驱动的几个关键经验
5.1 别把规格驱动当成“自动化测试升级版”
较早投入规格驱动时,团队容易陷入一种误区:既然规格要被自动执行,那就把所有用例都改写成自动化脚本,场景越全越好。慢慢地团队发现,一套端到端自动化脚本跑一次要二十分钟,一个业务状态的改动会让几十个场景同时失败,表面上覆盖率很好看,实际上每次修测试的时间比写业务代码还多。
后来我们才想明白一个道理:规格驱动里最有价值的部分,反而发生在自动化执行之前。团队坐在一起把规格逐条过掉、把模糊不清的边界定义清楚、把跨系统责任分配明确的那段时间,才是这套方法真正的核心产出。自动化执行只是在事后不断确认“当初的共识没有被破坏”。所以挑规格时一定要克制,优先覆盖那些“一旦错了会很贵”的行为。不要贪多求全,一份能挡住线上回归风险的精炼规格,远胜于一本面面俱到却没人能维护的规格大全。
5.2 经验教训:规格语言应该靠近业务,而不是靠近代码
实现规格的时候,有一条很容易踩的线——写规格的人会在描述中加入太多代码层细节,比如数据库表结构、缓存 key、方法名。一旦规格变成代码实现的镜像,它就失去了“中间语言”的意义,因为当代码变化时,规格也必须同步改,而且还要花同等精力去改。规格驱动和代码同步保持一致太多,就会变成维护负担。
更健康的做法是让规格与业务视角对齐,只写“系统必须对外保证什么”,不写“用什么方法保证”。举一个例子:与其写“提交订单后调用 InventoryService.DeductStock 方法”,不如写“提交订单成功后,对应 SKU 的可用库存数量必须减少购买数量”。前者是程序实现路径,后者是业务不变量。规格里保留后者,开发者在重构时就有自由空间,同时规格仍然明确约束了系统不能被破坏的底线。
5.3 个人体会:规格驱动表面上约束的是代码,实际上约束的是沟通习惯
如果把规格驱动拆开,它本身并没有多么神秘的技术内核,也没有非用不可的特定工具链。实现它的技术手段早在十几年前就成熟了:场景化语言、抽象测试、契约校验,这些都不是新东西。真正让“Spec-Driven Development”成为一种新模式而不仅是一堆工具组合的,是它带来的协作方式变化。
它强迫产品经理、开发、测试在代码出现之前,就围绕具体行为和边界条件做出选择;它强迫不同团队在跨系统协作时先解决数据结构问题,再各自开发;它强迫开发者把“实现完再说”的惯性,改成“先确认清楚再做”的节奏。这套流程跑顺之后,规格文件会沉淀成一个平台里最容易被理解、最能反映真相的资产。新人加入项目,与其去翻那些不知道是否过时的架构文档,不如直接从规格目录开始读,系统“合同”什么样、边界在哪里,都会清楚得多。
如果你准备在自己的项目中尝试规格驱动开发,我的建议是选择一个真正让团队头疼过的需求区域,拉上产品和测试,先用半天时间写出第一份可以被评审的规格。注意,是能被评审,而不是写得完美。然后原样走完“评审—红—绿—回归”这个闭环。跑过一次之后,你对这份规格到底改变了什么的感知,会比读十篇方法论文章更有说服力。
