Codex 进阶教程 02|任务稍一复杂它就开始乱改?用 Plan Mode 把需求关进笼子
Codex 进阶教程 02|任务稍一复杂它就开始乱改?用 Plan Mode 把需求关进笼子
前段时间,我让 Codex 顺手修一个后台列表页的 Bug。
问题很简单:用户在“正常”和“已禁用”之间快速切筛选条件时,旧请求因为响应慢,把新请求的结果给覆盖了(典型的前端竞态问题)。
我当时在对话框里随口扔了一句:这个用户列表切换筛选条件时偶尔会显示旧数据,帮我修一下。
Codex 吭哧吭哧读完代码,马上甩出了一套“宏伟”的方案:重做请求封装、引入全局请求状态、统一所有列表页的数据获取逻辑,顺手还想改一把缓存策略。
我只是想修个小 Bug,它却差点把我整个请求层给重构了。
这种情况大家肯定不陌生。我们只交代了“想改什么”,却没给 AI 划定“最多改到哪里”,以及“什么结果才算修好”。
吃过几次亏后,我现在处理稍微跨几个文件的任务,都会雷打不动地先开 Plan Mode。核心思路就一个:把 AI 对需求的过度理解和发散,全部掐死在写代码之前。
我的标准工作流是这样的:定义任务 -> /plan 调研 -> 狠批它的计划 -> 按阶段修改 -> 手动验证 -> 查 diff
1. 大炮不打蚊子:什么情况才需要开 Plan Mode?
在现在的 Codex 里,输入 /plan 就能进计划模式(或者按快捷键 Shift+Tab)。但我不会什么事都开。
遇到下面这几种情况,我必定先走计划:
- 调用链不明:改动可能跨文件,连我自己都没弄清到底会牵扯多深。
- 需求词汇模糊:比如“优化一下”、“重构一下”、“支持一下”这种 AI 极其容易放飞自我的词。
- 高危操作:动了认证、支付、权限或数据库,改错的代价太大。
- 方案未定:我知道想要什么结果,但还没想好用哪种代码实现。
反之,如果只是改个错别字、调个常量、或者修复某个精确到行的报错,直接让它改就行了,开计划纯属浪费时间。普通对话里说一句“先别改代码,分析一下”,也能起到轻量级的调查作用。
2. 别只甩一句话,先写份像样的“通缉令”
OpenAI 官方给复杂任务推荐了四个维度:Goal、Context、Constraints 和 Done when。
别被这些洋文名词唬住,其实也就是回答四个问题:
Goal:你要干嘛?Context:案发现场在哪?目前掌握了什么线索?Constraints:绝对不能动什么代码?(这个最重要,防重构神器)Done when:拿什么证明你干完了?
拿前面那个竞态 Bug 举例,我现在会这么提词:
/plan
Goal
修复后台用户列表快速切换“正常/已禁用”筛选条件时,旧请求结果覆盖新请求的问题。
Context
- 页面入口:src/pages/users/UserList.tsx
- 列表请求:src/hooks/useUserQuery.ts
- 浏览器里连续快速切换筛选条件可偶发复现
- 先调查实际调用链,别瞎猜问题一定出在 Hook 里
Constraints
- 绝对不准改后端接口
- 不要新增第三方依赖
- 严禁重构全局请求层和调整其他列表页
- 保持现有的 loading 和 error 样式不变
Done when
- 页面最终展示的数据,必须和最后一次选择的筛选条件一致
- 快速切换时,旧响应绝对不能覆盖新响应
- 补一条能覆盖这个竞态场景的回归测试
- 所有测试、lint 和 build 必须通过
先读代码,然后给我一份计划。计划里写清楚:你找到的根因证据、准备改哪些文件、有什么风险。先别急着写代码!
诀窍在于:Goal 是结果,不是动作;Constraints 要把 AI 可能想“顺手帮忙”的路堵死;Done when 必须是具体的业务表现,而不是一句干巴巴的“单测通过”。
3. 拿到计划后,先戴上有色眼镜审视它
以前看到 Codex 吐出一排排整齐的 Markdown 列表,我就会下意识觉得“它懂了”。后来才发现,格式工整和方向正确完全是两码事。它可能一本正经地给你出一个过度设计的杀鸡用牛刀方案。
拿到计划后,我会在心里问这五个问题:
- 它说找到了根因,有具体的调用链代码证据吗?
- 它列出的那些要改的文件,真的有必要全改吗?
- 它明确承诺了“哪些事这次不做”吗?
- 边界情况(乱序响应、组件卸载、报错中断)它考虑到了吗?
- 验证方法靠谱吗?还是只想糊弄一下编译器?
懒得自己看?可以直接把这口锅甩回给 Codex 验证它自己:
先别执行,重新审视一下你刚才的方案:
1. 给你的每个根因判断,附上具体的代码行或调用链证据。
2. 逐一解释为什么这些文件非改不可。
3. 标出本次任务明确“不碰”的代码范围。
4. 把验证阶段拆分为“自动化单测”和“手动交互验证”。
要是没把握就直说,告诉我还需要读哪些文件,不要乱猜。
如果它还是想扩大范围(比如“我帮你把整个项目的状态都迁到 Redux 吧”),直接强硬回绝:
方案范围太大了,超出了本次任务。
保留现有的请求封装,只处理用户列表中已确认的那个竞态点。
把方案缩成一个最小补丁(只改直接相关的页面和 Hook),其他列表页统统不准碰。
重新出个计划,说明缩小范围后为什么还能满足要求。
记住:没有确凿的代码证据,绝不接受 AI 扩充工作量的提议。
4. 计划敲定,像挤牙膏一样推进执行
即便计划很完美,最后可能只剩下三四步,也不要让它一次性把代码全吐出来。适合按 Checkpoint(检查点)一步步往前拱。
比如:
就按这个计划搞,但这一轮只做第 2 步。
要求:只修改最小范围的文件,先不补测试。改完停下来,告诉我你改了啥,以及这为啥能阻止旧响应覆盖。
每走完一步,先看 diff。在终端里直接敲:
git diff --stat
git diff -- src/pages/users/UserList.tsx src/hooks/useUserQuery.ts
重点抓包:它有没有偷偷改计划外的文件?有没有自作主张帮你格式化大段无关代码?
核心逻辑对味了,再下令做测试:
刚才的代码没毛病,继续做测试的 checkpoint。
写个回归单测,必须模拟出请求乱序:第一次请求先发但后到,第二次请求后发但先到。不要用死等(sleep),用项目里的 mock 机制去精准控制。
5. “测试通过”是 AI 最大的谎言
无数次任务最后,Codex 都会自豪地向你宣布:“测试和 Build 都通过了,搞定!”
千万别信。前端交互、缓存、并发这些复杂场景,现成的单测大概率根本没覆盖到原场景。
真实验收得分三层走:
- 基础设施:Lint、Type Check、Build 跑通。
- 行为验证:把手弄脏,到浏览器里狂点一通,看看它有没有为了过单测把业务逻辑给绕没了。
- Diff 审查:确认代码变动干净利落。
我通常用这段话作为最后通牒:
先别急着改代码,对着最初的 Done when 进行验收:
1. 逐条对照要求,给出现实证据。
2. 报一下你实际跑了哪些验证命令,别敷衍我说“理论上能过”。
3. 告诉我如果是手动快速切换,会观察到什么现象。
有没验的地方就标“未验证”,别给我假装大满贯。
6. 工具再好,也得有个趁手的底座(关于调用链路的吐槽)
/plan 是个好东西,但不适合所有场景。如果只是解决具体 Bug,计划确认后一路执行就够了;别动不动开全局 /goal,很容易跑飞。
这里顺便吐槽一下,当这种“反复读代码、跑测试、审 diff”的循环变成每天几十次的日常后,另一件事就变得极其要命:你的 API 调用链路抗不抗造?
以前我为了用满各家的能力,分别折腾了 Codex、Claude 和 Gemini 的账号绑定、环境配置和网络代理,每天被支付卡单和网络波动折磨。后来实在嫌麻烦,最终把日常调用转移到了中转平台(比如 modelapi01.com 这种专供 AI 编程场景的接口,很便宜,倍率低至 0.12x)。
说实话,对我来说最大的价值不是省那点钱,而是一套 API Key 管所有。不用每次换个模型就重新配一遍环境,用量看得很直观,网络也稳定,让我能把精力真正集中在“控制 AI 写代码”本身。
当然,官方链路跑得顺就继续用,重点是上面这套 “先定规矩 -> 挤牙膏开发 -> 严格验收” 的工作流不能丢。
7. 拿去即用的提示词模板
这里准备了三个开箱即用的 Plan Mode 模板,把路径换成你自己的就能用。
模板一:修复异步请求竞态
/plan
Goal
修复用户列表快速切换状态筛选时,旧请求结果覆盖新请求的问题。
Context
- 页面:src/pages/users/UserList.tsx
- 请求 Hook:src/hooks/useUserQuery.ts
- 快速切换“正常/已禁用”时可以偶发复现
Constraints
- 不改后端接口,不新增依赖
- 不重构公共请求层和其他列表页
- 保持现有 loading、empty、error 样式
Done when
- 页面最终数据始终对应最后一次筛选选择
- 回归测试能够模拟两个请求乱序返回
- 相关测试、lint 和 build 通过
先定位根因并给出最小修改计划。列出代码证据、修改文件、风险和验证方式,暂时不要改代码。
模板二:给订单列表增加取消操作
/plan
Goal
在订单列表中为 PENDING 状态订单增加“取消订单”操作,取消成功后更新当前行状态。
Context
- 列表页面:src/pages/orders/OrderList.tsx
- 行操作组件:src/pages/orders/OrderActions.tsx
- 已有取消接口:src/api/orders.ts 中的 cancelOrder
Constraints
- 复用现有取消接口、确认弹窗和消息组件
- 非 PENDING 订单不显示取消入口
- 不改变订单详情页和后端状态流转
- 不新增依赖,不调整列表整体样式
Done when
- PENDING 订单可以打开确认弹窗并完成取消
- 用户放弃确认时不发送请求
- 成功后当前行显示最新状态,不整页刷新
- 接口失败时保留原状态并显示现有错误提示
- 补充成功和接口失败测试
- 相关测试、lint 和 build 通过
先阅读相关组件和 API 实现,给出修改范围、状态更新方式、风险和验收计划。发现接口契约不明确时先提问,不要自行猜测。
模板三:局部迁移到项目现有请求封装
/plan
Goal
把订单模块中仍直接调用 fetch 的三个接口迁移到项目现有 requestClient,保持接口行为不变。
Context
- 待迁移文件:src/modules/orders/api.ts
- 现有封装:src/lib/requestClient.ts
- 参考实现:src/modules/users/api.ts
Constraints
- 只迁移订单模块,不处理其他直接调用 fetch 的文件
- 不改变 URL、HTTP 方法、请求参数和返回数据结构
- 不修改 requestClient 本身
- 不新增依赖,不顺手整理订单业务逻辑
Done when
- 三个订单接口全部改用 requestClient
- 调用方、现有行为和错误处理保持不变
- 现有接口测试通过,并补充缺失的错误响应断言
- lint、类型检查和 build 通过
- 最终 diff 不包含订单模块以外的业务文件
先对比旧实现、requestClient 和参考模块,列出三处调用的行为差异。给出分阶段迁移计划和回滚点,暂时不要改代码。
8. 一份好计划,应该能让你更容易说“不”
开工前要能说清:为什么改这些文件,哪些东西不改,原问题怎么复现,拿什么证明修好了。否则只是把返工推迟到 diff 已经很大的时候。
如果上一篇已经把项目规矩写进 AGENTS.md,任务说明就不用重复包管理器、测试命令和长期禁区。AGENTS.md 管长期规则,Plan Mode 负责调查当前任务。
下一篇准备继续讲一个经常被误判成“模型不稳定”的问题:审批、沙箱和环境权限到底该怎么配,什么任务应该保守,什么情况下可以逐步放权。
如果你最近也在用 Codex,我想知道你最常遇到的是哪一种:计划范围越列越大、测试通过但原问题没解决,还是它顺手改了任务之外的文件?把具体场景留在评论区,后面我会优先拆大家重复遇到的问题。
更多推荐




所有评论(0)