【AI Native开发-06】从 Spec 到 Plan:让 AI 先研究再动手
摘要
Spec 回答”做什么”,Plan 回答”怎么做”。这一篇讲 Gate 2 的核心产物:为什么 AI 必须先研究代码库、产出一份受约束的执行计划并经人评审,才准碰代码。
核心论点有三个:
- Plan 是成本最低的纠错点。在 Plan 阶段发现”方案和现有架构冲突”,改几行字;在实现后发现,要回滚整条分支。
- Plan 的价值不在”步骤列表”,而在”影响面声明”。一份合格的 Plan 必须写清三件事:改哪些文件、不碰哪些文件、出了问题怎么回滚。
- 每条 Spec 需求都必须能在 Plan 里找到落点。REQ → Plan 步骤的映射,是防止”做着做着漏了一条需求”的唯一机制。
本篇给出 Plan 的研究清单、完整模板、需求映射表、准入准出标准,以及三种最常见的 Plan 反模式。
一个案例场景(假想全栈项目)
接着”删除草稿文章”的需求。Spec 已经过了 Gate 1,十条业务规则、权限边界、验收标准都钉死了。你把 Spec 丢给 AI:
“按这个 Spec 实现删除文章功能。”
AI 很快返回:“已完成。新增了 deleteArticle 接口,加了删除按钮,测试也写了。”
你一看 Diff,心凉了半截:
- 它新建了一张
deleted_articles数据库表做软删除——但这个项目是 Astro 静态博客,内容是 Markdown 文件,根本没有数据库; - 它顺手”重构”了文章列表的加载逻辑,把三个组件的 props 改了;
- 它在删除接口里加了一个”回收站 30 天自动恢复”的功能——Spec 里非目标一节明确写了”不做回收站”。
问题不在 AI 笨。问题在于:你让它从 Spec 直接跳到了代码,中间没有任何东西强迫它先搞清楚”这个项目到底是怎么组织的”。 它用最熟悉的 Web 后端模式(数据库 + 软删除 + 回收站)去套一个文件型静态站,还自由发挥了 Spec 明确排除的功能。
Plan 阶段就是用来拦住这一切的。
核心问题:为什么不能从 Spec 直接到代码
传统开发里,“想清楚怎么做”发生在工程师脑子里,是内隐的。AI Native 开发把这一步外显成了一份产物——Plan。原因是:
- AI 不会自动”先看清楚再动手”。你不要求 Plan,它就用概率最高的通用模式填空白;
- 人脑里的方案没法审查,写下来的 Plan 可以;
- Plan 是人类意图和 AI 执行之间的最后一道书面契约。过了这道门,AI 就要开始改真实文件了。
成本上,这是整条流水线里纠错最便宜的地方:
发现阶段 相对修复成本
Spec 阶段 1(改几句话)
Plan 阶段 3(改方案,没动代码)
实现阶段 8(回滚已写代码)
测试阶段 20(缺陷已固化,排查耗时)
生产阶段 100+(线上事故 + 数据 + 信任)Plan 评审的本质,是花 3 份成本去拦住后面 8~100 份的损失。
正确流程:先研究,再规划,最后才动手
Plan 阶段分三步,顺序不能反。
第一步:代码库研究(只读,不改任何东西)
在写任何方案之前,AI 必须先回答”现状是什么”。给它一份研究清单:
请只做调研,不要修改任何文件。针对"删除草稿文章"这个需求,
阅读代码库后回答:
1. 内容是怎么存储的?(数据库 / Markdown 文件 / CMS API?)
2. 文章的"状态"在代码里如何表达?(字段名、类型、取值)
3. 现有的文章相关接口 / 路由在哪些文件?签名是什么?
4. 权限校验现在在哪里做?有没有可复用的中间件 / 工具函数?
5. 删除一个 Markdown 文件,在本项目里对应什么操作?
6. 现有的测试怎么组织?测试框架、fixture、登录态怎么准备?
7. 有哪些文件看起来相关但【绝对不该动】?
每条结论都要标注:是从代码里读到的(给出文件:行号),
还是你的推测(明确标"推测,未验证")。最后一条”标注来源”是关键。它强迫 AI 区分事实和幻觉。前面那个软删除的事故,只要 AI 老实回答第 1 题”内容是 Markdown 文件存储(读到 src/content/blog/*.md)“,就不会造一张数据库表出来。
研究产出一张”现状事实清单”,每条带证据。这张清单是 Plan 的地基。
第二步:影响面分析
基于事实清单,列出改动的边界。这是 Plan 里最值钱的部分,三张清单缺一不可。下列路径属于假想全栈项目的示意,不是本博客当前仓库的现有文件:
【新增文件】
- src/pages/api/articles/[id]/delete.ts (删除接口)
- src/lib/article-delete.ts (删除逻辑,含权限/状态校验)
- tests/api/article-delete.test.ts (接口测试)
【修改文件】
- src/components/ArticleList.astro (加删除按钮,仅未公开文章 hide: true 显示)
- 说明:只加按钮和事件,不改列表加载逻辑
【明确不碰的文件】(重要)
- src/content/blog/** (不动任何现有文章内容)
- src/lib/article-loader.ts (列表加载逻辑,与删除无关)
- keystatic.config.ts (CMS 配置,本次不涉及)
- 全局权限模型 src/lib/auth/* (复用,不修改)“明确不碰”这张清单不是可有可无。它做两件事:
- 给实现阶段画红线——Implement 时 AI 一旦改了清单外的文件,就是越界,立刻能发现;
- 逼 AI 显式声明它理解了边界——前面事故里”顺手重构列表加载”之所以发生,就是因为没有任何地方写下”不准动列表加载”。
第三步:实现步骤 + 需求映射 + 风险回滚
把改动拆成有顺序的小步骤,并且每一步都映射回 Spec 的需求编号:
实现步骤(每步可独立验证):
1. 写 article-delete.ts 的权限与状态校验函数 → 覆盖 REQ-002/REQ-003
2. 写删除接口 delete.ts,调用校验 + 删文件 → 覆盖 REQ-001/REQ-005
3. 接口测试:管理员删未公开文章(hide: true)成功 → 覆盖 REQ-001
4. 接口测试:普通用户 403 且文件仍在(副作用断言) → 覆盖 REQ-002
5. 接口测试:删已公开文章(hide: false)返回 409 → 覆盖 REQ-003
6. 前端删除按钮 + 二次确认,仅未公开文章显示 → 覆盖 REQ-004/REQ-006
7. E2E:点取消确认时不发 DELETE 请求 → 覆盖 REQ-004映射表的检查方法很简单:把 Spec 里所有 REQ 编号列出来,逐个在 Plan 里找落点;找不到的,就是漏了。
风险和回滚也要写:
风险:
- 删除是不可逆操作(文件直接删)。缓解:接口内先做权限+状态双重校验,
前端二次确认;高风险,建议实现时先做"移动到 .trash 目录"而非物理删除,
验证通过后再决定是否改为物理删除。
- 静态站删除文件后需要重新构建才生效,本地 dev 与生产行为有差异。
回滚方案:
- 所有改动在独立分支 feature/article-delete
- 新增文件直接删;修改的 ArticleList.astro 用 git checkout 还原
- 不涉及数据迁移,无数据回滚成本Plan 完整模板
# Feature Plan: <功能名>
## Related spec
<!-- 关联的 Spec 文件路径与版本 -->
## Current architecture(现状事实,带证据)
<!-- 每条标注:读到的(文件:行号)/ 推测(未验证)-->
- 内容存储方式:
- 状态字段表达:
- 相关接口 / 路由:
- 权限校验位置:
- 测试组织方式:
## Files to add(新增)
- <路径> — <用途>
## Files to change(修改)
- <路径> — <改什么,明确到函数 / 区块>
## Files NOT to touch(明确不碰)
- <路径> — <为什么不碰>
## Data / API changes
<!-- 数据结构、接口签名、状态机的变化;没有就写"无" -->
## Implementation steps(含 REQ 映射)
1. <步骤> → 覆盖 REQ-xxx
2. ...
## Test plan
<!-- 每个 REQ 对应什么测试、在哪一层(单元/接口/E2E)-->
## Risks
<!-- 技术风险、不可逆操作、环境差异 -->
## Rollback plan
<!-- 出问题怎么退回,成本多大 -->
## Out of scope
<!-- 照搬 Spec 的非目标,防止实现时自由发挥 -->
## Open questions
<!-- 进 Implement 前必须清零;无法清零的转 Spike -->Plan 准入准出
准入(进入 Plan 阶段前必须满足):
- Spec 已过 Gate 1,质量标尺 ≥ 7 分;
- 技术不确定性已通过 Spike 清零,或显式列为风险;
- 现状研究有事实清单,关键结论都有代码证据,不是推测。
准出(Plan 可以送去评审 / 进入 Implement 的条件):
- 三张文件清单(新增 / 修改 / 不碰)齐全;
- 每个实现步骤都映射到至少一个 REQ,每个 REQ 都有步骤覆盖;
- 测试计划明确到”哪条 REQ 在哪一层测”;
- 风险和回滚方案写清,不可逆操作有缓解措施;
- Out of scope 与 Spec 非目标一致;
- Open questions 全部清零或转 Spike;
- 没有”顺便重构""顺手优化”这类无主改动。
三种 Plan 反模式
反模式一:步骤列表式 Plan。 只有”1. 写接口 2. 写按钮 3. 写测试”,没有影响面、没有不碰清单、没有回滚。这种 Plan 和没有一样——它拦不住越界修改,因为它根本没声明边界。
反模式二:推测式 Plan。 现状全靠猜:“本项目应该用了数据库""权限大概在中间件里”。没有文件
。这种 Plan 地基是虚的,后面全歪。识别方法:看事实清单里有没有”推测未验证”的条目还没被清掉。反模式三:黑盒式 Plan。 AI 给出一个”高大上”的方案(引入新框架、加一层抽象、上事件总线),但说不清为什么现有简单做法不行。Plan 阶段要默认怀疑”增加复杂度的方案”——AI 倾向于展示它会用复杂模式,而简单方案往往更对。评审时多问一句:“不引入这个,直接做,会怎样?“
失败回退:Plan 阶段出问题回到哪里
- 研究发现现状和 Spec 假设冲突(比如 Spec 假设有数据库,实际是文件)→ 回到 Spec 修正假设,或走 Spike 验证技术路径;
- 研究中冒出新的技术不确定性(不知道 Keystatic 能不能拦删除)→ 转 Spike,不要硬写进 Plan;
- Plan 发现需求之间矛盾或漏了关键场景→ 回到 Spec 补规则;
- Plan 本身方案选择有分歧 → 这是 Gate 2 评审要解决的,留在 Plan 阶段讨论,不要带进实现。
一条经验法则:Plan 阶段一旦出现”到时候再说”,这个”到时候”一定会在实现阶段变成事故。 所有”到时候再说”都必须在准出前移走——要么查清,要么转 Spike,要么明确列为风险并指定决策人。
人审重点
AI 可以生成 Plan 的初稿,但有几件事必须人来确认:
- 影响面判断是否符合架构直觉:AI 说”只改这一个文件”,人要判断这个改动会不会其实牵动别的模块;
- 方案复杂度是否合理:警惕 AI 过度设计;
- 不可逆操作的处理方式:删除、支付、发消息这类操作,人必须确认缓解措施;
- “不碰清单”是否真的全覆盖:这是最容易漏的,人最清楚系统里哪些地方牵一发动全身;
- Out of scope 是否被尊重:AI 很容易”贴心地”多加功能。
结语
Plan 不是给 AI 看的施工说明,而是给人看的风险声明。它最重要的内容不是”要做什么”,而是”不碰什么”和”出事怎么办”。
Plan 写好了,但写得好不等于能放行。Plan 是 AI 产出的,它会倾向于把方案写得漂亮、把风险写得轻描淡写。下一篇就讲 Gate 2 里人的动作——Spec 和 Plan 的评审:人到底该在什么时候介入、重点审什么、怎么用一份检查表在十分钟内看出一份 Plan 靠不靠谱,以及为什么”计划阶段的一句质疑,值实现后的一次回滚”。
