☰
规格驱动开发实战:OpenSpec 如何让需求和代码不再脱节
2026/11/2 7:44:51 网站建设 项目流程

做后端开发的这些年,我越来越确定一件事:软件项目里最贵的从来不是写代码,而是把一群人脑子里对同一个功能的理解对齐到完全一致。尤其这两年大家开始用 AI 编码助手之后,问题变得更明显——你让 AI 改代码,它只能根据仓库里已有的东西猜测需求,而仓库里往往什么都没有。OpenSpec 就是冲着这个问题来的,它是一套规格驱动开发(Spec-Driven Development)的工具链:用结构化 Markdown 描述系统能力,用变更提案管理每一次改动,再通过配套 CLI 把规格变成人和 AI 都能直接消费的工程资产。这篇内容适合正在尝试 AI 辅助开发的团队,也适合那些被"文档过期、测试和需求脱节、改个功能要翻遍 PR 历史才能拼出需求全貌"折磨的开发者。我会从为什么需要规格驱动讲起,逐步拆解 OpenSpec 的目录结构、核心命令、完整工作流,以及我实际跑下来踩过的坑。

1. 为什么需要规格驱动开发:需求到代码之间那截断掉的路

1.1 传统流程里最贵的不是写代码

我见过太多项目是这个状态:需求出现在产品评审的 PPT 里,补充在 IM 聊天记录里,最终以某种被扭曲过的形态进入代码库。文档不是没写,是写了没人敢信——因为大家心里都清楚,文档更新永远滞后于代码。等到功能上线三个月后,新同事想改这块逻辑,只能顺着函数调用关系反推需求,推出来的版本还未必是产品当时拍板的版本。代码评审的时候,大家争论的往往也不是实现方式,而是"这个功能到底该不该这么做"。这不是执行力的问题,是流程里缺了一个环节:需求从来没有被沉淀成一份持续可信的资产。

测试先行的方法论(TDD)解决了一部分问题,它让代码有了可回归的保障,但单元测试验证的是"代码是否按我理解的意图运行",而不是"代码是不是团队真正想要的行为"。BDD 往前走了一步,用 Given/When/Then 描述行为场景,可它最终产出的仍然是测试代码,业务方看不懂,也不大愿意维护。规格驱动开发的思路完全不同:它把"系统应该具备什么能力、每个能力的行为边界是什么、每一次改动到底改变了什么"这三件事,直接变成仓库里的一等公民,和代码一样接受评审、校验、版本管理。

1.2 规格驱动开发和 TDD、BDD 的边界在哪里

规格驱动不是要替代测试,而是把测试前面那段"定义行为"的过程补上。TDD 回答"代码对不对",BDD 回答"功能是否符合预期",规格驱动回答的是更靠前的问题:"系统该具备什么能力,以及我们如何验收这个能力"。它产出的不是测试用例代码,而是任何人——包括产品、测试、新入职的工程师、以及没有感情的 AI 助手——都能读懂的规格描述,测试只是这些规格的验证手段之一。

方法论核心产物回答的问题适用阶段
TDD单元测试代码是否按预期运行实现细节
BDD行为场景与验收标准功能是否符合业务预期需求验证
规格驱动开发 / OpenSpec能力规格 + 变更提案系统该有什么能力、改动影响什么需求定义与变更管理

这个区别在实际协作里非常明显。以前我拿到一个需求,第一反应是"先建分支写代码,边写边问产品"。用 OpenSpec 之后,第一反应变成"先把这条能力写进规格,把验收示例列清楚,评审通过再动手"。看起来多了一步,实际上砍掉了大量返工。

1.3 为什么 AI 时代规格驱动突然变得重要

AI 编码助手普及之前,规格写不写,影响的是"人跟人之间沟通的效率"。AI 普及之后,影响的是"AI 能不能在一开始就做对"。LLM 读代码的能力很强,但它只能根据上下文猜测需求;你要它改一个登录模块,它大概率会参考项目里现有的登录实现、或者它训练数据里的常见模式,而不是你产品经理脑子里那个"必须锁定 15 分钟"的规则。OpenSpec 从设计上就是给 AI 消费的:它有专门的命令把规格汇总成上下文文件,让编码助手在动手之前先读规格。这一点我后面会专门展开讲。把规格喂给 AI,它的生产力才真正踩在需求的地基上,而不是踩在它自己的想象力上。

2. 环境准备与初始化:跑通 OpenSpec 的本地链路

2.1 安装 CLI 和前置检查

我本地是 macOS,Node 环境本来就有,安装走 npm 很顺畅:

npm install -g openspec openspec --version

如果你的机器没有 Node 环境,先去装 LTS 版本,再装这个 CLI。装完之后我建议先跑一条openspec doctor,它会检查当前仓库的状态、CLI 的配置是否完整、有没有缺依赖,相当于一个体检命令。不要觉得这一步多余,我第一次在旧项目里跑openspec init之前没体检,结果初始化之后 validate 一直不过,折腾了半天才发现是仓库里有一个历史遗留的openspec老目录,新旧结构冲突了。先诊断再动手,能省不少事。

提示:OpenSpec 的 CLI 还处在快速迭代期,不同小版本的子命令和参数可能略有差异。我在文里写的命令按我实际使用到的版本来,如果执行报错,优先看官方仓库 README 的最新用法。

2.2 openspec init 到底初始化了什么

在项目根目录执行:

openspec init

会生成这样一个骨架:

openspec/ ├── project.md # 项目总体描述 ├── specs/ # 已确认的能力规格 └── changes/ # 待实施和已归档的变更

注意,它不会给你生成任何示例代码,也不会动你现有的业务目录,只是把规格管理的骨架搭起来。我最开始误以为它会像脚手架一样生成一套完整的目录规范,实际上更接近"在仓库里建立一个规格工作区"。

project.md 是这个骨架里最容易被人忽略、但影响最大的文件。它用来描述项目是干什么的。我建议至少要写清楚三件事:项目解决什么问题、核心用户是谁、主要技术栈是什么。不要写"这是用户中心",要写"这是面向内部运营人员的用户权限中心,负责账号、角色、数据权限三块,前端 Vue,后端 Go,所有接口走内部网关"。这个文件后面会被 openspec ai 命令汇总成 AI 上下文,写得越具体,AI 对后续规格的理解偏差就越小。

2.3 目录结构逐层拆解:specs 和 changes 的分工

specs/下面是按领域(domain)拆分的目录,比如auth、billing、notification。每个领域目录里有两个文件:spec.md和capabilities.md。spec.md描述这个领域的边界和职责,相当于领域的简介;capabilities.md是这个领域的能力清单,也是整套路线的核心资产。能力清单里每条 capability 都包含一段行为描述和若干验收示例,后面我会详细讲怎么写。

changes/下面是变更提案目录,命名规范是YYYY-MM-DD_短横线名称,例如2025-01-20_add-login-lock。每个变更提案目录里默认有proposal.md、tasks.md、tests.md三个文件。可以这样理解:specs/是"系统当前应该长什么样",changes/是"我们打算把它改成什么样"。改动落地之后,变更提案会被归档,其内容回流到specs/里,这就是规格保持新鲜的机制。记住这个"提案-实施-归档"的循环,后面整个工作流都是围着它转的。

3. 核心工作流:一次真实的功能变更从提案到归档

3.1 第一步:把能力写清楚,和实现彻底解耦

假设我要给项目加一个"邮箱密码登录"的能力。在openspec/specs/auth/capabilities.md里,我会这样写:

# Capabilities: 用户认证 ## Capability: 邮箱密码登录 系统允许已注册用户使用邮箱和密码登录,登录成功后返回带过期时间的会话令牌。 ### Examples - [ ] 使用正确的邮箱和密码登录 -> 登录成功,返回令牌 - [ ] 使用未注册的邮箱登录 -> 返回 401,提示"用户不存在" - [ ] 连续输错密码 5 次 -> 账号锁定 15 分钟,并提示剩余锁定时间

这里有两个关键点。第一,描述聚焦行为结果,不写实现细节。我明确不写"调用 UserService.login()"这类话,因为一旦写了实现,规格就和代码耦合了,代码重构时规格就废了。第二,验收示例用"前置条件 -> 预期结果"这种任何人能看懂的自然语言,而不是测试代码。这样的好处是产品经理能看懂、能纠错,测试人员能照着写用例,AI 也能照着实现。你甚至可以把它理解为"用人类语言写的契约测试"。

很多人在这一步会犯一个毛病:规格写得像需求文档,事无巨细,恨不得把按钮的颜色都写进去。我的经验是,规格只写"行为承诺",也就是外部能够观察和验证的东西;内部怎么实现、字段怎么命名、表怎么设计,都不是规格该管的事。管得越宽,维护成本越高,最后规格必死。

3.2 第二步:创建变更提案,把"为什么"和"做什么"分开

能力规格更新之后,接着创建变更提案。命令大概是:

openspec change add-login-lock

执行后会在openspec/changes/2025-01-20_add-login-lock/下生成proposal.md、tasks.md、tests.md三个文件。我理解这个命令的定位是"开一个需求工单",它强制你在动手写代码之前,把变更的理由、范围、任务、测试计划一次性想清楚。

proposal.md要回答三个问题:为什么改(业务背景和动机)、改什么行为(对现有能力的修改增量)、影响什么(哪些既有能力可能被波及,是否需要迁移)。我见过写得好的 proposal,几乎可以当技术方案评审文档用。tasks.md是拆解后的实施任务,粒度要控制在一个任务对应一次可验证的改动,这样后续无论是人还是 AI 来执行,都能逐条勾选。tests.md写验收测试计划,注意这里是"计划",不是测试代码,它描述本轮变更需要验证哪些场景,真正落到代码里的测试在后面实现阶段完成。

创建提案这个动作本身就是在逼团队"先想清楚再动手"。我实测下来,凡是 proposal 写得含糊的改动,实现过程中返工率明显更高;凡是 tasks 拆得清楚的,AI 都能一步步执行到位。

3.3 第三步:validate 是质量闸门,不是摆设

提案写完之后,跑一遍:

openspec validate

这个命令会检查规格和变更提案的合法性:目录结构是否完整、文件名是否符合规范、capability 的 ID 是否有冲突、change 目录命名是否带日期前缀、Markdown 结构是否合规。validate的意义在于,它把"规格是否合格"变成了一道机器可判定的检查,而不是靠人自觉。我会把它加进 CI,只要有 PR 动了openspec/目录,就跑一次校验,不合格直接红掉。

但一定注意:validate通过只代表规格的"形式"合法,不代表系统的行为真的符合规格。行为是否符合,要靠tests.md里定义的验收场景去验证。把 validate 理解成"格式和结构闸门",把真正的测试理解成"行为闸门",两个都要过,这个流程才算完整。

3.4 第四步:实施、验证、归档,规格绝不能漂移

实现阶段就按tasks.md逐条推进。如果是人肉实现,按任务清单开发、提交、自测;如果是 AI 协作,把能力和提案的上下文喂给它,让它按任务清单执行。我习惯在实现过程中随时对照规格里 Examples 的每一条,写一个过一条,确保行为最终能对上。

功能合并进主干之后,最后一步是归档:

openspec archive 2025-01-20_add-login-lock

归档动作会把这次变更的能力更新写回specs/下的capabilities.md,并把changes/里对应的提案目录清理或标记为已归档。这个闭环是我认为 OpenSpec 最值钱的设计:它保证了规格不会被遗忘,也不会和代码漂移。归档的时机必须是功能合入主干之后,而不是实现过程中或者测试刚写完的时候,否则规格可能记录了还没真正生效的行为。

4. 命令行速查与进阶玩法:从单机使用到团队协作

4.1 常用命令速查表

我整理了一份自己日常用得最多的命令表,按使用频率排序:

命令用途我什么时候用
openspec init初始化 OpenSpec 工作区新项目接入、旧项目首次搭建
openspec validate校验规格和变更的合法性每次改完规范文件、CI 里必跑
openspec list列出所有领域、能力和变更快速查看仓库里现在有什么规格
openspec change <name>创建变更提案新需求开始之前
openspec archive <change-id>归档已合并的变更功能合并主干后立刻执行
openspec doctor检查环境和仓库状态遇到诡异报错时第一排查动作
openspec ai生成 AI 上下文文件准备让 AI 编码助手接手任务时

openspec list我特别推荐养成习惯。它比翻目录结构直观得多,一个命令能看出整个系统当前暴露了哪些能力、有哪些变更还挂着没归档。每周跑一遍基本等于给自己做一次项目体检。

4.2 给 AI 编码助手喂规格:openspec ai 是怎么用的

这才是 OpenSpec 在当前这个节点最有想象力的功能。openspec ai会把整个规格工作区的关键信息汇总成专门的上下文文件(我的版本里生成的是 AI 友好的汇总说明,具体输出物随版本演进),把project.md、领域能力、正在进行的变更全都整理进去。实际协作时,我让 Copilot 或 Claude 之类的编码助手在动手前先读这个上下文,再让它按tasks.md逐条实现。

我一个很深的体会:让 AI 写代码之前,先花十分钟把规格喂好,比你在提示词里写一千字"你是一个资深工程师"有用得多。因为规格里是你们团队特有业务逻辑,这是 AI 训练数据里不可能有的东西。之前我让 AI 改过一个支付回调的逻辑,不喂规格时它给我写出了一套常见的通用处理流程,看着没问题,实际上和我们业务对账规则完全对不上;喂了规格和验收示例之后,它立刻按我们的业务行为来实现了。规格驱动开发解决的不只是人的沟通问题,也解决了 AI 幻觉问题。

4.3 多领域拆分和 CI 集成的实战建议

领域拆分的粒度需要拿捏。我见过把整个系统塞进一个specs/core的,结果文件巨大,验证和阅读都费劲;也见过拆得过于零碎,一个只有两条能力的小模块也单独建一个 domain。我的经验是:一个领域对应一个可以独立演进和独立验收的业务模块,比如认证、计费、通知这类边界清晰的模块就合适。两个领域之间如果经常一起改动,说明边界划错了,该合并。

CI 集成很简单,我在流水线里加了一个阶段:

openspec validate

只要有改动碰了openspec/目录就触发。团队规范上我也定了一条硬规矩:没有提交变更提案的改动,不允许直接改规格。这不是工具限制能强制的事,得靠团队约定,但 OpenSpec 已经把检查手段给齐了,剩下的就是执行问题。

5. 常见问题与排查实录:我踩过的坑,希望你别踩

5.1 validate 报错的几类典型原因

我自己用下来,validate报错集中在这么几个原因:

报错现象常见原因解决办法
目录结构校验失败openspec/下缺project.md或目录名不规范按 init 生成的骨架逐层比对,不要手搭
capability 冲突不同领域文件里出现了重复的 Capability 名称改名前先openspec list查重
change 命名不规范目录名没带YYYY-MM-DD_日期前缀重命名目录,或直接用openspec change创建
Examples 格式解析失败预期结果的写法不统一,有的少箭头分隔每条示例统一用条件 -> 结果的格式
版本升级后老配置失效CLI 升级后配置格式有变动跑openspec doctor,按提示重新生成配置

最气人的一次是:我手欠把changes/里的一个归档目录手动删了,结果validate报引用缺失,我花了半天才意识到是归档流程没用命令走,把specs/里的更新也绕过去了。从此我定了个规矩:归档必须用命令,决不手动改specs/和changes/的对应关系。

5.2 规格粒度怎么把控:写多细才算合格

这个可能是所有刚上手规格驱动的团队问得最多的。我的判断标准很简单:如果实现的时候你需要去翻代码才能理解这条规格,那就是太粗了;如果规格里出现了 SQL 语句、函数名、字段名,那就是太细了。合理的粒度是"行为承诺 + 可观察的验收结果",比如"登录成功后返回带过期时间的令牌",不写"生成一个 24 小时有效的 JWT,存在 Redis 里 key 为 user:{id}:token"。

还有一条经验:写规格时,把"系统会做什么"和"系统不会做什么"都写清楚。边界条件往往比主路径更能体现业务规则。比如"未注册邮箱返回 401"这条示例,比"正确登录返回 200"更能让实现者少犯错误。一条能力如果主干行为 3 条示例写得完,加上边界条件大概会到 5-8 条,这个数量级是比较健康的。

5.3 团队落地时真正难的不是工具,是习惯

最后说点工具之外的事。OpenSpec 这条链路,单人使用很简单,难的是团队一起用。我遇到过最大的阻力是:规格评审没人认真做,大家觉得这不过是"另一种文档",于是规格很快变成了新的没人看的文档。我的对策是把规格评审纳入 PR 流程——凡是涉及行为变更的 PR,必须附带对应的提案文件链接,评审人第一眼看提案,第二眼看代码。这个习惯养成之后,规格的质量和代码的质量基本就同步了。

另一个常见的团队问题是变更提案和 issue 的关系没理清。我现在的方式是:issue 里只写业务诉求,提案文件里写技术方案和行为变更,两边互相引用,但职责不混。issue 会关闭,提案会归档,规格会更新,整个链条是闭环的,不会出现"issue 关了但没人知道改了什么"的情况。

结尾:一点个人体会

把这些经历串起来看,我觉得规格驱动开发最大的价值,不是产出了一批漂亮的文档,而是倒逼团队在动手之前把行为说清楚。这种"说清楚"的价值,在没有 AI 的时候体现在减少返工和扯皮,在有 AI 的时候体现在让机器从一开始就站在正确的地基上。最后分享一个我坚持的小习惯:每周五花半小时,跑一遍openspec list,看看这周新增了哪些能力、哪些变更还挂着没归档。这个动作比写周报对我有用多了,它让我对项目的理解永远停留在最新状态。如果你也受够了需求和代码长期脱节的日子,不妨从一个小模块开始试试 OpenSpec,先写一条能力,跑通一个变更的提案到归档,你会明显感受到差异。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询