news 2026/10/16 11:23:17

规范驱动开发实战:用OpenSpec把需求变成可执行验收标准

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
规范驱动开发实战:用OpenSpec把需求变成可执行验收标准

我第一次接触到 OpenSpec,是在接手一个已经折腾了两周的搜索模块。需求文档洋洋洒洒写了一万多字,代码也堆了一千多行,可每次评审都对不上,测试更不知道该按什么标准写。后来我把核心行为整理成一份 spec,把所有验收点变成可运行的检查项,团队终于有了共同语言。这就是 SDD(Specification-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_code=401, detail="missing token") if not token.startswith("Bearer "): raise HTTPException(status_code=401, detail="invalid token") token_value = token.removeprefix("Bearer ") if not is_valid_token(token_value): raise HTTPException(status_code=401, detail="invalid 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 到底有没有用,我的回答是:你先坚持三个迭代,不要中断,再回来看代码返工率,答案自己会浮出来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 11:22:50

SSM+Vue酒店客房预定系统:前后端分离架构与调试指南

1. 项目定位:一套能被“真正跑起来”的Java Web练手系统先说说这套系统的定位。基于SSM(Spring Spring MVC MyBatis)的酒店客房预定管理系统,是一个非常典型的Java Web全栈项目。标题里几个关键词值得拆开看:SSM说明…

作者头像 李华
网站建设 2026/10/16 11:22:12

acled-parser:ACLED冲突事件数据的清洗与解析实践

简介:acled-parser 是一份面向 Java 开发者的 ACLED 数据辅助解析工具包,适用于需要批量读取、清洗并导出“武装冲突地点与事件数据”的研究人员与数据分析师。资源压缩包共 11 个文件,体积约 12.4MB,主要包含 4 个 Java 源码文件…

作者头像 李华
网站建设 2026/10/16 11:21:41

高性能图像处理库选型与优化:从瓶颈定位到算子级压榨的完整实践

接手一个实时质检项目的时候,我原本认为所谓高性能图像处理库,核心优势不外乎算子的种类、算法的精度、文档的完善程度。可真等我把图像处理流程塞进产线环境,用高速相机一帧一帧灌数据时,才发现真正决定系统上限的,是…

作者头像 李华
网站建设 2026/10/16 11:21:10

数组原地操作与双指针技巧:从移动零到移除元素的算法模板解析

最近我在整理自己的刷题清单,算法系列的第二篇,我选了数组原地操作这个方向。这个专题里最典型的组合,就是我自己清单里编号238的移动零,和编号27的移除元素。这两道题初看都很简单,移动零是把数组里的0都挪到末尾&…

作者头像 李华