SDD规范驱动开发与OpenSpec实战:从规范到代码的工程化落地

发布时间:2026/10/27 6:23:21
SDD规范驱动开发与OpenSpec实战:从规范到代码的工程化落地

1. 从"能跑就行"到"规范先行":SDD 到底在解决什么

第一次接触 OpenSpec 这套东西,是因为团队里一个很典型的老问题:需求文档、接口约定、代码实现三份东西各说各话。产品在文档里写"用户下单后触发通知",后端理解成同步调用,前端以为要轮询,测试按第三种逻辑写用例。等到联调那天,三个人对着屏幕互相问"你这块到底怎么设计的",然后花两天时间把逻辑重新对齐一遍。这种事发生三次之后,我开始认真思考一个问题:有没有一种方式,能让"规范"本身变成可执行、可校验、可追溯的东西,而不是躺在文档库里吃灰的 Word?

SDD,也就是 Spec-Driven Development,规范驱动开发,核心思路就是把"规范"从附属品提升为开发流程的一等公民。传统开发里,规范是给人看的,代码是给机器跑的,两者之间靠人的理解来桥接,而人的理解天然有偏差。SDD 想做的事,是让规范具备机器可读的结构,让代码生成、校验、测试用例推导都能围绕同一份规范展开。OpenSpec 就是这套理念的一个工程化落地工具,它用一套约定好的目录结构和配置文件,把"规范"变成项目里真实存在的、可以被工具链消费的资产。

这里要先说清楚一个容易混淆的点:SDD 不是"写更详细的文档",也不是"用 AI 帮你写代码"这么简单。它的本质是把意图显式化。你写下一段规范,不只是描述"要做什么",还要描述"边界在哪""异常怎么处理""验收标准是什么"。这些信息在传统开发里往往散落在需求评审的会议纪要、聊天记录、某个人脑子里,SDD 要求你把它固化下来。OpenSpec 则提供了承载这些规范的骨架,以及一套让规范与代码保持同步的机制。

那 OpenSpec 具体长什么样?它的核心是一个项目根目录下的openspec文件夹,里面按功能模块组织规范文件,每个规范文件用结构化的格式描述行为、输入、输出、约束。配合config.yaml做项目级配置,声明规范路径、校验规则、生成目标等。工具链读取这些规范后,可以做的事情包括:校验规范本身的完整性、根据规范生成接口骨架、比对代码实现与规范的偏差、在规范变更时提示受影响的代码范围。这套机制的价值在于,它把"规范与实现的一致性"从依赖人的自觉,变成了依赖工具的强制。

适合谁来用这套东西?我的判断是三类人收益最明显。第一类是多人协作的中大型项目,尤其是前后端分离、跨团队对接频繁的场景,规范不一致的代价被放大得很厉害。第二类是需要长期维护的项目,半年后回来看代码,如果规范还在且准确,理解成本会低很多。第三类是正在尝试把 AI 编码工具引入工作流的团队,因为 AI 生成代码的质量高度依赖输入的清晰度,一份结构化的规范比一段模糊的自然语言描述效果好得多。反过来说,如果是个人做的一次性小脚本,或者需求天天变的探索性原型,上 SDD 的收益可能覆盖不了成本,这点要诚实评估。

我自己的体会是,SDD 和 OpenSpec 这类工具最大的门槛不在技术,而在习惯。你得先接受"写规范是开发的一部分,不是额外负担"这个前提,才可能真正用起来。下面我会从环境准备、规范结构设计、配置细节、与编码工具的配合、以及实际踩过的坑几个角度,把整套流程拆开讲。

2. OpenSpec 项目骨架与 config.yaml 的配置逻辑

2.1 目录结构不是随便定的,每一层都有它的职责

OpenSpec 的目录组织方式,乍看只是"把文件分门别类放好",但实际用起来会发现,这个结构本身就在约束你的思考方式。典型的骨架大致是这样:

project-root/ ├── openspec/ │ ├── specs/ │ │ ├── user-auth/ │ │ │ ├── spec.md │ │ │ └── cases.yaml │ │ └── order-flow/ │ │ ├── spec.md │ │ └── cases.yaml │ ├── changes/ │ │ └── 2024-xx-xx-add-refund/ │ │ └── proposal.md │ └── archive/ ├── config.yaml └── src/

specs目录放的是当前生效的规范,按功能模块分子目录。每个模块下通常有一个描述性的spec.md和一个结构化的cases.yaml。为什么要把描述和用例分开?因为这两类信息的消费者不同。spec.md主要给人看,讲清楚这个模块的意图、边界、设计取舍;cases.yaml主要给工具和测试用,把输入输出、预期行为写成机器可解析的形式。混在一起写会导致两边都不讨好——人读起来太啰嗦,机器解析起来又太模糊。

changes目录是我个人觉得设计得最巧妙的部分。它借鉴了"变更提案"的思路:任何对规范的修改,不是直接改specs里的文件,而是先在changes下建一个带日期的提案目录,写清楚这次改什么、为什么改、影响哪些模块。等提案评审通过、代码实现完成,再把变更合并进specs,提案归档到archive。这个流程强制你把"规范变更"当成一件需要走流程的事,而不是随手一改。实测下来,这个约束能挡掉很多"改着改着就忘了同步文档"的问题。

2.2 config.yaml 里那几个关键字段,配错了会很难受

config.yaml是 OpenSpec 的项目级配置入口,它决定了工具链去哪里找规范、按什么规则校验、生成什么产物。字段不多,但每一个都有实际影响。下面这张表是我整理的核心字段和它们的实际作用:

字段作用常见取值配错的后果
specs_path指定规范根目录./openspec/specs工具找不到规范,校验直接失败
changes_path指定变更提案目录./openspec/changes变更流程无法追踪
schema_version规范格式版本1.0版本不匹配导致解析报错
validate_on_commit提交时是否校验true/false关掉后规范容易腐化
generate_targets生成产物类型interface,test,doc配错会生成无用文件
ignore_patterns忽略的路径["**/draft/**"]草稿被误校验,噪音多

specs_path和changes_path这两个基本不会配错,但schema_version和validate_on_commit是新手最容易忽略的。schema_version如果不写或者写错,工具在解析规范文件时会按默认版本处理,遇到格式差异就报一堆看不懂的错。我的建议是显式写上当前工具链支持的版本号,别依赖默认值。validate_on_commit我强烈建议开成true,虽然它会让每次提交多花几秒钟做校验,但这几秒钟换来的是规范不会悄悄腐化。我见过太多项目,一开始规范写得好好的,几个月后因为没人校验,规范里的字段和实际代码完全对不上,最后整套 SDD 流程名存实亡。

generate_targets这个字段值得单独说。它决定了 OpenSpec 根据规范生成什么。常见的取值有interface(生成接口定义骨架)、test(生成测试用例模板)、doc(生成可读文档)。我的经验是不要一次全开。刚上手时只开interface,让工具帮你把接口签名对齐,跑顺了再逐步加test。全开的话,生成物太多,你反而不知道该看哪个,而且生成物和手写代码混在一起,容易乱。

2.3 规范文件本身怎么写才算"合格"

规范文件的质量直接决定整套流程的收益。我总结了一个判断标准:一份合格的规范,应该能让一个没参与过需求讨论的人,仅凭它就能写出符合预期的实现。这个标准听起来高,但它是可操作的。具体来说,一份规范至少要覆盖四类信息:

  • 意图:这个模块解决什么问题,为什么需要它
  • 接口:输入是什么、输出是什么、数据结构长什么样
  • 行为:正常流程怎么走、异常怎么处理、边界条件是什么
  • 验收:怎么判断实现是对的,有哪些必须通过的场景

spec.md负责前两类和第三类的描述性部分,cases.yaml负责第三类的结构化部分和第四类。举个具体的例子,假设是"用户登录"这个模块,cases.yaml里的一条用例大概长这样:

- id: login-success description: 正确账号密码登录成功 input: username: "test_user" password: "correct_password" expect: status: 200 body: token: "<non-empty>" expires_in: 3600 - id: login-wrong-password description: 密码错误返回明确错误 input: username: "test_user" password: "wrong_password" expect: status: 401 body: error_code: "AUTH_INVALID_CREDENTIALS"

注意expect里的写法。token: "<non-empty>"表示只校验非空,不校验具体值,因为 token 是动态的。error_code则要求精确匹配,因为错误码是契约的一部分。这种"哪些字段严格校验、哪些字段宽松校验"的区分,是写用例时最需要拿捏的地方。写得太严,测试会因为无关的字段变化而失败;写得太松,又起不到校验作用。我的经验是:对外契约字段严格校验,内部实现细节宽松处理。

3. 把规范接进编码工具链:Claude Code 的配合方式

3.1 为什么要在 SDD 流程里引入编码助手

规范写好了,接下来是把它变成代码。这一步如果纯手工,规范的价值会打折扣,因为人还是会偷懒、会走样。引入编码助手的目的,是让"从规范到代码"这一步有工具参与,减少人为偏差。Claude Code 是这类工具里比较适合配合 SDD 的一个,原因是它对结构化输入的理解能力比较强,你把一份规范丢给它,它能比较准确地推导出实现骨架,而不是瞎编。

这里要说明一点:编码助手不是替代你思考,而是放大你规范的质量。规范写得清楚,它生成的代码就靠谱;规范写得含糊,它生成的代码就是"看起来对但经不起推敲"。所以我在实际使用中的顺序永远是:先把规范打磨到位,再让工具生成,最后人工审查。跳过第一步直接让工具生成,等于把不确定性放大了。

3.2 环境准备:安装与基础配置

Claude Code 的安装方式根据平台不同有差异。在 macOS 和 Linux 上,通常通过包管理器或者官方提供的安装脚本完成;Windows 上则需要注意终端环境的兼容性,建议在 WSL 或者 PowerShell 较新版本下操作。安装完成后,第一次运行需要完成登录和基础配置。这里有个实际经验:安装路径和项目路径尽量不要有中文和空格,否则某些工具链在解析路径时会出问题,这个坑我在两个项目里都踩过。

配置层面,Claude Code 支持通过配置文件声明模型、工作目录、忽略规则等。和 OpenSpec 配合时,关键是把工作目录指向项目根,让它能同时看到openspec目录和src目录。这样你在让它生成代码时,它可以直接读取规范文件作为输入,而不是你手动复制粘贴。忽略规则里建议把openspec/archive加进去,归档的提案不需要参与生成,加进去能减少噪音。

关于模型选择,不同模型在代码生成上的表现差异明显。我的做法是:规范解析和骨架生成用理解能力强的模型,细节填充和重构用代码能力强的模型。这个分工不是绝对的,但比"一个模型用到底"效果好。另外要注意,某些模型在特定地区可能不可用,这个要提前确认,别等到流程跑到一半才发现调不通。

3.3 让工具读懂规范:提示词的组织方式

直接对编码助手说"根据规范生成代码",效果往往一般,因为它不知道你关注哪些部分。我的做法是把提示词分成三段:上下文、任务、约束。

上下文部分,明确告诉它规范文件的位置和结构,比如"项目规范在 openspec/specs/user-auth/ 下,spec.md 描述意图,cases.yaml 描述验收场景"。任务部分,说清楚这次要生成什么,比如"根据规范生成登录接口的实现骨架,包括路由、参数校验、错误处理"。约束部分,列出必须遵守的规则,比如"错误码必须与 cases.yaml 中的 error_code 完全一致""不要引入规范里没提到的依赖"。

这套提示词结构的好处是,它把"规范"和"生成"之间的映射关系显式化了。工具不需要猜你的意图,你也不需要反复调整。实测下来,用这套结构生成的代码,第一次就能通过大部分验收用例,剩下的偏差集中在边界条件处理上,人工补一下就行。

3.4 生成之后的校验闭环

生成完代码不是结束,而是校验的开始。OpenSpec 的校验能力在这里派上用场:把生成的代码和规范做比对,看接口签名是否一致、错误码是否匹配、必填字段是否都处理了。这个比对不需要你手动做,工具链可以跑。我的习惯是把这个校验挂到提交钩子上,每次提交前自动跑一遍,不通过就拦下来。

这个闭环的价值在于,它让"规范"和"代码"之间有了持续的约束关系。传统开发里,规范写完就没人看了,代码改着改着就和规范脱节。有了这个闭环,任何脱节都会在校验时暴露出来,逼着你去同步。刚开始会觉得烦,但用久了会发现,这种"烦"换来的是项目长期的可维护性。

4. 实战中真正会卡住你的几个地方

4.1 规范粒度的拿捏:太细和太粗都是坑

这是我踩得最深的一个坑。刚开始用 OpenSpec 时,我恨不得把每个函数的行为都写成规范,结果规范文件比代码还长,维护成本高得离谱,团队怨声载道。后来我调整了策略,只对跨模块的接口契约和有明确验收标准的业务逻辑写规范,模块内部的实现细节不写。这个粒度调整之后,规范文件精简了大概六成,但覆盖了九成的协作痛点。

判断粒度的标准其实很简单:如果这段逻辑只有一个人维护,且短期内不会变,就不用写规范。规范的价值在于"多人协作"和"长期维护",脱离这两个前提,写规范就是纯成本。我见过一些团队为了"规范而规范",把简单的 CRUD 也写成厚厚的规范文档,最后没人看,纯粹是形式主义。

4.2 规范变更的同步问题

规范改了,代码没改,这是 SDD 流程里最危险的状态。OpenSpec 的changes机制就是为了解决这个问题,但它需要你自觉走流程。实际使用中,最容易出问题的是"紧急修复"场景:线上出 bug,你直接改代码,忘了同步规范。等下次有人按规范理解代码时,就被误导了。

我的应对办法是:紧急修复允许先改代码,但必须在 24 小时内补一个变更提案。这个提案不需要很详细,但要记录改了什么、为什么改。补提案这个动作本身,就是一次"规范与代码对齐"的检查。另外,我会在 CI 里加一个检查:如果代码有变更但changes目录没有对应的提案,就给出警告。这个警告不阻断合并,但会提醒人补上。

4.3 团队协作中的认知差异

SDD 推行的最大阻力往往不是技术,而是人的习惯。有人觉得"写规范是浪费时间",有人觉得"规范约束了灵活性"。我处理这个问题的方式是先在小范围试点,用数据说话。选一个协作痛点最明显的模块,用 SDD 流程做一遍,记录下"因为规范清晰而节省的沟通时间"和"因为校验而提前发现的偏差"。等这些数据出来,再向团队推广,阻力会小很多。

还有一个细节:规范的评审要轻量化。不要搞成"必须三个人签字"那种重流程,那样没人愿意写规范。我的做法是规范变更只需要一个相关方 review 即可,重点是快,让规范能跟上代码的节奏。重流程的规范体系,最后都会因为太慢而被绕过。

4.4 工具链的版本兼容

OpenSpec 和编码助手都在快速迭代,版本兼容问题时有发生。我遇到过schema_version升级后旧规范文件解析失败的情况,也遇到过编码助手更新后提示词效果变化的情况。应对办法是锁定版本:在项目里记录当前使用的工具版本,升级前先在测试分支验证,确认没问题再全量升级。别在生产分支上直接升级工具链,这个教训我是用一次线上事故换来的。

5. 一个完整的小案例:从规范到代码的走通

5.1 场景设定

假设我们要实现一个"优惠券核销"功能。需求是:用户下单时可以使用优惠券,系统需要校验优惠券有效性、扣减库存、记录核销日志。这个场景涉及多个模块协作,正好适合用 SDD 流程走一遍。

5.2 规范编写

先在openspec/specs/coupon-redeem/下建spec.md,写清楚意图和边界:优惠券只能核销一次、过期券不可用、库存不足时核销失败并返回明确错误码。然后写cases.yaml,把关键场景结构化:

- id: redeem-success input: { coupon_id: "C001", order_id: "O001" } expect: { status: 200, remaining_stock: 99 } - id: redeem-expired input: { coupon_id: "C002", order_id: "O002" } expect: { status: 400, error_code: "COUPON_EXPIRED" } - id: redeem-out-of-stock input: { coupon_id: "C003", order_id: "O003" } expect: { status: 409, error_code: "COUPON_OUT_OF_STOCK" } - id: redeem-duplicate input: { coupon_id: "C001", order_id: "O004" } expect: { status: 409, error_code: "COUPON_ALREADY_USED" }

5.3 生成与校验

把这份规范作为输入,让编码助手生成核销接口的骨架。生成后跑 OpenSpec 的校验,重点看错误码是否和cases.yaml一致、库存扣减逻辑是否处理了并发。实测下来,生成代码在正常流程上基本没问题,问题集中在并发场景——两个请求同时核销同一张券时,可能都通过了有效性校验。这个偏差是规范里没写清楚的,我补了一条"并发核销必须保证幂等"的约束,重新生成后问题解决。

这个案例说明一个道理:规范的质量决定生成的质量,而规范的质量靠迭代。第一版规范不可能完美,重要的是有校验闭环,让偏差能被发现和修正。

5.4 归档与沉淀

功能上线后,把changes里的提案归档到archive,specs里的规范更新为最终版本。归档不是删除,而是标记为"已生效"。这样后续有人想了解这个功能的历史决策,可以翻归档的提案,看到当时的取舍。这个沉淀对长期维护的项目价值很大,尤其是人员流动之后,新接手的人能通过规范和历史提案快速理解设计意图。

6. 我对这套流程的真实看法

用 OpenSpec 和 SDD 这套东西大概一年,我的整体判断是:它适合对协作效率和长期可维护性有要求的项目,不适合追求短期速度的场景。它的收益不是立竿见影的,而是在项目跑到中期、人员开始流动、需求开始叠加的时候才显现出来。如果你现在做的项目三个月就结束,或者只有你一个人维护,那这套流程的成本可能大于收益,不必强上。

真正让我坚持用下来的,是它解决了一个我一直很头疼的问题:知识的沉淀。传统开发里,很多设计决策只存在于当事人的记忆里,人一走就断了。SDD 把这些决策固化在规范和历史提案里,让项目有了"记忆"。这个价值在人员流动频繁的团队里尤其明显。

最后分享一个实操小技巧:规范文件里留一个"决策记录"小节,专门记录"为什么这么设计"和"考虑过但放弃的方案"。这部分内容对工具生成没用,但对人理解项目极其有用。我现在的习惯是,每次做一个非显而易见的设计选择,就顺手记一笔。半年后回头看,这些记录帮我省了无数次"当时为什么这么写"的困惑。