最近在折腾 AI 辅助开发,发现一件让我挺头疼的事:模型写代码越来越快,但代码“偏”得也越来越离谱。让它加个功能,它顺手把无关模块重构了;让它修个 bug,它自作主张改了接口命名。后来我开始用 OpenSpec 这套规范来约束整个流程,实测了一两个月,开发节奏顺了不少。
OpenSpec 说白了就是一套面向 AI 编码场景的规范格式,核心思路是用结构化的 Markdown 文件,把“需求、验收标准、任务拆解、进度状态”写清楚,让 AI 拿到的不再是一句模糊的 prompt,而是一份它真正能读懂、能照着执行的设计文档。今天我把它拆开揉碎了讲一遍,包括我踩过的坑、摸索出来的写法,以及怎么跟手头的 Git 工作流、Code Review 流程配合起来。
1. 我为什么从“提示词驱动”切到 OpenSpec
先说背景,我日常维护的是一个中型的业务系统,代码量不算小,前后端加起来 30 多万行。早先我带团队用 AI 编码,流程非常简单粗暴:开个对话窗口,把需求粘贴进去,让模型直接改代码,改完人肉看一遍差异就提交。前几次体验确实惊艳,但项目复杂之后,问题频繁冒出来。
1.1 AI 写代码最让人崩溃的三个场景
第一个是需求漂移。你说“给订单列表增加一个导出按钮”,模型非常敬业,顺手把订单状态机、日志上报、权限校验全部重构了一遍。看起来是好意,但你根本分不清哪些改动是必须的,哪些是它自己脑补的。
第二个是重复劳动。同一个小需求,今天让 A 模型改一版,明天让 B 模型改一版,两版实现思路完全不同。代码风格统一不了,后面维护的成本全压在人身上。
第三个是验收无依据。模型说自己“完成”了,你让它在测试环境点一遍,发现边界值全是坑。问题出在哪?因为需求描述里根本没写清楚什么算完成。
1.2 我试过的替代方案为什么不够用
一开始我想用注释规范、代码检查规范来兜底,比如强制 JSDoc、ESLint 严格模式。确实能拦住一部分低级错误,但拦不住“方向错误”。后来试过把需求写成详细的 PRD 文档再喂给 AI,文档是写清楚了,但模型读起来费劲,经常把结论性描述当成可执行步骤,效果也不稳定。
真正让我下决心换思路的,是一次排期很紧的功能开发。我一口气把需求拆成十几个小任务,每个任务对着一个明确的验收标准。那一次 AI 的产出质量稳定得惊人,我基本只需要做轻微 review 就能合入。
后来我才意识到,问题不在 AI 能力,而在我给它看的材料。AI 编码工具更像一个执行力极强的实习生,它需要的是高密度的约束文本,而不是散文式聊天记录。OpenSpec 恰好提供了这样一套文本组织格式。
2. OpenSpec 的核心机制,我是怎么理解它的
OpenSpec 的名字虽然带“Spec”,但和传统软件工程里的需求规格说明书是两回事。传统规格书是给人看的,写得再详细,模型依然要从中“二次理解”。OpenSpec 的设计目标,是让规范文档同时能被人和模型高效读取。
2.1 最基础的目录结构与文件约定
我习惯在仓库根目录建一个specs/文件夹,下面是所有功能规格的存档区。每个功能都单独建一个子目录,目录名就是功能名,用短横线连接。比如:
specs/ order-export/ proposal.md tasks.json progress.mdproposal.md描述这个功能“是什么、为什么做、边界在哪”。tasks.json把实现过程拆成可并行的子任务。progress.md则是执行过程中的状态记录,类似任务看板。
这套结构的好处是,模型拿到specs/order-export目录后,不需要我额外讲解,就能明白自己要从哪个文件开始看、当前做到哪一步、下一步要产出什么。
2.2 写好验收标准的关键:可测试、可判定
OpenSpec 里我最看重的是proposal.md中的验收标准部分。刚开始我写得特别含糊,比如“导出功能应正确工作”。后来我发现,这种话写不写没区别。
真正有效的验收标准,每条都要做到能通过代码或操作直接判定真伪。我一般写成三行格式:给定某个前提、执行某个动作、得到某个可观测结果。
举个例子:
## 验收标准 - 给定:订单列表中存在 100 条记录且当前筛选条件为“已支付” - 当:用户点击“导出当前筛选结果”按钮 - 则:浏览器触发一个文件下载,文件名格式为 orders_YYYYMMDD_HHmmss.csv,且文件内包含且仅包含这 100 条订单数据模型看到这种描述,整个思考路径会明确多,因为它不需要猜测“正确”是什么意思,只需要对着代码一项项验证。
2.3 状态流转与版本化记录
另一个很实用的机制是progress.md的状态标记。每完成一个子任务,就在对应条目后面更新状态。AI 在下一轮继续干活时,先读一遍状态记录,就能精准接上进度,不重复造轮子。
这其实和我们平时用 Git 管理代码是一个道理。代码有版本,需求描述和任务进度也要有版本。OpenSpec 把“开发过程中的中间产物”——也就是需求理解、任务拆解、验收标准——全部纳入版本管理,让整个开发链路可追溯。
3. 从零开始用 OpenSpec 驱动一次完整开发
说了这么多概念,接下来拿一个我实际做过的功能,完整走一遍流程。这个功能叫“库存预警通知”,核心诉求是:当商品库存低于安全阈值时,系统自动给运营人员发送站内信和邮件。
3.1 第一步:先把需求和边界写进 proposal.md
我没有直接让 AI 动手写代码,而是先花一个小时自己写proposal.md。很多人觉得这一步浪费时间,我反而觉得这是整个流程里最值钱的半小时。
我在这份文档里写清楚了几件事:
- 功能背景:库存不足导致超卖投诉率上升,需要提前通知运营。
- 功能范围:只做后台库存模块的预警监测,不做前端展示,不做供应商通知。
- 触发条件:库存量低于 3 件时触发,且同一商品 24 小时内只能通知两次,避免轰炸。
- 非目标:本期不接入短信渠道,不处理赠品库存。
特别要注意“非目标”这个字段。开始写的时候我也觉得多余,后来发现它极其重要。AI 非常擅长给自己“加戏”,没有明确边界,它可能顺手把促销活动、价格策略全包进来。边界写清楚,模型反而更好理解什么不该动。
3.2 第二步:把整个功能拆成可验证的任务清单
写tasks.json时,我遵循一个原则:每个任务都能独立验证,并且任务之间的依赖清晰。
我拆出来的任务大概长这样:
{ "tasks": [ { "id": "task-1", "title": "设计库存预警数据模型", "depends_on": [], "validation": "新增 inventory_alert_rule 表结构,包含商品ID、安全阈值、通知间隔字段,并包含对应的数据库迁移脚本" }, { "id": "task-2", "title": "实现库存变更监听逻辑", "depends_on": ["task-1"], "validation": "商品库存字段更新时,触发一次预警检查,单测覆盖低于阈值和恢复阈值两种场景" }, { "id": "task-3", "title": "实现通知去重与限流逻辑", "depends_on": ["task-2"], "validation": "同一商品在配置的通知间隔内不会被重复通知,有对应的单元测试证明" } ] }depends_on字段很关键,它明确告诉 AI 哪个任务要做在前面。没有这个字段的话,AI 经常会把数据库变更和业务逻辑同时写掉,一旦数据库设计有问题,后面全部返工。
3.3 第三步:让 AI 按任务逐个实现,并把进度写回 progress.md
任务拆好之后,我并没有一次性把所有任务都丢给 AI 处理。一次只丢一个任务,让它读完对应的 spec 和当前进度,然后开始编码。每完成一个任务,我都会强制它在progress.md里更新状态,写下“完成了什么、引用了哪些文件、下一步计划”。
这一个“每完成一步就写一步”的习惯,起初非常反直觉——感觉像是给 AI 增加额外工作。但实际运行一周后,好处非常明显:中途如果切换模型,或者讨论中打断了上下文,新的会话只需要读一遍progress.md就能接上,不需要我重新解释背景。
3.4 验收环节:把标准当成测试用例执行
所有任务完成后,我拿proposal.md里的验收标准挨个过了一遍。其中有一条是“库存低于阈值时,预警通知在 5 秒内发出”。第一版实现里,通知任务做成了异步队列调度,延迟不稳定,部分场景下超过 10 秒。
如果按照传统开发方式,这种问题可能要等上测试环境后才能暴露。但因为在 OpenSpec 里验收标准是写出来的,AI 实现阶段就会主动考虑性能问题。后来它给我的第二版方案直接改成了实时内存计算加异步分发,测试结果满足 5 秒约束。
这个经历让我明白一个道理:给 AI 写验收标准,本质上就是在做测试驱动开发。只不过 TDD 的测试用例是代码,OpenSpec 的验收标准是先写成文档,再转化成代码逻辑。
4. 用 OpenSpec 流程时,我踩过哪些坑、怎么填平
这部分是我最想分享的内容。网上讲 OpenSpec 理论的多,讲真实踩坑的少。我按踩坑频率排个序,把最有代表性的问题列出来。
4.1 问题一:规范文件写得太全,反而让模型手足无措
最开始我追求“规范文档越详细越好”,一份 proposal 写了几百行,把数据库字段、接口路由、前端组件全部定义死了。结果模型确实不敢乱来了,但也变得畏手畏脚,有些非常普通的逻辑,非要回到文件里找依据,开发效率明显下降。
后来我调整策略,把规范分成两层:
- 高层规范只写“做什么、边界、验收标准”。
- 低层技术选型只给约束,不给具体方案。
例如我告诉它“缓存模块必须使用现有 Redis 封装,不允许引入新依赖”,但到底用哪个 key 格式,留给它自己设计。这样模型既不会走偏,又有发挥空间。
4.2 问题二:验收标准里混进了“形容词”,AI 无法判断对错
早期的proposal.md里我写过“导入速度要快”“界面要美观”这类话。AI 看到这种描述,除了瞎猜没有别的办法。后来我给自己定了个硬指标:验收标准里不允许出现比较级词汇。凡是形容词,必须改成可量化的约束。
“速度快”改成“导入 1 万行数据耗时小于 5 秒”。
“界面美观”改成“弹窗在 1280 宽度下无横向滚动条,按钮间距不小于 8 像素”。
这套写法开始觉得别扭,但坚持下来之后,我发现不仅 AI 产出稳定了,连后来做人工 Code Review 都轻松不少。因为验收标准就是最清晰的评审清单,我不用自己脑子再过滤一遍“这里到底算不算完成”。
4.3 问题三:没有机械执行检查,AI 也会“自我感觉良好”
AI 生成的代码,经常出现“看着没问题、一跑就报错”的情况。我一开始过于信任验收标准,模型说“已验证通过”,我就信了。后来发现不行,它所谓的“验证”,有时候只是读了读代码。
解决办法是,我在任务验收阶段加入了一个“必须执行指令”。我在提示词里明确要求,完成实现后,必须运行对应的测试命令和 lint 命令,并把输出结果贴到progress.md里。这一步加上后,模型的“自我感觉良好”现象大幅减少。
后来又更进一步:对关键任务,我要求它额外写一个最小复现脚本。脚本不复杂,可能只有二三十行,但能直接展示核心路径的运行结果。这样一来,AI 的产出就不再是“代码片段”,而是一堆“可运行证据”。
4.4 问题四:多专业协作时,大家理解同一份规范的方式不一样
OpenSpec 刚在团队里推广时,不是所有人都接受。后台开发觉得 Spec 文档写得像需求文档,前端开发觉得验收标准太技术化,测试同事觉得没有直接可用的测试用例。
我的折中方案是,把proposal.md里的验收标准做成一张“双向映射表”,每条验收标准都标注对应的任务 ID 和实现文件路径。这样后端对着任务看代码,前端对照验收标准看交互,测试同事直接按表转化成测试用例,效率反而提升不少。
4.5 高频问题速查表
为了方便参考,我把实践过程中遇到过的最典型问题整理成了表格。
| 问题场景 | 根本原因 | 我的处理方式 |
|---|---|---|
| AI 擅自扩展需求范围 | 非目标没有写明 | 在 proposal 中设“非目标”段,明确什么不该做 |
| 模型实现方向正确但接口设计很怪 | 技术约束不足 | 在 tasks.json 中增加接口约定字段 |
| 同一功能多次实现代码风格差异大 | 缺少风格基线 | 在 specs 根目录放一份 coding-standards.md 作为全局规范 |
| 任务依赖混乱导致返工 | 依赖关系没定义 | 严格遵守depends_on,未完成前置任务不允许执行后续任务 |
| 模型报告的进度与实际代码不一致 | 没有强制更新进度文件 | 明确要求每次改动后必须更新 progress.md 并附运行结果 |
| 文档写得太全,开发速度反而慢 | 方案空间被封死 | 区分“硬约束”和“建议”,只约束必要项 |
5. 把 OpenSpec 嵌进团队现有的开发流程
用了几个月之后,我的感受是:OpenSpec 的最优解不是取代开发流程,而是嵌在现成的 Git 工作流、Code Review 和 CI 流程里。几个人以下的个人项目,可以像我一样轻量使用;十人以上的团队,则需要进一步建立共识。
5.1 分支策略与规范文档同时变化
我在 Git 工作流上的习惯是,从main开一个分支做功能,分支跑通后再合并。落到 OpenSpec 上,每个功能分支对应一个specs/功能名/目录。分支合并时,规范目录跟着代码一起进main,保证主干上的规范文档始终和代码状态同步。
这里有个细节值得多说几句:我不建议在代码合并后再去补规范。因为一旦规范文档落后于代码,它就彻底失去意义了——AI 下次读这份规范时,会以为旧逻辑还是当前逻辑。
5.2 Code Review 时先看 Proposal 再看代码
以前 Code Review 我是直接打开 Diff 逐行看,费眼睛而且容易漏。现在我的顺序变了:先读这个功能的proposal.md验收标准,再对照 Diff 检查实现与验收标准之间的对应关系。这其实很像测试人员先看用例再看实现的做法。
发现差异的时候,我会直接在评审意见里引用 proposal 里的验收标准编号。比如“AC-03 要求 24 小时重复通知次数不超过两次,但当前实现完全没有缓存去重逻辑”。评审意见一旦有了编号锚点,讨论起来特别高效,因为大家都知道在说哪一条。
5.3 结合现有检查代码规范的工具链
有人问过 OpenSpec 和传统 lint、代码检查工具是什么关系。我的理解是这样的:代码检查工具负责“代码长得好不好看、有没有低级错误”,OpenSpec 负责“需求理解对不对、任务拆得准不准、验收标准有没有达成”。
两者是互补关系,不是替代关系。我自己的实践是:在 CI 里保留了全部原有的 lint 和类型检查,再额外加一道“规范一致性检查”,大概的伪代码逻辑是:
for spec in specs: tasks = load_tasks(spec) progress = load_progress(spec) for task in tasks: if task.id not in progress.completed_tasks: fail(f"Task {task.id} 未完成")这行脚本不复杂,但它能保证“流程上的完成”和“代码上的完成”对齐。没有这种硬检查,“AI 说完成了、实际上没有”的事情还会反复发生。
5.4 个人开发者和小团队的轻量用法
如果你现在只是一个人在用 AI 写项目,完全不用把流程搞得太重。我建议先保留三个文件就够了:proposal.md、tasks.json、progress.md。coding-standards 和更复杂的版本策略,等项目变大后再加。
我见过一些人把 OpenSpec 用成“写作文大赛”,规范文档洋洋洒洒几千字,代码倒没写几行。这是走了另一个极端。合理的参考比例是,一个中大型功能(大概一周开发量),规范文档整体控制在 300 到 500 行以内,重点写透验收标准和任务边界。
6. 关于 OpenSpec,我最后的几条心得
回到最初的问题,我为什么觉得普通开发者应该掌握 OpenSpec?因为现在的 AI 编码工具已经足够强了,真正稀缺的不是“让模型写更多代码”的能力,而是“让模型朝正确方向写代码”的控制力。OpenSpec 提供的就是这种控制力的最小可用实现。
我个人在实际操作中的体会是,它逼着我养成了两个好习惯。第一个习惯是动手写代码前,永远先把“什么叫完成”定义清楚;第二个习惯是每个任务做完,把运行证据留存下来。这两个习惯看起来朴素,但在 AI 驱动开发的时代,它们比炫酷的模型选择重要得多。
如果你正准备在下一个项目里引入这套思路,我建议从一个小功能开始试水。不需要一下子把所有规范流程都铺开,先把proposal.md写好、验收标准量化、任务拆细,让 AI 按任务清单走一遍。跑通一次,你就能直观感受到,原来让 AI“听话”不是靠多几个限定词,而是靠一份结构严谨、边界清晰的规范文本。
最后再分享一个小技巧:规范文档本身,也可以让 AI 帮你 review。我经常把写好的proposal.md丢给模型,问它“这份规范里有哪些验收标准无法被机器判定,哪些任务边界存在歧义”。模型给出的意见往往一针见血,毕竟它最了解自己这类模型在读文档时会卡在哪些地方。这个逆向思路,是我实践下来最受益的点,建议你也试试。