规范驱动开发实战:用OpenSpec把需求变成可执行验收标准 我第一次接触到 OpenSpec是在接手一个已经折腾了两周的搜索模块。需求文档洋洋洒洒写了一万多字代码也堆了一千多行可每次评审都对不上测试更不知道该按什么标准写。后来我把核心行为整理成一份 spec把所有验收点变成可运行的检查项团队终于有了共同语言。这就是 SDDSpecification-Driven Development规范驱动开发的实用价值而 OpenSpec 就是把这套理念落地的开源框架。这篇文章是 OpenSpec 指南的第一期不讲虚的先把它的核心思路、安装初始化、spec 怎么写、怎么跑通一条完整流程以及我踩过的坑一次说清楚。适合正在做后端服务、接口开发或者开始大量用 AI 写代码但总被“答非所问”折磨的人。无论你是技术负责人还是写业务代码的一线开发者看懂这一篇之后至少能把“需求”这个词变成项目里真正能执行的东西。1. SDD到底在解决什么问题OpenSpec凭什么成立1.1 传统开发模式里最让人头疼的三个环节我做了十几年开发最耗精力的从来不是“写代码”本身而是“到底要做什么”这件事。需求评审会上大家点头说“明白了”回去一写发现理解完全不一样。产品经理说的是“用户要能快速登录”开发理解成“给个手机号验证码”测试理解成“用户名密码表单”三个人最后验收的其实是三套东西。这种混乱的成本一般不在当时爆发而是等接口已经联调完、页面已经渲染完测试用例跑出来一堆红色才发现根子上的偏差。这时候改需求容易改代码难改已经达成的“口头共识”最难。AI 时代这个问题又被放大了。市面上代码生成工具越来越强可你把一句话丢给它它给你生成的东西看着像样实际行为却经常偏得离谱。原因很简单自然语言描述在细节上是稀疏的一千个人有一千种补全方式。没有可执行的标准生成得越快错得越远。1.2 SDD的核心思路把“说什么”和“怎么做”彻底拆开SDD 的核心思路其实特别朴素先写清楚“系统应该表现出什么行为”再动手写实现。这里的“清楚”不是用一段散文描述而是写成像验收标准、测试样例这种能被程序判定真假的东西。打个比方做菜的时候菜谱就是 spec。菜谱上写着“盐 3 克、大火煮 5 分钟”这是可以量化的验收标准。你换成电磁炉还是煤气灶切成片还是切成块那是实现细节。只要最终咸度合适、软烂程度到位就算合格。反过来如果菜谱只写“加适量盐、煮一会儿”十个厨师能做出十种味道你没法判断谁做得对。SDD 就是把“菜谱”和“做菜过程”分离让菜谱本身成为约束、测试和验收的基准。这个思想并不新TDD、BDD 都有类似影子但 SDD 更强调 spec 是第一公民代码是 spec 的一种实现而 spec 是所有讨论的起点。1.3 OpenSpec在SDD里的位置OpenSpec 是一套开源的、围绕 spec 来组织开发流程的工具链。它不强迫你用某种编程语言也不绑定某个测试框架而是用 Markdown 文件承载规格用命令行对规格做校验和验证。我理解它的价值有三层。第一层是“沉淀”把散落在文档、聊天记录、白板照片里的需求整理成结构化 spec 文件放进 Git 里管理。第二层是“约束”每个 spec 里有明确的验收标准和测试样例实现完没实现跑一下就知道。第三层是“协作”无论是人还是 AI 助手拿到的都是同一份 spec谁都可以按它去实现也都可以按它去验证。我实际用下来的感受是OpenSpec 特别适合那些需要频繁迭代、多人协作、或者把代码交给 AI 工具来写的场景。它像给需求装了一个可执行的“标尺”不再靠默契和记忆推进。1.4 为什么是OpenSpec而不是自己写一套很多人听到这可能会想我自己在仓库里建一个 docs/requirements.md 不就行了我在早期也这么干过但结果往往是文档写完之后再也没人看因为它是静态的跟代码毫无关联。OpenSpec 跟普通文档最大的区别是它强调“可执行”。spec 里有标准化的字段结构有验证命令可以接入 CI还可以导出给 AI 助手作为上下文。这些东西如果自己搭需要设计格式、写校验脚本、做测试运行器、处理各种边界情况至少得花掉一两周时间还不一定比社区版本想得周全。我用一张表说明它和常见方案的区别方案是否能验证是否进代码库对 AI 友好度维护成本普通 Markdown 文档否一般会低高容易过期Cucumber/Gherkin是但偏端到端会中中语法较重自建脚本看情况会低很高OpenSpec是会很高低结构固定当然OpenSpec 不是万能的。如果你的需求非常简单一个接口就一个行为那直接写测试就行没必要上框架。但当需求一多、场景一交叉规范和实现的边界变得模糊时OpenSpec 这套结构才真正体现出价值。2. OpenSpec快速上手环境准备与初始化2.1 安装与版本确认先说环境要求。OpenSpec 的 CLI 是基于 Node.js 生态的所以你需要先确保机器上有 Node.js 和 npm。版本我建议 Node 18 以上太低会有兼容性问题。检查完 Node 之后直接全局安装node -v npm -v npm install -g openspec openspec --version装完先看一眼帮助信息不同小版本的命令命名可能会有差异这很正常openspec --help我这里给出的命令是当前主流版本的用法如果你手里的版本不一样优先以openspec --help的输出为准。第一次跑不认识的命令前先加--help看看这是所有 CLI 工具的通用安全操作。2.2 初始化一个规范的仓库结构安装完毕之后新建一个项目目录并初始化。我自己习惯把 OpenSpec 从项目第一天就放进去而不是等代码写了一半再补mkdir my-service cd my-service openspec init初始化命令会在当前目录生成一个.openspec目录里面存放所有规格相关的内容。这个目录应该提交到 Git 里它跟源码一样重要而不是临时工具。如果openspec init后没有任何提示就去检查一下当前目录是否已经是 Git 仓库以及你的 npm 全局安装路径是否在系统环境变量里。这两个问题占了我遇到过的安装问题的八成。2.3 目录结构与约定初始化完成后目录大概是这样的.openspec/ ├── config.yaml ├── specs/ │ └── .gitkeep ├── reports/ │ └── .gitkeep └── templates/ └── spec.template.md每个目录都有明确的职责。config.yaml是全局配置控制 spec 的扫描路径、验证规则、报告输出位置specs目录用于存放各类功能规格reports是运行验证后的输出目录templates是新建 spec 时用的模板文件。我自己会额外在项目根目录写一条开发规范任何新功能必须先有 spec再动代码。刚开始团队会觉得“多此一举”但坚持两个迭代之后大家就能体会到好处了开发前只需要看 spec评审也只需要看 spec代码 review 反而退居其次。2.4 第一个spec登录功能的最小版本创建 spec 有两种方式。可以用命令生成模板也可以手动在specs目录下建文件。我个人喜欢先命令生成再手动修改这样格式不容易错openspec spec new auth-login执行之后会在specs目录下生成一个叫auth-login的子目录里面会有 spec 主文件和关联的测试样例文件。你需要按照模板补全内容。补全之后先跑一遍校验命令openspec validate这条命令会扫描所有 spec 文件检查 YAML 格式、必填字段、验收标准是否符合规范。看见输出没有红色错误说明你的 spec 已经是一个合法规格了。这里的“合法”只是格式合法内容靠不靠谱我会在下一节详细讲。3. 规格文件怎么写才不白写核心细节解析3.1 Spec文件的核心组成front matter、验收、测试样例一个标准的 OpenSpec spec 文件长这样--- id: auth-logout title: 用户退出登录 status: proposed priority: high tags: - auth - api --- ## 需求描述 退出登录需要让当前会话失效并返回 200 状态码。 ## 验收标准 - 携带有效 token 调用 POST /api/logout 时返回 200。 - 退出成功后再用原 token 访问受保护资源必须返回 401。 - 未携带 token 调用 POST /api/logout 时返回 401。 ## 测试样例 - 用例有效 token 退出成功 input: header: Authorization: Bearer valid-token expected: status_code: 200 - 用例退出后原 token 失效 input: header: Authorization: Bearer revoked-token expected: status_code: 401文件最上方的---之间是 YAML front matter用来记录这个 spec 的元信息。id是全局唯一的标识title是人类可读的标题status表示当前状态proposed、in-review、implemented、deprecated 等priority是优先级tags用来分类。正文部分需求描述写背景和目的不要在这里写实现细节验收标准是必须满足的硬性条件测试样例是把验收标准转成可执行判断的实例。3.2 验收标准从“差不多”到“可判定”验收标准是整个 spec 的灵魂也是最难写的部分。我见过太多人在这里犯同一个错误写得像文学。举个例子。一条验收标准写成“系统需要有良好的安全机制”这句话你没法判断是否通过。什么叫良好谁能拍板但改成“用户密码连续错误 5 次后锁定账号 15 分钟”这就变得可判定。我总结了三个原则运维到现在都很受用第一动词要具体。不要用“处理、支持、提供”要用“返回、拒绝、跳转、锁定”。第二数字要明确。超时时间、状态码、限制次数能写数字就写数字。第三每个验收标准只能有单一结论。不要在一条标准里写“A 返回 200并且 B 返回 401”拆成两条跑起来才知道哪条挂了。3.3 测试样例让Spec可运行测试样例是把验收标准变成机器能跑的东西。OpenSpec 的测试样例本质上是一种小型的契约测试定义输入给定期望输出然后由运行器去验证真实实现是否匹配。上面例子里的input和expected是最简单的形式。接口场景可以直接描述请求头、请求体和期望状态码函数场景可以描述输入参数和期望返回值。OpenSpec 本身不过度关心你怎么写测试逻辑它更关心你一定要把“输入-期望输出”这对关系写出来。我自己的建议是测试样例不要写得太大。一个用例只验证一条行为失败时你能立刻定位到是哪个验收标准没过。宁可多写几个小而精的用例也不要堆一个“全链路大用例”后者失败时排查成本非常高。3.4 与现有代码/接口的协同方式spec 不是悬浮在项目之外的纸面文档它要跟真实代码发生关系。在 OpenSpec 里一个 spec 从proposed变成implemented意味着对应的测试样例已经在真实实现上跑通了。实际项目里你可能会面对老接口、新需求并存的情况。这时候我建议不要指望一口吃成大胖子。一个接口如果有一堆历史包袱先挑最核心的两三个行为写成 spec跑通后固化下来再逐步扩大覆盖范围。这个做法很像把一团乱麻先剪出一根清晰的线头后续整理就会容易很多。另外spec 文件本身也应该纳入 Code Review。我们团队现在每次提交代码时如果涉及行为变更就要求同时提交或更新对应的 spec。评审代码之前先评审 spec确认改的东西是大家共识内的事情。这个流程一旦跑顺代码评审的争论会少很多。4. 实操记录从一张白纸到验证通过4.1 定义一个具体场景用户退出登录为了让整个流程更直观我带你完整走一遍我自己常用的小例子给一个已有的用户服务增加“退出登录”接口。这个场景足够小但覆盖了“正常流程、异常流程、退出后的连带影响”三类典型验收点。需求先说清楚用户携带有效 token 调用退出接口服务返回 200。退出之后旧 token 立即失效。没带 token 直接调用退出接口服务返回 401。4.2 动手写spec先在项目里执行openspec spec new auth-logout然后把模板内容替换成前面展示的那版 spec。写完后跑校验openspec validate如果输出没有任何警告说明 spec 结构合格了。注意这里说的是结构合格不代表实现已经满足它。此时 spec 的status还是proposed表示这是一个待落地需求。我会习惯性地把status改成in-review提交一次 MR让同事先评审 spec 本身。这一步虽然看起来“耽误事”但它的价值是从源头拦截不合理的需求——比如验收标准没法测试、需求范围过大等。4.3 生成验证入口并准备测试OpenSpec 的验证通常是这样运行的openspec run auth-logout它会把 spec 里定义的测试样例转换成一个个具体的检查然后驱动对应的测试适配器去请求真实服务。整个项目初次接入时这一步大概率会报“无法连接服务”之类的错误这是正常的因为你还没有实现。我一般会用 OpenSpec 的“导出”能力把 spec 转成一段结构化完整的需求描述直接粘贴给 AI 编程助手。这个操作的妙处在于你不再是让 AI “写一个退出接口”而是让它“实现这份 spec 中定义的行为并跑通验证”。后者对 AI 来说约束强得多产出质量也稳定得多。4.4 实现关键代码我这边用一个最简单的 FastAPI 示例来演示实现端。记住实现本身用什么语言、什么框架完全不重要OpenSpec 只关心行为是否满足 spec。from fastapi import FastAPI, Header, HTTPException app FastAPI() app.post(/api/logout) async def logout(token: str Header(None)): if token is None: raise HTTPException(status_code401, detailmissing token) if not token.startswith(Bearer ): raise HTTPException(status_code401, detailinvalid token) token_value token.removeprefix(Bearer ) if not is_valid_token(token_value): raise HTTPException(status_code401, detailinvalid token) revoke_token(token_value) return {message: logged out}这里面的is_valid_token和revoke_token是伪代码实际项目里对应你已有的鉴权模块。重点在于实现必须覆盖 spec 列出的所有验收标准尤其不能漏掉“未带 token 返回 401”这条异常分支。4.5 跑通验证并记录结果代码写完启动本地服务然后再次执行openspec run auth-logout我预期第一次跑的时候大概率会有一条失败比如“退出后原 token 失效”在真实代码里还没做。这时候别慌这是 SDD 的正常节奏有失败说明 spec 和实现之间的确有 gap。修 gap要么改实现要么发现 spec 写得不合理就改 spec。最终目标是让该 spec 的所有测试样例变成绿色。全部通过之后再把 spec 的status改成implemented。这样整个仓库里哪些需求落地了、哪些还在讨论中一眼就能看出来。5. 常见问题与排查技巧实录5.1 Spec写了但验证不跑最常遇到的情况是spec 文件明明写好了openspec run却提示“没有找到匹配的 spec”。我排查过很多次原因基本是三种。第一spec 文件放错了目录。文件没有放在specs目录下或者放在子目录但命名方式不符合配置里的扫描规则。第二id不匹配。运行命令时写的 id 和文件 front matter 里的 id 不一致系统匹配不上。第三YAML 格式有问题。front matter 某个字段缺了引号或者缩进错位导致整个文件解析失败。我的建议是任何时候先跑openspec validate。它是最快的体检工具能暴露绝大多数低级错误。如果 validate 通过但 run 不跑就把输出信息贴到终端里逐行看OpenSpec 的错误信息一般都会指向具体的 spec 文件和字段。5.2 测试与实现不匹配还有一类问题测试样例定义得很理想但实现跑起来就是过不了。这时候要区分是“实现错误”还是“样例错误”。我遇到过一次很典型的例子我写测试样例时约定退出成功后原 token 访问受保护资源返回 401。但实现里 token 是用 JWT无状态设计的服务端根本没有“销毁”这个动作退出后 token 在有效期内依然能用。这就是需求与技术方案冲突了。最后的解法不是硬改代码而是回到产品层面确认用户退出后原 token 是必须立即失效还是说等它自然过期也可以。如果产品方明确要立即失效那就得引入 token 黑名单或者换有状态的会话凭证。这个问题必须在 spec 阶段想清楚否则代码白写。5.3 多人协作时经常冲突OpenSpec 的 spec 也是文件多人同时改同一个 spec 就会产生合并冲突。这本身不可怕可怕的是冲突过程中把验收标准悄悄改了。我们团队的约定是spec 文件只由功能负责人修改其他人在 MR 里提意见不要直接改别人分支上的 spec。另外spec 要尽量拆小一个功能一个 spec避免出现一个巨型 spec 被五六个人同时碰的情况。给 CI 里加一道检查也很重要。每次有新代码合入主分支之前强制执行openspec validate格式不合格直接让流水线红掉。成本极低收益是长期稳定的。5.4 排查技巧速查表现象常见原因处理方式validate 报 YAML 错误字段缩进、引号、特殊字符导致用 YAML 格式化工具检查或重贴模板run 找不到 specid 不匹配或目录结构不对核对 id确认文件在 specs 下跑 validate测试全部失败服务未启动或测试适配器地址配置错误检查 config.yaml 里的 baseUrl、端口先手动 curl 服务单条用例失败实现没覆盖该分支或样例期望值不对明确失败字段对照验收标准逐条检查合并冲突频繁一个 spec 太大多人同时修改拆分 spec指定文件负责人5.5 分享一个排查时的独家技巧debug spec 的时候不要只看通过不通过要会看失败信息里的“上下文”。OpenSpec 的失败输出通常会带着输入参数和实际响应。我每次都会把实际响应和 expected 逐字段对比而不是急着改代码。如果实际响应是一个 500说明大概率是代码异常跟 spec 逻辑无关如果实际响应是 200但返回体字段名跟你预期不一致那可能是契约问题要去对齐命名。定位方向不同处理方式完全不同。这个区分能帮你省掉大量瞎猜的时间。6. 落地心得与避坑经验6.1 最容易被低估的三个坑第一贪大求全。我刚引入 OpenSpec 时恨不得把所有历史功能一次性全部写成 spec结果规范文件堆积如山维护跟不上最终全部沦为摆设。合理做法是只对新需求、正在重构的模块写 spec存量代码用二八原则逐步补。第二把 spec 当成写完之后就锁死的合同。需求变了spec 不更新那测试就会开始误报团队也会逐渐失去对 spec 的信任。SDD 里的 spec 应该是活文档它需要跟着业务一起演进。每次变更都同步更新 spec这应该作为开发流程的一部分而不是额外负担。第三忽略了 spec 评审。如果你只在写完代码之后跑一遍验证那本质上还是传统流程只是多了一层测试。真正的收益在源头让产品、开发、测试坐在一起先审 spec。这个动作能把很多需求问题挡在编码之前省下的返工时间是立竿见影的。6.2 团队落地的建议如果你不是一个人瞎折腾而是想把 OpenSpec 带进团队我建议从一个小模块试水别一上来全团队铺开。挑一个两周迭代内要交付的新接口让两三个人先跑通全流程把经验沉淀成团队内部文档。然后把“spec 写好了没”加进需求评审的检查项。我不建议强制命令大家写而是要在试用过程中让大家尝到甜头。比如有人因为 spec 清晰少改了三遍代码这个体验比任何制度都好用。数据上可以观察几个指标需求变更导致的返工次数、测试用例与需求的对齐度、AI 生成代码的一次通过率。这几个数据在引入 OpenSpec 前后对比会很明显。6.3 下一步可以玩什么第一期到这里OpenSpec 的基础使用已经能覆盖日常开发了。接下来可以继续深入的方向至少有三个一是把 OpenSpec 验证集成进 CI/CD 流水线让每次提交都自动跑规格验证二是研究它和多智能体协作让 AI 编程助手直接消费 spec 并自主迭代实现三是用它做需求追踪把 spec 状态看板化和周报打通管理层也能直观看到需求落地进度。我自己现在的习惯是接到新需求第一件事不是写代码也不是画脑图而是打开命令行敲一句openspec spec new。这个动作会让我的大脑从“马上动手”变成“先想清楚边界”对需求的理解深度完全不一样。很多朋友问 SDD 到底有没有用我的回答是你先坚持三个迭代不要中断再回来看代码返工率答案自己会浮出来。