做后端开发这些年,我发现自己最耗神的环节其实不是写代码,而是“对需求”。需求文档、代码实现、验收标准这三样东西,经常在同一周内各自漂移成三个不同版本,最后开会全靠拍桌子定谁对。最近我把 OpenSpec 这套规范驱动开发的工具链引入到工作流里,先在十几人的小组试跑了两个迭代,效果比预想中实在。OpenSpec 的核心思路一句话能说清楚:把散落在文档软件里的需求描述、设计约束和验收标准,变成代码仓库里有结构、可校验的文件,并围绕这些文件提供生成与验证能力。它适合正在做 AI 辅助开发的团队,也适合任何被“需求—实现—验收”对不上号折磨到崩溃的开发小组。
1. 为什么需要 OpenSpec:需求文档与代码之间的断头路
1.1 多数团队的规格管理现状
先说个真实场景。需求评审会上产品经理口头描述了一个“历史订单可以按时间筛选”的需求,开发在 Jira 里记了三行笔记,测试在 Excel 里写了几条用例,两周后功能上线,产品经理一看发现筛选范围理解错了,测试也说预期结果跟文档对不上。问题出在哪?出在整个团队其实没有一个统一、真实、可持续维护的“需求真相来源”。
传统的规格管理就是静态文档。PRD 存在飞书文档里,接口设计存在 Wiki 上,验收标准散落在测试用例里,代码逻辑则在代码仓库内。这四样东西各自独立,没有任何机制保证它们同步。每次需求变更,开发可能会改代码,但文档大概率得不到同步更新,等到下一个迭代的人来改这块功能时,看到的只剩代码本身,而代码能跑通为什么这么写,没人说得清。
我在团队里做过一次排序调查,超过一半的人认为“理解历史需求”比“写新代码”更耗时。这背后就是规格和实现长期脱钩积累下来的认知债。规格管理不是文档问题,是工程效率问题。
1.2 OpenSpec解决问题的三个抓手
OpenSpec 应对上面这摊子事,核心是三招。
第一招是规格文件进仓库。需求描述、验收标准不再是某个外部文档软件里的页面,而是跟随代码一起提交、一起评审、一起合并的普通文件。文件放在仓库里,就意味着它可以走 MR 流程,有历史记录,有 review 记录,有作者信息。规格每次变化都能像代码 diff 一样被审阅,团队对需求的每一次调整都有迹可循。
第二招是变更最小化。OpenSpec 的单位是“提案”(Proposal),一个提案只描述一次独立、可交付的变更。这个设计逼着团队把大需求拆成小批次,每次只聚焦一个问题。小批次的好处很朴素:评审变快了,开发周期短了,出一个错也不至于影响全局。
第三招是自动校验。规格文件是半结构化的,OpenSpec 的 CLI 可以检查提案的格式是否合法、必填字段是否存在、依赖的提案是否已经合并、验收标准的表述是否符合规范。校验通过后,可以进一步生成实现任务清单、测试模板和代码脚手架。也就是说,规格不再只是给人看的备忘录,它变成了能自动驱动后道工序的半成品原料。
1.3 它和 TDD、BDD 是什么关系
很多人第一次听到“用文件驱动开发”,第一反应是“这不就是 TDD 或者 BDD 吗”。我跟团队解释的时候会画一条线来区分:TDD 管的是函数级行为,你针对一个方法写红绿测试,确保它对输入给出正确输出;BDD 管的是业务行为描述,用 Given-When-Then 让三方对齐;OpenSpec 管的层级更高,它先把“要做什么、为什么做、怎么验收”沉淀成结构化文件,再把这些文件作为生成任务清单和测试用例的输入源。
打个比方:TDD 是施工队在砌每一块砖之前先量一次尺寸;OpenSpec 则是在动工前先把整栋楼的设计图纸存进工程系统,并且让每一块砖的数据都能被追溯。两者不冲突,反而可以叠加使用。我在项目里就是这么干的:OpenSpec 负责需求和验收层面的对齐,具体到函数实现时,照旧用单测兜底。
为了说清楚这个差别,我列过一张对比表给组里新人参考:
| 维度 | 传统工作流 | OpenSpec 工作流 |
|---|---|---|
| 需求载体 | PRD/文档/Wiki | 仓库内规格文件 |
| 变更记录 | 依赖人工回填 | 随 MR 自动留存 |
| 验收标准 | 测试用例文档 | 结构化数据,可校验 |
| 任务拆解 | 靠 PM 人肉拆 | 从提案半自动生成 |
| 与代码关系 | 独立,易失配 | 同仓库,天然同源 |
| 学习成本 | 低但有长期维护成本 | 前期有成本,后期省流 |
表格一列,组里那几个“文档无用论”的信徒也承认:问题不是文档没用,是文档离代码太远,而 OpenSpec 把距离拉到了几乎为零。
2. OpenSpec 的工作方式:把规格写进仓库,让变更可追溯
2.1 提案机制:一次变更一个文件夹
OpenSpec 最核心的抽象就是提案。所谓提案,本质上是一个独立目录,里面装着这次变更的所有规格材料。我习惯的目录结构长这样:
openspec/ ├── proposals/ │ ├── 2025-03-order-history/ │ │ ├── proposal.md │ │ └── specs/ │ │ ├── 001-filter-by-time.md │ │ └── 002-pagination.md │ └── 2025-04-order-export/ │ ├── proposal.md │ └── specs/ │ └── 001-csv-export.md └── README.md每个提案目录里,proposal.md是总纲,说明这次变更的背景、目标、影响范围和关联需求;specs/下面放具体规格文件,一个文件描述一个行为点。这个结构强制执行了一个很好的习惯:想改需求,先去改规格文件;想加功能,先新建提案。代码反而是“照着规格长出来的”。
提案的命名我建议直接用日期加短横线语义词,例如2025-03-order-history。日期天然排序,语义词让人一眼知道改的是哪块。早期我们有人用final_v2_really_final这种名字,后来统一规范,这类问题就再没闹过。
2.2 规格文件的核心组成
一份标准的specs文件承担两件事:描述行为,定义验收。OpenSpec 一般建议用 Markdown 写,头部是结构化字段,主体是自由描述加验证条款。我写过一个典型的订单筛选规格文件,结构可以参考:
--- name: filter-by-time description: 历史订单列表支持按时间范围筛选 status: active related_requirements: - req-001 --- ## 行为描述 在订单历史页,用户可以通过起止时间筛选订单,默认展示最近 30 天。 ## 验收标准 - SC-001: 用户选择一个时间范围后,列表只展示该范围内的订单。 - SC-002: 默认时间范围为最近 30 天,且日期选择器能正确回填。 - SC-003: 起止时间跨度超过 90 天时,前端给出友好提示,不发起请求。字段和验收标准尽量用短句,一件事一条。我一直提醒组员:验收标准写得好不好,直接决定后面 AI 生成的测试用例准不准。如果你写“筛选功能正常”,那 AI 和测试都只能摸黑干活;写成 SC-001 这种可判定的条款,后面的工具链才有的放矢。
2.3 从规格到生成的闭环路径
规格文件就位后,OpenSpec 的 CLI 会把它们变成可操作的东西。我用这套工作流时,最常用的命令有三个:plan、generate和validate。
plan会读取提案里的验收标准,产出一份实施任务清单。它通常按“先改接口、再改前端、最后补测试”的顺序拆解,并标记每个验收标准对应的实现步骤。generate则是根据规格文件生成测试模板或者代码骨架,相当于把验收标准翻译成半成品的测试用例,开发者直接在模板里填具体断言就行。validate负责检查规格文件本身有没有问题,比如字段格式、依赖关系、状态是否合法。
这里的核心思想是把人从“写重复代码”中解放出来。规格文件是上游,代码和测试是下游。上游稳定,下游就能自动化生产。哪怕自动化只覆盖了百分之五十的样板代码,剩下的工作也已经轻松很多。
3. 动手实操:从初始化到跑通第一个提案
3.1 安装与初始化
OpenSpec 以 CLI 工具为主,安装方式根据自己环境的包管理器选。我这边用的是 npm 全局安装:
npm install -g @openspec/cli安装完先看一眼版本,顺便确认核心命令可用:
openspec --version openspec --help然后找一个空目录初始化。我的习惯是直接在业务代码仓库根部初始化,让规格和代码真正同源:
mkdir my-service cd my-service git init openspec init初始化完成后,仓库里会多出一个openspec/目录,自带 README 和一个示例提案。这个阶段建议顺手把示例提案跑一遍validate和plan,确认环境能正常工作,再删掉示例自己写。
提示:如果你用的是 pnpm 或者 yarn,安装命令换成 pnpm add -g @openspec/cli 或 yarn global add @openspec/cli 即可。不同发行版的命令命名可能有细微差异,以官方仓库 README 为准。
3.2 创建第一个提案
初始化好之后,用子命令创建提案模板:
openspec proposal new order-history执行完,openspec/proposals/下会多出一个order-history目录,里面默认生成proposal.md和specs/文件夹。刚生成的proposal.md自带注释和示例,提醒你要填哪些字段。我通常拿到手先改三样:标题、背景、目标。这个文件不用长,但要把“为什么做这件事”说清楚。背景不写清楚,两个迭代之后看的人只能靠猜。
3.3 写一份有验收标准的规格
接着填规格文件。我在specs/下新建一个描述文件,比如001-filter-by-time.md,内容就按上一节给过的模板写。
这一步我特别说一下怎么定验收标准。很多同学第一次写会写成“性能良好”“界面友好”这种不可判定的话,这在 OpenSpec 里等于没写。我的判断标准很简单:如果这句话没法用脚本或断言来验证,那它就不配叫验收标准,只能叫期望。合格的验收标准必须包含明确条件、可观察结果和边界约定。
比如 SC-003 这条“起止时间跨度超过 90 天时,前端给出友好提示,不发起请求”,就包含了条件(超过 90 天)、结果(给出提示、不发起请求)、边界(90 天整点为界怎么处理,你需要在实现时精确对齐)。有了这种规格,后端接口返回什么样的错误码、前端在哪一层拦截,都能顺藤摸瓜找到答案。
3.4 校验提案的正确姿势
写完文件,跑校验是最能获得安全感的一步:
openspec validate order-history正常输出会是一个状态清单,告诉你每个验收标准格式是否合法、规格字段是否完整、依赖是否满足。如果规格里引用了不存在的需求 ID 或者验收标准编号重复,它会直接标红报错,并给出文件路径和在文件中的位置。
把validate塞进 Git 提交钩子和 CI 流程里,是我比较推荐的做法。它可以保证一旦规格文件进入主分支,就必然是结构合法的。这样后续接手的同事或 AI 工具读到的一定是可解析、可信赖的内容,而不是一份排版漂亮但没规矩的文档。
我第一次把validate接入 CI 后,团队里就再也没出现过“规格文件忘了填状态字段”这种低级事故。人总会忘,机器不会。
4. 在真实项目中推进一个完整提案的经验
4.1 提案的粒度怎么拿捏
提案拆小了,流程会变得琐碎;拆大了,又会退化成原来的大 PRD。我目前比较顺手的标准是:一个提案对应一个用户可感知的变更点,并且应当能在一到两天内开发完成。如果一个提案需要超过两天开发,或者要横跨多个不相关的功能模块,我会果断拆掉。
举个例子。做“历史订单导出”时,我没有一上来就建一个“订单工具集”的大提案,而是拆成两个独立提案:一个是“订单数据 CSV 导出”,一个是“导出任务异步化”。前者用户能直接看到下载按钮和文件,后者解决的是大订单量导出时接口超时问题。两步分开走,第一版即使同步导出性能差些,也能先让用户用上,再异步优化。每个提案合入主分支时都保持系统可用,这一点比一次憋一个大变更重要得多。
4.2 依赖关系怎么表达
一个功能经常站在另一个功能肩膀上。比如先有了查询接口,才能做导出功能。这种依赖关系我一般直接通过目录命名和related_requirements字段来体现。
目录命名按日期排时,后建的提案如果依赖先建的,我会在proposal.md里加一段“依赖说明”,写清楚依赖哪些提案、哪些规格文件,以及联调时需要注意的接口约定。validate阶段它也会检查这些依赖是否存在,如果引用了不存在的提案或字段就直接报错。这让依赖关系从一个“口头约定”,变成了机器可检查的契约。
4.3 与 AI 辅助编程工具配合的方法
这半年团队用了不少 AI 辅助开发的工具,从代码补全到基于对话的编码助手都有。OpenSpec 和这类工具搭配使用,效果出奇地好,前提是把规格文件作为上下文喂给 AI。
我的标准做法是:把当前提案的proposal.md和specs/文件路径贴给 AI 工具,然后要求它照着验收标准生成实现代码。因为验收标准是结构化的短句,AI 对它的理解准确率明显比自由文本高。生成完代码后,我再用对应的测试模板去校验 AI 的输出,哪里不符合验收标准,一眼就能看出来。
这个流程相当于给 AI 配了一份施工图。去年我们让 AI 直接生成新功能时,经常出现“代码看着对,但和需求不是一回事”的现象。引入 OpenSpec 后,AI 输出的偏跑率低了不少,因为验收标准就在提示词里,代码写到哪一步、该满足什么条件,都有据可依。说到底,AI 需要的不是更多的自由,而是更清晰的边界。
4.4 分支策略和审查流程
接入 OpenSpec 后,我把团队的开发分支流程微调成了两段式。
第一段是提案准备:从主干拉分支,只提交规格文件,不写业务代码。分支合入主干的唯一条件是validate通过,并有人审过规格内容是否和需求一致。这一步相当于把需求和设计冻结在主干上。
第二段是实现落地:基于主干的规格文件,再拉一个新的实现分支,照着规格写代码、写测试。合入前跑全部验收测试。这样一来,规格评审和代码评审被彻底分开,各自关注各自的问题。规格评审时不用看代码逻辑,代码评审时不用担心需求和实现失配。
我自己的体会是:这个流程一旦跑顺,MR 里撕扯变少了。以前代码评审经常演变成需求讨论会,现在需求问题在规格评审阶段就吵完了,代码评审只关心代码本身的质量。
5. 我踩过的坑和几条实用建议
5.1 提案命名与目录规范
刚开始推行 OpenSpec 时,我在命名上翻过车。有人用中文命名,有人用new_order_history,有人干脆叫test。目录名一旦混乱,后面的依赖写起来就是灾难。
后来我强制统一了几条规则:提案目录一律用日期加 kebab-case;规格文件用三位数字加语义词命名;提案内符号和 ID 全部小写并以下划线分隔。规则简单到不用查文档,保证了工具和人都能无歧义地引用。
5.2 别把规格文件写成 PRD
这是新人最容易犯的错。打开proposal.md写了一大段背景、竞品分析、运营要求,结果specs/下只有半句话:“功能要流畅”。这等于把以前的 PRD 换个地方又贴了一遍,毫无意义。
规格文件的价值在于结构化、条目化、可判定。背景可以提三两句,但重心永远是行为描述和验收标准。我自己的写作顺序是先写验收标准,再写行为描述,最后补背景。验收标准写不出来的需求,要么是没想清楚,要么是需求本身还不到开发时机。
5.3 校验工具不是摆设
我见过同事把validate当摆设,写完规格文件完全不跑,直接推分支。结果到了 CI 阶段,机器报出一堆格式问题,还得回头慢慢改。
我现在的习惯是编辑器里用自动保存触发校验,保存一次检查一次,顺手就把问题消化在萌芽阶段。同时把 CI 门禁打开,校验不过的 MR 不允许合入。这个自动化门槛不设,靠人的自觉,撑不过两周就会回到放任自流的状态。
5.4 什么时候不该用 OpenSpec
OpenSpec 不是银弹,它也有不合适的场景。我总结下来,三类情况我不会硬套。
探索型需求不要用。需求本身还在试错阶段,今天觉得 A 方案好,明天又推翻,硬写规格只会浪费大量修改成本,那点收益还不如直接原型来的快。
一次性脚本不要用。跑完就删的运维脚本或临时数据处理工具,给它写提案和规格纯属自嗨,没有任何沉淀价值。
小到一行配置的变更不要用。为了改一个超时时间就建提案,等于用牛刀杀鸡。这类改动直接走普通 MR 就行,别被流程绑架。
我之前也走过“为了用工具而用工具”的弯路,后来想明白了,OpenSpec 的边界很清楚:它适合需要长期维护、多方协作、验收要求明确的功能模块。在这个范围里,它把从需求到实现的链路理顺;出了这个范围,它确实是个累赘。
最后再分享一点个人体会。工具引入初期,最大的阻力永远是“额外工作量”。组员会质疑:一个功能直接写代码三天就上线了,为什么要先花半天写提案?我的回答是:那半天买的不是这三天的心安,而是三个月后有人来改这块功能时,不用再把代码当侦探小说来读。OpenSpec 不会让你写得更多,但它能让你不需要把所有事都记在脑子里。这一点,用了两三个迭代后,团队里抱怨的人就都不说话了。