Appearance
Spec 到底该怎么写?
导读
Spec 是把模糊任务收束成 AI 可执行、可自查、可验收的契约。它的重心不是规定怎么做,而是定义什么算做完:写清目标、边界约束和可逐条打勾的验收标准,把 HOW 留给 AI。
传统 Spec 与人机协作 Spec 不一样在哪
传统 Spec 大量是形式约束:交付物结构、接口形态、目录、技术栈、流程、模板。它假设执行者没有自主性,需求可预测,路径可提前定。
人机协作 Spec 应该以验收标准为主:定义「什么结果可接受」,让 AI 在方案空间里自主搜索。它假设 AI 有规划能力,而人不需要懂实现。
常见误区:把 Spec 写成操作手册
写死步骤会让 AI 从智能体退化成脚本执行器。
为什么 Vibe Coding 必须配上 Spec
Vibe Coding 主打即兴:随口说想法,AI 出代码,边写边调。 但它天生有几个硬伤:上下文容量有限,记忆不可靠,空白处就靠脑补。memory.md 这类补救方案目前并不稳定。
- AI 喜欢自行加需求:对话多了,悄悄夹带私货,范围持续膨胀,慢慢跑偏;
- 长会话遗忘约定:模型忘掉之前定好的数据结构、命名规则,偷偷乱改;
- 可运行 ≠ 合格:跑起来只是最低门槛,异常、边界、安全经常漏掉;
- 项目一大就崩盘:小 demo 很爽,多模块迭代之后技术债爆炸,改一处崩一片。
Spec‑Coding:面向 AI Agent 的轻量规格实践
这套实践来自 spec‑coding.dev。下面用 Spec‑Coding 来指代这套方法。这套方法我实际用了一段时间,不可否认这是一个巧妙的方法论,但普通用户使用就显得有些笨拙和束缚。有兴趣的可以去它的项目了解一下。本文借用了它的经验,但是在他的基础之上又做了大量的减法。
Spec‑Coding 认为:绝大多数返工不是代码质量差,而是人和 AI 之间假设不一致。如果你花 10 分钟写好一份 spec,往往可以避免 AI 反复产出三版错误实现。
同时,Spec‑Coding 给出了一个「验收标准」的标准:「功能必须具体到能独立进行一次测试」。也就是说,如果你写不出这份 spec,说明这个功能本身就还没有满足启动的前提。
Spec‑Coding 的 Spec 标准骨架
Context(上下文) 记录现有代码位置、关键文件、已有的系统模式。告诉 AI 现有系统长什么样,哪些模块需要复用,避免 AI 凭空造一套新结构。只陈述现状,不写需求。
Goal(目标) 写交付之后系统、用户会获得什么行为。重点写 WHAT,不是 HOW。描述「发生什么结果」,不要写一步步操作流程。
Plan(实施计划) 可评审的实现步骤,写明涉及的文件名、依赖。这里可以写实现思路,但 Plan 是建议方案而非铁律;如果 AI 有更好方案,在 spec 内讨论更新 Plan,而不是直接改代码。
Out of scope(不在本次范围,最容易被忽视但价值极高) 明确列出本次绝对不做的事情:顺手的重构、锦上添花功能、相关但不属于当前迭代的需求。这是对抗 AI「顺便做一点」范围膨胀最关键的防线。很多时候 spec 最大价值恰恰来自这一节。
Acceptance(验收标准) 全部要写成可观测、可验证的行为。禁止「体验良好、代码优雅、性能不错」这类模糊描述。要写清楚正常场景,更要写异常、失败场景:过期输入、重复提交、权限不足等边界情况。任何人不需要读代码,就可以逐条核对是否达成。
Evidence(证据与回滚) 定义合并前必须具备的产出:单元测试、集成测试、需要截图的 UI 状态;同时明确灰度开关、观测指标、回退路径。回滚不是事后补救,要提前写进 spec,不能等到线上故障再想怎么回退。
本文的轻量实践做了三处减法:
Plan 降级为可选建议,轻量版可以没有 Plan;
Context 视 AI 是否冷启动决定写不写;
Evidence 里的回滚按风险等级选配。
差 Spec 与好 Spec 对比
举例子:给结账页加优惠券功能
差 Spec:路径预设型
markdown
# 优惠券功能
## 目标
优化结账体验,支持优惠券。
## 实现
- 在结账页加一个优惠券输入框
- 把用户输入过的券码存下来,下次自动带出
- 调用后台校验服务判断券是否可用
- 校验通过后重新计算总价
- 样式参考现有的地址选择组件
## 完成
优惠券功能正常运行。这份 Spec 的问题不在「写得太少」,而在把本该由 AI 规划的 HOW 写成了完成条件,真正该写清的 DONE 却是空的。
「优化体验」不可判定——没说清什么变化算优化,AI 只能猜,你也只能凭感觉验收。
「正常运行」不可判定——没说什么算正常;做完了没法打勾,出错时也没法说「这条没过」。
实现步骤写死了方案——「存下来下次带出」「调后台校验」是方案选择,不是可验收标准;把步骤写进完成条件,等于用操作手册冒充验收契约。
结果是:AI 可能严格按这五步做完了,但它极不稳定,甚至可以断言——AI 呈现的效果一定不等于你脑子里的效果,而你纠正 AI 的这个动作困难重重。
好 Spec:验收契约型
markdown
# 优惠券结账
## 目标
结账时可用一张优惠券;失败时用户看得懂原因,能重试。
## 约束
- 一次只能用一张券,不做叠加。
- 不改动支付流程、订单确认页和数据库。
## 验收标准
- AC-1 成功:购物车 120 元,用有效券 SAVE20 → 总价变 100 元,出现“SAVE20”折扣行。
- AC-2 过期:用已过期的券 → 总价不变,提示“券已过期”。
- AC-3 重复:用户再次输入同一张券并点击应用 → 不出现第二条折扣行。
- AC-4 不越界:改动只涉及结账页和提示文案,不碰支付和订单确认。
## 验证
每条标准都要有能看到的证据:截图或测试结果。
## 终止
四条全过 → 交付;连续两轮过不了,或要动约束里的东西 → 停下问我。结论:每一行都在回答「什么算做完」或「怎么证明」
HOW 全部留空——用不用输入框、券码存不存、调哪个服务,一个字不写;那是 AI 的规划空间,写死就把方案上限锁死了。
AC 覆盖三条路径加一条边界——AC-1/2/3 分别管成功、失败、重复,AC-4 把「不越界」变成可检查的条件;验证和终止各一句,让这份 Spec 在工作循环里成为一个有退出条件的循环,而不是一个礼貌的愿望。
终止条件是出口——「连续两轮过不了就停下问我」明确告诉 AI:遇到阻碍可以停,不必硬编。这一层极容易被忽略,也是一个非常省 token 的设计。
一眼能看到的差异
| 差 Spec | 好 Spec | |
|---|---|---|
| 读完知道什么 | 要做哪几步 | 什么算做完 |
| 「完成」指什么 | 功能正常运行 | 四条可逐条打勾的标准 |
| 出错路径 | 没写,AI 自己猜 | 过期、重复都写了 |
| 会不会越做越多 | 没边界 | 约束 + AC-4 挡住 |
| 什么时候停 | 不知道 | 全过交付,过不了问人 |
| AI 的角色 | 按步骤执行的脚本 | 在验收契约内自己想办法 |