Home
avatar

.Sam

【AI Native开发-06】Plan 不是待办清单:AI 动手前还要查清什么?

30 秒看懂这篇

一份只有“写接口、写按钮、写测试”的 Plan,看起来完整,却回答不了三个关键问题:现状是什么、边界在哪里、出事怎么退。

flowchart TB
    A["已批准 Spec"] --> B["只读研究<br/>事实 + 文件证据"]
    B --> C["影响面<br/>新增 / 修改 / 不碰"]
    C --> D["实现步骤<br/>REQ 映射"]
    D --> E["测试计划<br/>风险 + 回滚"]
    E --> F{"Gate 2 评审"}
    F -->|通过| G["受控实现"]
    B -. "发现技术未知" .-> H["Spike"]
    H -. "结论回写" .-> B

Spec 回答“做什么”,Plan 回答“在当前代码库里怎么做”。REQ → Plan 映射能显著降低漏项风险,但还要靠评审与验证闭环,不能把它称为唯一防线。

本篇给出研究清单、完整模板、需求映射表和三种 Plan 反模式。案例仍是第 04 篇的假想全栈内容服务,不再把运行时 API 当作当前静态博客的部署能力。

一个案例场景(假想全栈项目)

接着”删除草稿文章”的需求。Spec 已经过了 Gate 1,业务规则、权限边界、验收标准都已明确。你把 Spec 丢给 AI:

“按这个 Spec 实现删除文章功能。”

AI 很快返回:“已完成。新增了 deleteArticle 接口,加了删除按钮,测试也写了。”

你一看 Diff,心凉了半截:

  • 它新建了一张 deleted_articles 数据库表做软删除——但这个假想服务的内容存储是受控 Markdown 文件,现有架构没有数据库;
  • 它顺手“重构”了文章列表的加载逻辑,把三个组件的 props 改了;
  • 它加了一个“回收站 30 天自动恢复”的功能——Spec 的非目标明确写了“不做回收站”。

问题不在 AI 笨,而在于它从 Spec 直接跳到了代码,没有先核实现状和边界。它用一套看似完整的通用后端模式,覆盖了项目事实与明确约束。

Plan 阶段就是用来拦住这一切的。

核心问题:为什么不能从 Spec 直接到代码

传统开发里,“想清楚怎么做”发生在工程师脑子里,是内隐的。AI Native 开发把这一步外显成了一份产物——Plan。原因是:

  • AI 不会自动”先看清楚再动手”。你不要求 Plan,它就用概率最高的通用模式填空白;
  • 人脑里的方案没法审查,写下来的 Plan 可以;
  • Plan 是人类意图和 AI 执行之间的最后一道书面契约。过了这道门,AI 就要开始改真实文件了。

成本上,Plan 评审通常比在实现或生产阶段纠错更便宜。下面沿用第 01 篇的教学相对成本示意,用于比较阶段,不是行业测量结果:

发现阶段        相对修复成本(教学示意)
Spec 阶段         1(改几句话)
Plan 阶段         3(改方案,没动代码)
实现阶段          8(回滚已写代码)
测试阶段         20(缺陷已固化,排查耗时)
生产阶段        100+(线上事故 + 数据 + 信任)

这组数字表达的是:在方案尚未变成代码前纠正问题,通常能避免后续实现、排查和数据修复的连锁成本;真实 ROI 应使用团队自己的耗时与缺陷记录。

正确流程:先研究,再规划,最后才动手

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/*             (复用,不修改)

“明确不碰”这张清单不是可有可无。它做两件事:

  1. 给实现阶段画红线——Implement 时 AI 一旦改了清单外的文件,就是越界,立刻能发现;
  2. 逼 AI 显式声明它理解了边界——前面事故里”顺手重构列表加载”之所以发生,就是因为没有任何地方写下”不准动列表加载”。

第三步:实现步骤 + 需求映射 + 风险回滚

把改动拆成有顺序的小步骤,并且每一步都映射回 Spec 的需求编号:

实现步骤(每步可独立验证):
1. 写 article-delete.ts 的权限与状态校验函数        → 覆盖 REQ-002/REQ-003
2. 写删除接口 delete.ts,调用校验 + 删文件           → 覆盖 REQ-001
3. 接口测试:管理员删未公开文章(hide: true)成功    → 覆盖 REQ-001
4. 接口测试:未登录 401 / 非管理员 403,文件仍在     → 覆盖 REQ-002
5. 接口测试:删已公开文章(hide: false)返回 409     → 覆盖 REQ-003
6. 前端删除按钮 + 二次确认,仅未公开文章显示          → 覆盖 REQ-004/REQ-006
7. E2E:点取消确认时不发 DELETE 请求                → 覆盖 REQ-004
8. 接口测试:不存在与重复删除返回 404,无额外副作用  → 覆盖 REQ-007
9. 重新构建并检查产物,确认文章与索引不再出现        → 覆盖 REQ-005

映射表的检查方法很简单:把 Spec 里所有 REQ 编号列出来,逐个在 Plan 里找落点;找不到的,就是漏了。

风险和回滚也要写:

风险:
- 物理删除不可逆。缓解:权限与状态校验、二次确认、测试只操作临时目录。
- 若希望改成 .trash / 软删除,业务语义已经改变,必须回到 Spec 重新评审,
  不能在已批准 Plan 中临时替换。
- 内容删除后需要新的发布构建才生效,本地与生产行为存在时间差。

回滚方案:
- 代码:独立分支 feature/article-delete;未合入时按提交回退,已合入时 revert。
- 内容:只从已提交 Git 历史或已验证备份恢复;未提交且未备份的删除不能靠分支找回。
- 验证:恢复后重新构建并确认文章、索引与关联资源符合 Spec。

把“代码回滚”和“内容恢复”分开写,能避免把 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 倾向于展示它会用复杂模式,而简单方案往往更对。评审时多问一句:“不引入这个,直接做,会怎样?“

回退与评审

现状或需求冲突回 Spec,技术未知转 Spike,方案分歧留在 Gate 2 讨论。人重点确认影响面、方案复杂度、不可逆风险和不碰清单;所有“到时候再说”都应在准出前查清、转 Spike,或显式记录风险与决策人。

结语

Plan 不是给 AI 看的施工说明,而是给人看的风险声明。它最重要的内容不是”要做什么”,而是”不碰什么”和”出事怎么办”。


Plan 写好了,但写得好不等于能放行。Plan 是 AI 产出的,它会倾向于把方案写得漂亮、把风险写得轻描淡写。下一篇就讲 Gate 2 里人的动作——Spec 和 Plan 的评审:人到底该在什么时候介入、重点审什么、怎么用一份检查表在十分钟内看出一份 Plan 靠不靠谱,以及为什么”计划阶段的一句质疑,值实现后的一次回滚”。

AI Native SDD Plan 技术方案 软件工程