规格驱动开发:把"给 AI 派活"变成工程流程

让 AI 直接开干很爽,直到同一个功能返工四遍。这篇讲我改成"先写规格再动手"之后的具体做法:一份可抄的规格模板、怎么让 AI 先挑刺、什么时候这套方法反而是浪费。
去年有段时间我很上瘾那种"让 AI 直接开干"的爽感。一句话丢过去,它哗哗写完两百行,跑起来还真能用。直到有个功能我前后返工了四遍,才意识到问题不在模型身上,在我身上。我每次提的要求都比上一次更具体,可每次它都只能猜我脑子里没写出来的那部分。它猜错不是因为笨,是因为我根本没告诉它。
那四遍返工之后我改了个习惯:动手之前先写规格。不是写给我自己看的设计笔记,是一份 AI 和我都要照着执行、并且能用来验收的文档。这套做法后来被行业起了个名字叫规格驱动开发,讨论度还挺高。这篇就讲讲我实际怎么用,以及哪些坑我踩过。
vibe coding 的爽点和它到期的时刻
先说清楚我不是来批判快速原型的。写个一次性脚本、做个内部小工具、验证一个技术想法,直接让 AI 写完全没问题,那种场景下写规格反而是浪费。我自己现在还会这么干,周末想试个新库,十分钟就出结果,很爽。
问题出在"这事要长期存在"的那一刻。一旦代码要进版本库、要上线、要被别人看、要三个月后自己回来改,那些"当时它猜对了"的部分就变成了隐患。因为猜对了一次不代表这套理解有据可查。三个月后你想加个功能,你都不记得当时的约束是什么,AI 更不记得。
我的返工四遍大概是这样:第一版它做了个表单,没做校验;我补一句要有校验,它加了前端校验;我说服务端也得校验,它加了;我说错误信息要统一格式,它改了;我说字段要支持可选……每一轮它都干得挺好,每一轮我都在补一条本可以一开始就写出来的信息。四轮下来代码是能用了,但中间那些对话里定下的规则,一条都没有留在仓库里。下一个接手的人,或者三个月后的我,面对的是四轮补丁叠出来的行为,没有任何地方写着"为什么校验要分两层"。
规格的作用就是在动手之前,把我脑子里那些默认值变成白纸黑字。
规格里到底该写什么
我不追求规格写得漂亮,我追求的是"看完之后没有需要猜的地方"。实际有用的部分大概是这几块。
要解决的问题,一两句话,写清楚现在什么不行、为什么要动。这块经常被跳过,但它决定了后面所有取舍的方向。有次我让 AI 优化一段查询代码,写得挺快,结果它优化的是我根本不在乎的那个维度(它把代码行数减了一半,而我想要的是响应时间)。
然后是功能描述和验收标准。这里我借用了产品岗常用的 EARS 写法,不是我有多讲究,而是这套句式天然逼着人把条件和行为写全。比如系统始终要满足的约束写成"系统应当……",事件触发写成"当……发生时,系统应当……",异常处理写成"如果……则系统应当……"。写完之后你会发现有些需求自己就站不住了,因为条件写不清楚,说明你自己也没想明白。
再往下是非目标。这一块我认为是整份规格里性价比最高的,因为它明确告诉 AI 不要做什么。模型有个很强的倾向,你说要个文章列表,它会顺手给你加搜索、加分页、加排序、加骨架屏,每一项都出于好意,但每一项都是你没要的代码量和维护面。写上一句"本版本不做搜索和排序",能省掉一半的返工。
最后是数据契约。接口的出入参、字段类型、可空性、错误码。这部分我强烈建议写成具体的代码或 JSON 示例而不是散文描述,因为模型对具体样本的理解远好于对形容词的理解。
一份真实的规格长什么样
拿我给博客加"定时发布"这个功能举例,当时的规格大概是这样。我节选核心部分:
## 目标
文章可以设置未来某个时间自动发布,不需要作者守在那儿手动点。
## 功能要求
- 当文章状态为 scheduled 且当前时间已过发布时间时,系统应当自动将其状态改为 published。
- 当文章状态为 scheduled 且发布时间未到时,前台接口不应当返回该文章。
- 如果保存时设置的发布时间早于当前时间,系统应当拒绝并返回明确错误。
- 系统应当保留计划发布时间,即使文章已被提前手动发布。
## 非目标
- 不做草稿协作和多人编辑。
- 不发送发布通知。
- 不做周期性的重复发布任务。
## 数据契约
status 枚举:draft / scheduled / published / archived / trash
published_at: ISO 8601 字符串,带时区,可空写完这份东西我花了大概二十分钟。写的过程中我改了两处设计:原本想做的"提前手动发布后清空计划时间"被我改成保留,因为写下来才发现清空会导致审计信息丢失。这种在写规格时发现的问题,修复成本接近于零,如果等代码写完再发现,就是一次伤筋动骨的改动。
然后我把这份规格交给 AI,附上一句"按这份规格实现,有歧义先问我不要猜"。它写出来的第一版就能跑,改动只涉及两个我没写清楚的边界情况。对比之前四遍返工,这个差距是规格带来的,不是模型变强了。
流程:人审在哪一环
规格驱动不等于把规格写完就撒手。我的实际流程分四步,人出现在第一步和第四步,中间基本放手。
写规格并确认,这是人的活。AI 可以帮忙起草,但最终拍板的是你,因为"要做成什么样"这件事没有标准答案,模型只能给你一个平均值。
拆任务,这步可以交给 AI。让它把规格拆成有依赖顺序的实现步骤,你来扫一眼顺序合不合理。这一步的价值在于暴露隐藏依赖:它经常会在拆解时发现"这个功能需要先有一个东西不存在",那你就得回去补规格。
实现,放手让它写。这中间它如果问问题,说明规格有洞,回去补规格而不是在对话里临时口头约定,因为口头约定不会留在仓库里。
验证,人来做,而且要做的是回看规格逐条核对,不是"跑起来看着没问题就行"。验收标准那几行就是给自己准备的检查单。这一步偷懒,规格的意义就废掉一半。
有一点我特意强调:中间 AI 问的问题,答案必须回写到规格文档里。很多人图省事直接在对话里回一句"对,用 UTC 存储",这个知识就只存在于那次会话里,下次重新开对话它就没了。回写文档只多花十秒。
规格该放哪儿,怎么改
我的做法是把规格当代码管:放在仓库里,跟实现同目录或者放在 docs/specs/,走同样的提交和评审。这样它有几个好处。
一是 AI 能读到。开新对话时直接让它读规格文件和现有代码,上下文天然对齐,不用我再回忆一遍。二是改动有迹可查。哪天行为变了,你 diff 规格就知道意图变了什么,回顾起来非常舒服。三是它自然地变成了团队沟通材料,谁想看这个功能该做成什么样,看规格就够了,不用读代码。
规格也要允许改。我自己踩过的坑是把规格当成不可改的契约,实现到一半发现设计有问题,硬撑着按规格写完,结果上线两周就重构。正确做法是改规格、记一笔为什么改、再改实现。规格的价值在于它是唯一的事实来源,不在于它不能变。
什么时候别用这套
得说清楚边界,不然这套方法会变成一种新的形式主义。
探索性任务不要用。你要验证一个想法行不行、要看看某个方案长什么样,这时候你连自己要什么都不知道,写规格等于凭空捏造。直接让 AI 快速做一个,看效果,扔掉重来。扔掉的代码不需要规格。
一次性脚本不要用。跑完就删的东西,写规格的成本比写代码还高。
小改动不要用。改个文案、调个样式、修个明显的 bug,直接说就行。规格有固定成本,二十分钟起步,收益是小改动换不回来的。我的经验线大概是:这件事预计要改超过一个下午,或者会被别人读、会被反复修改,才值得写规格。
还有一点,规格不能替代测试。规格是给人看的意图,测试是给机器跑的断言。我看过有人把规格写得很详细,然后指望 AI 从规格自动生成测试并相信它们,结果测试只是把规格的话翻译成断言,根本没覆盖规格没写到的边界。规格写的是"应该是什么样",测试要负责的是"哪些情况下也不能坏",两者不是一回事。
一份可以抄的规格模板
前面讲的都是原则,落到纸上大概长这样。这份模板是我改了七八次之后稳定下来的版本,你直接复制去用:
# 规格:<功能一句话>
## 背景
现在的问题是什么,为什么要改。两三句话,不要铺垫。
## 目标
这个功能做成之后,什么会变好。用可观察的结果描述,
不要写"提升体验"这种无法验收的话。
## 功能要求
- 系统应当 <始终满足的约束>
- 当 <事件> 发生时,系统应当 <行为>
- 如果 <异常条件>,则系统应当 <处理方式>
- 在 <特定状态> 下,系统应当 <行为>
## 非目标
- 本版本明确不做的事,一条一行,写具体
## 数据契约
字段名 / 类型 / 可空 / 默认值 / 取值范围
接口出入参给一段真实 JSON 示例
## 验收标准
逐条可勾选,每条都能用一次手动操作或一个断言验证
## 待确认问题
还没想清楚的,写在这里,动手前必须清空两个细节值得单独说。待确认问题 这一节别省,它是给我自己准备的提醒,也是给 AI 的免责声明:让它知道哪些地方不要自己拿主意。我一般的做法是要求它遇到这一节涉及的分歧时停下来问我,而不是选一个看起来合理的答案继续往下写。模型在"看起来合理"这件事上太强了,强到你很容易忽略它其实是在替你决策。
验收标准 要和 功能要求 分开写。功能要求是意图,验收标准是操作。写"系统应当拒绝早于当前时间的发布时间"是要求,写"把发布时间设成一小时前,点击保存,页面出现明确错误提示且文章未创建"才是验收标准。前者模型能读懂,后者你才能核对。
别把它写成 PRD
我做过产品,所以对 PRD 的写法有肌肉记忆,一开始写规格也带着那套语言,结果发现不对味。
PRD 面向的是人:给设计师、开发、测试、业务方看的对齐工具,所以它需要背景铺陈、用户故事、竞品分析、优先级论证。规格面向的是执行:给 AI 或者给三天后的你自己看的执行契约,所以它需要的是可判定的条件和边界。一份 PRD 里那句"用户可以方便地管理自己的文章",在规格里必须变成"列表页支持按状态筛选,筛选条件写入 URL query 以便分享"。
区别的本质在于可判定性。PRD 允许有需要讨论的模糊地带,因为讨论本身就是它的用法;规格不允许,因为后续没有任何讨论环节了,AI 拿着它就直接开写。
那两者能合并吗?我的做法是分层:如果这件事需要跨角色对齐,PRD 照写,但写完 PRD 之后我会抽出其中执行层面的部分做成规格文档放仓库里。PRD 用来开会,规格用来干活。两个文档加起来可能多花半小时,但只要涉及两个人以上,这半小时一定回本。如果从头到尾只有我一个人,我直接写规格,省掉 PRD 那一层。
三个我踩过的误区
误区一:规格写成了实现方案。 我早期会不由自主地写"用字典缓存、加一把锁、在 service 层做校验",结果 AI 就严格按我说的写,而它本该有机会告诉我有个更简单的路径。现在我尽量写"要满足什么",把"怎么实现"留白,除非那个实现约束本身是硬需求(比如必须兼容已有的数据格式)。判断标准很简单:如果删掉这句实现描述,需求的意图仍然完整,那它就不该出现在规格里。
误区二:规格太大,大到没人读。 我曾经为一次大的重构写了八千字规格,自己都不想读第二遍。后来改成按功能切碎,每份规格只管一件事,每份不超过两页。大改动就拆成多份有先后依赖的规格,按顺序做。这还有个附带好处:每份规格做完就能验收,你一直有可用的中间状态,不会出现"重构到一半发现方向错了但已经改了两百个文件"。
误区三:把规格当成交付物而不是工具。 有段时间我很享受"我有一份写得很整齐的规格"这种感觉,甚至开始花时间排版、加目录、调格式。后来发现它对执行没有任何帮助,纯粹是自我感动。规格是拿来用的,丑一点没关系,只要它能让 AI 少猜一次、让我少返工一轮,它的价值就实现了。
让 AI 拆任务时,我在看什么
拆解这步我交给 AI,但会认真看它的输出,因为这一步最容易暴露规格的洞。它大概会给你这样的东西:
1. 数据层:posts 表加 status 枚举校验,兼容历史数据
2. 服务层:新增到期检查函数,处理时区转换
3. 触发点:列表接口读取前先执行一次到期检查
4. 前台:scheduled 状态文章从公开列表过滤
5. 管理端:编辑器加发布时间选择器,含过去时间校验
6. 测试:覆盖"到点发布""未到点不可见""过去时间被拒"三条我的检查动作是三个:顺序里有没有隐藏依赖(第 3 步依赖第 2 步的什么行为)、有没有步骤是规格里没提的(第 1 步提到"兼容历史数据",这是规格里的空白)、有没有规格里写了但没出现在任务里的(拿规格逐条扫一遍)。第三个检查最容易被跳过,但它恰好是验收时最疼的地方,因为漏掉的通常就是那些"当时没做,上线之后才发现"的东西。
顺带一提,那份拆解里第 6 步"测试"经常被模型排在最后甚至省略,得主动要求。它更愿意交付能跑的功能,而不是能证明自己对的功能,这个倾向在拆任务阶段就要纠正。
动手前,我会先让 AI 挑刺
这是我后来加进来的一步,收益超出预期:规格写完之后,不要立刻让它实现,先让它当一次审稿人。
具体做法是把规格发过去,配一句话:"不要写代码,先告诉我这份规格里有哪些地方是含糊的、自相矛盾的,或者缺失的边界情况。"它的回复通常能挑出两三处我没想到的,比如"当文章已经是 published 状态时,再次设置未来的发布时间应该怎么处理"这类我压根没考虑的组合。
这一步的价值不在于它有多聪明,而在于它比我更有耐心去逐条穷举条件组合。人写规格时会不自觉地省略"显然"的情况,而机器没有"显然"这个概念,它会老老实实问你。我现在的习惯是让两轮挑刺:第一轮抓逻辑缺口,第二轮专门找"如果这个字段为空会怎样""如果两个用户同时操作会怎样"这类异常路径。
代价很小,一轮对话而已。相比写到一个小时之后发现前提错了,这个投入划算得不像话。
怎么衡量这套做法有没有用
我不想把规格驱动讲成一种信仰,所以给自己定过几个观察指标,跑了大概半年。
看返工轮次。同一个功能,从第一次交付到验收通过,中间来回的次数。我之前的记录是四到六轮,改成先写规格之后稳定在一到两轮。这个变化是能感受到的,因为它直接对应我的时间。
看新对话的启动成本。隔几天回来继续做某个功能,我需要花多久让 AI(以及我自己)重新理解上下文。以前要来回翻聊天记录、翻代码、回忆当时的取舍,现在打开规格文件就行。这项收益随着项目时间拉长会越来越大,属于复利型。
看规格被改的次数。如果一个功能的规格从写完到上线一次没改过,我会有点警惕,怀疑是不是需求本身太简单、不值得写规格。改过一两次是正常状态,说明规格在承担它该承担的职责,把设计变更挡在写代码之前。
也有没改善的地方,说句实话:写规格并没有让需求变更变少。业务要变还是会变,规格只是让变更有据可查、影响范围可评估,它不能阻止变更。要是指望它减少需求波动,会失望的。
一个反复出现的心态问题
最后说说我观察到的、也是最难改的一点:很多人不写规格,是因为写规格会立刻暴露自己没想清楚。
这挺难受的。让 AI 直接开干的时候,你的模糊是被代码盖住的,看起来一直在推进;一旦要求自己写下验收标准,你会发现自己有三分之一的需求是"我想要个好用的 X",这个"好用"到底指什么,得现场想。这个过程非常有挫败感,我前几次写规格都写得半途而废。
但那个挫败感恰恰是它的价值。那些想不清楚的地方,早暴露比晚暴露便宜,暴露在自己面前比暴露在用户面前便宜。我现在把写规格当成一次免费的自我提问,写到卡住的地方就说明这里需要决策,写完一份规格通常比写之前想清楚了不止一倍。
对跟 AI 协作这件事来说,规格还有一层不太被提起的意义:它是你和模型之间唯一不会随对话丢失的东西。模型的上下文会满、会话会断、你会换工具,只有那份文档还在。把它写好,是把协作从"每次重新解释"变成"每次接着往下做"的唯一办法。这件事我做得也不算好,经常写着写着就偷懒,但每次偷懒之后都会在返工里还回来。