规格驱动开发
概览
在写任何代码之前,先写一份结构化的规格说明。Spec 是你和人类工程师之间共享的事实来源,它定义了我们要构建什么、为什么构建,以及如何判断它已经完成。没有 spec 的代码,本质上是在猜。
何时使用
- 开始一个新项目或新功能
- 需求有歧义或不完整
- 改动会涉及多个文件或模块
- 你将要做架构决策
- 任务实现预计会超过 30 分钟
不适用的场景: 单行修复、拼写修正,或需求已经非常明确且范围自洽的小改动。
分阶段工作流
Spec-driven development 分为四个阶段。当前阶段没有通过验证前,不要进入下一个阶段。
SPECIFY ──→ PLAN ──→ TASKS ──→ IMPLEMENT
│ │ │ │
▼ ▼ ▼ ▼
Human Human Human Human
reviews reviews reviews reviews
阶段 1:明确规格(Specify)
从高层愿景开始。持续向人类提出澄清问题,直到需求足够具体。
立刻暴露假设。 在写任何 spec 内容之前,先列出你当前的假设:
我当前的假设:
1. 这是一个 Web 应用,不是原生移动端
2. 认证使用基于 session 的 cookie,而不是 JWT
3. 数据库是 PostgreSQL(基于现有 Prisma schema 推断)
4. 目标仅为现代浏览器,不考虑 IE11
→ 如果不对请现在纠正,否则我会按这些继续。
不要悄悄补齐有歧义的需求。Spec 的全部价值,就是在代码写下去之前先暴露误解,而假设正是最危险的误解形式。
写一份覆盖以下六个核心区域的 spec 文档:
Objective:我们要做什么,为什么做?用户是谁?成功的标准是什么?
Commands:给出完整可执行命令和参数,而不是只写工具名。
Build: npm run build Test: npm test -- --coverage Lint: npm run lint --fix Dev: npm run devProject Structure:源码在哪里、测试在哪里、文档放哪里。
src/ → 应用源码 src/components → React 组件 src/lib → 共享工具 tests/ → 单元与集成测试 e2e/ → 端到端测试 docs/ → 文档Code Style:一个真实代码片段,比三段文字描述更有用。包括命名约定、格式规则,以及好的输出示例。
Testing Strategy:使用什么框架,测试放在哪里,覆盖率预期如何,不同关注点分别用哪一层测试。
Boundaries:三层边界系统:
- Always do: 提交前跑测试、遵循命名规范、做输入校验
- Ask first: 改数据库 schema、加依赖、改 CI 配置
- Never do: 提交 secrets、编辑 vendor 目录、未经批准删除失败测试
Spec 模板:
# Spec: [项目/功能名称]
## Objective
[我们要构建什么、为什么。用户故事或验收标准。]
## Tech Stack
[框架、语言、带版本的关键依赖]
## Commands
[Build、test、lint、dev 的完整命令]
## Project Structure
[目录结构及说明]
## Code Style
[示例片段 + 关键约定]
## Testing Strategy
[框架、测试位置、覆盖要求、测试层级]
## Boundaries
- Always: [...]
- Ask first: [...]
- Never: [...]
## Success Criteria
[如何判断完成,必须是具体且可测试的条件]
## Open Questions
[任何仍需人类确认的未决问题]
把模糊指令改写成成功标准。 当接收到模糊需求时,把它翻译成明确条件:
原始需求:"让 dashboard 更快"
改写后的成功标准:
- Dashboard 的 LCP 在 4G 网络下 < 2.5s
- 初始数据加载在 < 500ms 内完成
- 加载期间没有布局偏移(CLS < 0.1)
→ 这些目标对吗?
这样你就能围绕清晰目标迭代、重试和解决问题,而不是去猜“更快”到底是什么意思。
阶段 2:制定计划(Plan)
在 spec 验证通过后,生成一份技术实现计划:
- 识别主要组件以及它们的依赖关系
- 确定实现顺序,先做哪些基础项
- 标出风险及缓解策略
- 识别哪些可以并行,哪些必须串行
- 定义阶段间的验证检查点
这个计划应该是可审阅的:人类读完后应该能明确回答“对,这就是正确做法”或“不是,X 需要调整”。
阶段 3:拆成任务(Tasks)
把计划拆成离散、可执行的任务:
- 每个任务都应该能在一次专注工作中完成
- 每个任务都要有明确的验收标准
- 每个任务都包含验证步骤,例如测试、构建或手工检查
- 任务按依赖顺序排序,而不是按“看起来重要”
- 单个任务最好不要涉及超过约 5 个文件
任务模板:
- [ ] Task: [描述]
- Acceptance: [完成后必须成立的事实]
- Verify: [如何确认,例如测试命令、构建、手工检查]
- Files: [会改到哪些文件]
阶段 4:开始实现(Implement)
一次只执行一个任务,并遵循 incremental-implementation 与 test-driven-development 两个 skill。用 context-engineering 在每一步只加载当前需要的 spec 片段和源码文件,而不是把整份 spec 一股脑塞给 agent。
让 Spec 保持鲜活
Spec 是活文档,不是一次性产物:
- 决策变化时要更新:如果你发现数据模型需要调整,先更新 spec,再实现。
- 范围变化时要更新:新增或删减功能,都要同步到 spec。
- 把 spec 提交进版本控制:它和代码一样属于仓库的一部分。
- 在 PR 中引用 spec:让每个 PR 都能链接回它实现的 spec 章节。
常见自我安慰
| 自我安慰 | 现实 |
|---|---|
| “这很简单,不需要 spec” | 简单任务不需要长 spec,但仍然需要验收标准。两行 spec 也完全可以。 |
| “我先写代码,之后再补 spec” | 那叫文档,不叫规格说明。Spec 的价值在于它能在编码前强迫你想清楚。 |
| “写 spec 会拖慢我们” | 15 分钟的 spec,能省掉数小时返工。15 分钟的轻量 waterfall,总比 15 小时 debug 强。 |
| “需求反正还会变” | 所以 spec 才应该是活文档。过期的 spec 也比没有 spec 强。 |
| “用户自己知道他想要什么” | 再清晰的需求也有隐含假设,spec 的作用就是把这些假设显性化。 |
危险信号
- 在没有任何书面需求的情况下开始写代码
- 在搞清楚“完成”意味着什么之前就问“要不要我直接开始做?”
- 实现了 spec 或任务列表中没有提到的功能
- 做了架构决策却没有记录
- 因为“这要做什么很明显”就跳过 spec
验证
进入实现阶段前,确认:
- Spec 覆盖了六个核心区域
- 人类已经审阅并批准了 spec
- 成功标准具体且可测试
- Boundaries(Always / Ask first / Never)已定义
- Spec 已保存为仓库中的文件