用OpenSpec落地SDD:让需求和代码不再分家

发布时间:2026/10/31 6:11:51
用OpenSpec落地SDD:让需求和代码不再分家 做后端开发这么多年我最大的一个感触是需求文档和代码最后总是会分家。文档写了一套代码跑的是另一套等业务方来验收的时候才发现到处都对不上。这种割裂感在团队协作里特别明显尤其是引入 AI 辅助编码之后AI 确实能快速生成代码但它生成的代码到底符不符合初始意图没人能说得准。所以我最近半年一直在研究和实践SDDSpecification-Driven Development规范驱动开发并且在一个中台项目的核心模块里用了一个叫OpenSpec的轻量级开源 CLI 工具把 SDD 从“方法论口号”真正落到了每天的提交、测试和发版流程里。这篇文章不是泛泛谈理念而是把我从零开始接入 OpenSpec、建立规范、跑通“规范到代码再到验证”这条链路的完整过程、踩过的坑、以及最后沉淀下来的标准动作全部摊开来讲。项目还在演进但已经跑完了一个完整的迭代值得整理出来。这篇文章适合的人有两类一是被“文档写归写、代码写归代码”折磨的技术负责人和一线工程师二是正在用 AI 辅助写代码、但苦于没法约束 AI 行为一致性的团队。读完你会知道SDD 为什么这两年重新被重视、OpenSpec 在中间扮演什么角色以及在一个真实工程环境里怎么一步步把它落地成每天都能用的工作流。1. 整体设计拆解OpenSpec 是怎么把 SDD 变成可执行工作流的1.1 先厘清 SDD 和 TDD、DDD 的本质区别很多团队一提“开发方法论”就想到 TDD测试驱动开发觉得先写测试已经很先进了。但 TDD 解决的是“实现是否正确”的问题它回答不了“我们做的功能到底是不是业务方要的”这个问题。DDD领域驱动设计解决的是“领域模型怎么划分”的问题但它很容易陷入大量建模会议产出物一片 UML 图离代码依然很远。SDD 的核心主张是把规范Specification当成软件开发的一等公民。需求不再是“一段解释性文字”而是“一份可校验、可版本化、可追溯的契约”。在这份契约里你要明确描述系统的行为期望、约束边界、交互协议。代码、测试、文档、发布说明都是从这份契约推导出来的产物。换句话说SDD 把“做什么”和“怎么做”彻底拆开“做什么”由规范牢牢钉死“怎么做”由工程师自由发挥。这个思路的优势在 AI 时代被无限放大了。过去我们担心的是“代码跑偏”现在还得担心“ AI 生成的代码跑偏”。给 AI 喂一大段产品文档它生成的代码往往貌合神离。但如果你把一份结构化的行为期望清单交给它它生成的东西至少在语义上不会跑偏——因为你已经把“正确行为”定义清楚了。OpenSpec 就是承接这个想法的一个具体工具。1.2 OpenSpec 的定位一个规格仓库 校验器 工作流引擎OpenSpec 本身不写代码它管的是“规格本身”。我用下来的理解它包含三块核心能力规格仓库Spec Registry在项目里建一个专门的specs/目录用统一的 Markdown 结构存放所有规范。每个规范都是一个独立文件或文件夹描述了某个功能区域的目标、边界、行为期望。这套结构是人和机器都能读的。可验证期望ExpectationsOpenSpec 规范里最重要的不是形容词而是行为期望。每条期望都对应一个可执行的验证动作。如果期望是“系统拒绝未登录用户的请求”那它必须映射到一个能自动跑的测试或用例。这一点让规范和测试真正形成了绑定关系。工作流闭环Workflow它内置了一套从“创建规范”到“变更规范”再到“发布规范”的命令流。规范变更不是直接编辑文件而是先提一个 Change Request经过检查后合入保证整个迭代过程有记录、可回溯。听起来抽象我举个实际例子。假设我负责用户中心里“手机号注册”这个功能。传统做法是产品写需求、我建表写接口。用 OpenSpec 的做法是先在specs/里创建一个phone-registration规范里面写明目标允许新用户通过手机号验证码注册。边界不包含密码设置需要接第三方短信服务。行为期望未注册手机号可用验证码注册已注册手机号返回明确错误码验证码错误时注册失败且同一手机号每分钟最多尝试 5 次然后把这个规范提交进仓库。此时 OpenSpec 会运行检查器确保每条期望都符合格式要求并且能映射到测试。后续开发、联调、验收所有人都以这份规范为唯一基准。1.3 为什么选择 OpenSpec 而不是自研一套规范系统在接入 OpenSpec 之前我们也试过在项目里维护一个“需求规格文档”的文件夹用 Confluence 写用 Excel 跟踪状态。效果很差。原因是文档平台的沉淀能力太弱没有 diff、没有版本、没有自动检查规范和代码仓库是物理隔离的。选择 OpenSpec 的关键理由有三个第一它把规范放进了代码仓库。规范文件和代码同库管理每次修改都能走 Code Review。规范变更再也不会悄无声息每一次变更都有据可查。第二它有命令行校验器。这不是一个只会“读文档”的工具它会检查规范的完整性、期望的可验证性、变更请求的格式。我可以直接把它接到 CI/CD 里让规范质量和代码质量一样被自动卡控。第三接口设计非常克制。它只解决规范驱动这一个核心问题不强行绑定某个语言或框架。我是用 Node.js 的项目但 OpenSpec 本身不依赖我的技术栈它操作的是目录和 Markdown 文件只要规范写得准确校验和发布流程就成立。有一点要提前说明我这里根据实践中比较通用的用法来描述它的命令和目录结构如果你用的版本不同以官方仓库的 README 为准。我真正想分享的不是某个具体命令的拼写而是把 SDD 落地时的那套思考框架。2. 实操准备与首个规范的创建2.1 初始化一个规范仓库目录结构的设计OpenSpec 的初始化命令很简单但我建议不要直接默认一把梭先在脑子里想清楚仓库结构。我在实践中用过的目录结构是这样project-root/ ├── specs/ │ ├── engines/ # 核心能力规范相对稳定 │ │ ├── user-account/ │ │ │ ├── spec.md # 功能规范主体 │ │ │ └── expectations.md # 行为期望清单 │ │ └── payment-order/ │ │ ├── spec.md │ │ └── expectations.md │ ├── requests/ # 变更请求一次一文件 │ │ ├── 2025-06-add-phone-registration.md │ │ └── 2025-06-update-order-timeout.md │ └── releases/ # 发布记录由规范生成 │ └── v0.1.0.md我把规范分成engine和request两层。engines存放稳定的核心能力比如用户账户、支付订单这些模块短时间不会大改规范一旦建立就是长期契约。requests存放单个变更请求每次开发一个功能点就新增一个请求文件请求被批准并实现后再合并进对应的 engine 规范。这种设计的价值是既有稳定的锚点又有灵活的变化层。核心能力的规范不会因为一次小改动就被改得面目全非而变更请求又保证了任何变化都有记录。2.2 用 CLI 快速创建一个标准规范我在实际项目里用下面的命令创建了第一个规范# 初始化规范仓库 openspec init # 创建一个新的核心能力规范 openspec engine create user-account # 创建一个变更请求 openspec request create add-phone-registration我的实际操作习惯是先创建 request因为一个功能通常是从变更请求开始的。创建之后OpenSpec 会在specs/requests/下生成一个 Markdown 文件内容大致包含标题和变更范围Scope本次变更涉及哪些引擎、哪些模块。原因Rationale为什么要做这个变更解决什么业务问题。变更内容Change Details新增了什么行为删除了什么行为修改了什么行为。可验证期望Verifiable Expectations每条变更对应的具体验证点。坦白说刚开始写的时候很不习惯。因为大家平时写需求描述习惯用模糊的形容词——“提升用户体验”“响应更快”“更稳定”。这些词汇在 OpenSpec 里被直接打回因为校验器无法从“更稳定”里提取出任何可执行的验证动作。所以我强制自己用“Given... When... Then...”的句式来写期望。比如## Expectations - Given 一个未注册的手机号 When 用户提交验证码注册请求 Then 系统创建新账户并返回成功 - Given 一个已注册的手机号 When 用户提交注册请求 Then 系统返回错误码 ACCOUNT_EXISTS - Given 一个错误的验证码 When 用户提交注册请求 Then 系统拒绝注册 And 同一手机号在一分钟内最多允许 5 次尝试这种写法不仅仅是格式好看它最直接的作用是每条期望都能映射到一条或N条测试用例。写测试的时候再也不用凭空猜“测试该怎么设计”了照着期望清单翻译就行。团队成员哪怕之前根本不了解这个模块拿到期望清单也能写出覆盖核心路径的测试。2.3 验证规范本身是否合格写完规范运行验证命令是检查它的“格式病”和“逻辑漏洞”openspec validate这条命令会扫描所有规范文件检查Markdown 结构是否完整是否缺少标题或必填字段。Expectations 是否符合可验证格式。每个 Change Request 是否引用了存在的 Engine。是否有重复定义或冲突的期望。我头一次运行 validate 的时候报了好几个错全是“期望不可验证”。后来我总结了原因因为我还是习惯写解释性的描述比如“系统应该有较好的安全性”而没有把它拆成具体的可观察行为。后来改成“系统对错误密码的校验耗时为一个固定值不暴露账户是否存在”后校验就通过了。这里有个我强烈建议执行的习惯把openspec validate接进 CI 的 MR 检查流程。不需要每次提交都跑但至少在有规范文件变更的 MR 上强制跑一次。这样规范和代码一样具备“不合格不能合入”的硬性标准。3. 核心环节实现从规范到代码到测试的闭环3.1 把规范翻译成开发任务一份规范一旦被批准接下来的工作就是实现。OpenSpec 本身不生成代码但它会给你一个非常清晰的“任务地图”——所有 Expectations 就是你要实现的功能点列表。我在实际操作里会把每个 Expectations 条目变成一个开发任务然后做一件事给每条期望标注优先级和关联代码文件。做法是在规范文件的 Expectations 后面加一个标记比如- Given 一个未注册的手机号 When 用户提交验证码注册请求 Then 系统创建新账户并返回成功 priority high source src/services/registration.ts这个source标记不是 OpenSpec 强制的而是我们自己约定形成的团队规范。好处是当某个期望对应的测试挂了你顺手就能定位到具体代码文件当你想重构某个文件也能快速查到这个文件承载了哪些核心期望评估影响面。3.2 测试就是期望的“人肉翻译机”再不违背规范的前提下测试编写就变成了一件很机械的事情。我仍然用 Jest 来写但每个测试的描述基本是期望原文的镜像。比如“未注册手机号可注册成功”这条期望对应测试就是describe(phone registration, () { test(unregistered phone can register via verification code, async () { const phone generateUnregisteredPhone(); const response await request(app) .post(/api/register) .send({ phone, code: 123456 }); expect(response.status).toBe(200); expect(response.body.token).toBeDefined(); }); });这个过程看起来“傻”但我后来想明白了它其实是整个 SDD 落地里最精髓的一步。测试不是独立于规范的发明而是规范的机器可读形式。以前我们写完需求文档再花大量时间讨论测试策略讨论着讨论着两边就互相偏离了。现在规范是所有讨论的共同起点测试只是它的镜像两边永远不会背道而驰。3.3 用规范校验器卡住实现偏差在实现过程中最容易出现的问题是写着写着某个边界行为被省略了。比如“已注册手机号返回明确错误码”这条开发者在正常流程里顺手就把所有错误都返回成一个通用的REGISTER_FAILED导致业务方无法区分是手机号被占用还是验证码错误。为了卡住这些问题我在每个 MR 里要求变更代码的人同时做两件事新增或修改的测试必须关联到规范的某一条期望。运行openspec check我们配置的本地校验命令确保所有期望都有测试覆盖。实际操作中openspec check会扫描测试目录根据测试文件里的 tag 关联到规范性文件。如果某个高优先级期望没有对应测试check 会直接报红。于是“漏测”这件事在流程上就被扼杀了。我体会很深的一点是SDD OpenSpec 最大的收益不是写出来的代码变短了而是开发过程中“拍脑袋”的部分变少了。每个行为都有依据每次改动都有审计线索。3.4 处理规范变更变更请求闭环一个系统活着就会不停变化。今天的新功能明天可能就是老逻辑。SDD 并不排斥变化非常重视变化的管理。我举个例子我们刚开始做注册的时候规定“同一手机号每分钟最多尝试 5 次验证码”后来接入了一个风控平台要求改成“同一手机号每天最大验证次数为 20 次”。如果没有 SDD这个需求八成是研发直接改个判断条件就上线了。但有了 OpenSpec流程变成了新建一个 request标题为“调整手机号验证码频控策略”。在 request 里写明变更原因和新旧的期望对比。request 进入评审产品、研发、测试看同一份变更说明。评审通过后合并 request同步更新对应 engine 里的 expectations。测试文件同步修改CI 跑全部测试验证新旧行为切换正确。这套流程看起来比“直接改代码”多了两步但它的价值在于每一个决策痕迹都被沉淀下来了。三个月后如果有人问“为什么频控策略这么定”看 request 的历史记录就是最完整的答案。这种可追溯性在人员流动频繁的团队里简直是无价之宝。4. 发布流程与规范治理把规范变成团队的“宪法”4.1 从规范自动生成发布说明过去我们发版之前最痛苦的工作之一是写 Release Notes。每次都得回忆这个迭代做了哪些功能、哪些 bug 修复研发和产品回忆的版本还经常对不上。由于 OpenSpec 里记录了所有合并的 request我们发版时运行openspec release create v0.2.0它会汇总从上一个 release 到当前的所有 request 和关联的 expectations自动生成一个发布说明草稿。草稿里每条变更都写明变更涉及的功能区域engine。变更请求的编号和标题。影响到的行为期望即用户可见的行为变化。生成的 Release Notes 基本不需要产品经理再动脑筋回忆只需要微调语气和补充非功能性的内容。这还有一个隐性收益业务方能直接看到每个发版对应的行为变化而不是看一堆抽象的代码 commit。跨部门沟通顺畅了很多。4.2 用“期望覆盖率”做技术债务体检一个项目做久了必然产生技术债。SDD 给了我们一个特别好的工具来量化债务期望覆盖率的缺口。我在每个月结束时都会跑一遍openspec report它输出的是一张表列出每个 engine 下面有多少条 expectations、已经有多少条被测试覆盖、高优先级期望是否全部实现。我见过一个非常典型的场景某个老模块的规范有 40 条期望实际只有 18 条有测试覆盖。那剩下 22 条就是潜在的风险点——要么是功能没实现要么是实现了但没验证要么是规范写得太超前但代码完全没跟上。这个报告直接变成了我们技术 backlog 的重要一部分。每月技术评审不看 KPI 汇报就看这个覆盖率表哪条期望没做实就排期补齐。团队对“债务”的认知从“感觉哪里有坑”变成了“列表上具体哪一行是红的”。4.3 多模块团队的规范所有权当团队超过 10 个人、前端后端测试三拨人并行开发的时候规范驱动最担心的是“谁都能改规范最后规范乱七八糟”。我的做法是给每个 engine 指定唯一的Owner通常是对这个模块最熟悉的那个工程师。任何涉及该 engine 的 request 合并必须由 Owner 批准。Owner 对规范质量负责包括是否每条期望都清晰、是否可验证、是否与当前系统行为一致。这既不是多余的审批流也不是为了权力分配而是为了保证规范本身的专业性和一致性。从团队进入规范驱动状态后的实际效果看代码评审的重心发生了很大的偏移。以前评审主要是“你这个实现方式对不对”现在变成了“你这个实现是不是符合规范、有没有偏离期望”。后者比前者更容易达成共识争议也更少。5. 常见问题与避坑指南5.1 字典型错误把规范写成了产品文案这是我见过最多的错误。团队一开始接触 SDD写出来的期望全是一堆形容词“用户应拥有流畅的注册体验”“支付流程应简单便捷”。这种期望用 OpenSpec validate 一查必然报错因为它无法映射到测试。我这里给出一个判断诀窍如果一条期望里找不到“可以观察到的系统响应”或“明确的输入输出关系”那它就不是期望是口号。我后来给团队定的规矩是写期望的时候把自己当成黑盒测试工程师只描述外部行为不描述内部情绪。5.2 期望粒度不对太粗或太碎太粗的问题在于一条期望里包含了太多行为比如“用户注册流程的所有异常场景都应正确处理”这种必须拆开。太碎的问题则相反写了一堆描述琐碎细节的期望比如“页面上的按钮颜色为红色”这类细节根本不应该写进规范里它属于 UI 实现层。我实践下来觉得合适的粒度是一条期望对应一个可观察的业务行为且这个行为能为业务带来独立的价值。比如“验证码错误时注册失败”是有价值的独立行为而“注册按钮在验证码错误时抖动 3 次”就不是——它只是一个交互细节。5.3 规范与测试的同步维护最大的坑永远在这里规范更新了测试没更新测试更新了规范没更新。两种都会导致下一次校验失败或覆盖报告失真。我采用的解法是技术层面和流程层面双管齐下在 CI 里强制openspec validate任何规范和测试 tag 不一致的 MR 无法通过。在团队约定里明确凡涉及行为变化的 PR必须同时出现对 expectations.md 的修改这跟“必须要有新测试”是同等级的铁律。从执行结果看这两个约束基本消除了“改了行为但忘了更新规范”的漏网之鱼。关键在于不要依赖个人自觉要把它变成 MR 模板里的必填项。5.4 一个特别容易踩的坑规格成了实现方案的附庸还有一个细思极恐的问题写着写着规范变成了“对代码的解释”而不是“对行为的定义”。表现为代码先写完了然后照着代码补一个规范文档让 CI 通过。这种情况下规范和代码是完全同构的SDD 的纠偏作用就完全失效了因为它只是在给既成事实做注释。识别这个问题的方法很简单新需求来了先让一个人独立写规范写完再让实现的人看。如果实现的人看完就能准确说出要做什么规范就是合格的。如果实现的人回来说“这段规范读了两遍不知道具体怎么干”那规范已经变成了代码的影子需要重新收敛。5.5 配合 AI 编码时的一点额外心得最后聊一个很切身的体会。我接 OpenSpec 的直接动机其实是 AI 辅助编码的流行。现在团队里人人都在用 AI 补代码但 AI 补出来的代码最大的问题就是“看着是对的但行为跑偏”——它经常基于训练语料里的“一般做法”来写而不是基于你们项目的“特殊约定”。有了 OpenSpec 之后我把一个模块的spec.md和expectations.md直接作为上下文塞给 AI 工具要求它“严格按照期望清单逐条实现”。事实证明给 AI 喂一份结构化的期望清单比喂一大段自然语言描述代码通过率要高出一大截。尤其是那些“不能做什么”比如不允许暴露账户存在性、不允许绕过频控的约束性期望AI 在自然语言需求里经常忽略但在明确列出的期望清单里它会当成硬性约束来遵守。我个人现在的习惯是任何面向 AI 的编码任务先甩规范文件过去再让它产出具体代码。同时保留人类的最终评审权。这套组合目前来看是把 AI 的产能和人类的品控结合得比较好的方式。6. 后续还能怎么扩展OpenSpec 这层规范设施搭好以后它就像是一个“地基”很多东西可以在上面继续生长。我目前看到且准备尝试的方向有两个。一是把规范直接作为 API 契约的来源。现在对外提供 HTTP 接口时还是要手工维护 OpenAPI 文档后续可以考虑从 specs 里的期望描述自动生成接口契约文档实现“行为规范即接口文档”。虽然还需要补一些字段定义但至少核心的交互逻辑不需要两边维护了。二是把规范引入培训场景。新人入职最怕的是不了解历史背景。以前我只能翻着代码和 PR 讲费时费力。现在我可以直接把 specs 目录扔给他从 engine 到底层 expectations 拉一遍整个系统的行为地图就印在他脑子里了。这个体验比我见过大多数团队的“看代码”方式高效得多。如果你所在的团队正被需求漂移、文档失效、AI 代码不可控这类问题困扰我建议别急着上各种重型的流程平台先从一份可校验的规范开始用最低成本的目录和 Markdown 跑通一个小闭环再逐步扩展。从我自己的实践看SDD 落地最难的不是工具而是人习惯性地想“绕过定义直接写代码”。一旦这个习惯被扭转后续整个团队的默契度会有一次非常明显的提升。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询