1. 为什么“会提问”比“会写代码”更影响 Codex 的产出质量
用 Codex 这类编码 Agent 的人,大概都经历过两种极端:一种是随便丢一句“帮我修个 bug”,它给你改了三行代码,跑起来直接崩;另一种是把任务背景、约束、验收标准写清楚,它一次就能给出可用的 diff,你只需要 review 一下就能合并。这两种体验的差距,本质上不在模型能力,而在任务描述的质量和你对 Code Diff 的检查习惯。
我自己用 Codex 做日常开发辅助有一段时间了,从最开始把它当“高级补全”用,到后来把它当“结对程序员”用,中间踩的坑基本都集中在两个地方:一是提问太随意,导致它理解偏差;二是拿到 diff 就直接 apply,结果引入了一些隐蔽问题。后来我慢慢总结出一套方法:把任务描述当成一份 mini 技术方案来写,把 Code Diff 当成一次 code review 来做。这套方法不依赖具体模型版本,换任何编码 Agent 都适用。
这篇文章适合三类人:刚接触 Codex、还在摸索怎么提问的新手;已经能用但产出不稳定的中级用户;以及想把 Codex 接入团队工作流、需要一套可复制规范的开发者。我会从任务描述的拆解方法讲起,再到 Code Diff 的逐层检查技巧,最后给出一套可以直接抄的提问模板和检查清单。全程都是实操经验,不讲虚的。
2. 任务描述到底该怎么写:从“一句话需求”到“可执行规格”
2.1 任务描述的三个层次:意图、约束、验收
很多人提问效果差,根本原因是只给了“意图”,没给“约束”和“验收”。我把它拆成三层:
- 意图层:你想让 Codex 做什么。比如“给用户列表接口加分页”。
- 约束层:在什么条件下做。比如“不能改现有接口签名”“必须兼容旧版分页参数”“用项目已有的 PageHelper”。
- 验收层:怎么算做完。比如“单测通过”“返回结构保持
{code, data, msg}”“边界情况 pageSize=0 要返回空列表”。
只给意图,Codex 会按它自己的“常识”补全约束和验收,而它的常识往往和你的项目规范不一致。这就是为什么同样的需求,别人用 Codex 很顺,你用就总出问题。
我实测下来,约束层的信息量对结果影响最大。因为意图层模型基本能猜对,验收层你可以事后检查,但约束层一旦缺失,它就会自由发挥,改出你意想不到的东西。
2.2 把“帮我修 bug”翻译成 Codex 能执行的规格
“帮我修 bug”是典型的无效提问。Codex 不知道 bug 在哪、什么表现、期望什么行为。我一般会把它翻译成这样的结构:
现象:调用 /api/user/list 时,当 pageNum 超过总页数,返回 500 而不是空列表。 复现步骤:1. 请求 pageNum=999;2. 观察响应状态码。 期望行为:pageNum 超出范围时返回 200,data 为空数组。 约束:只改 UserController.list 方法,不要动 Service 层逻辑。 验收:补充一个单测覆盖 pageNum 超界的情况。你看,同样是“修 bug”,这样写之后 Codex 基本能一次定位到问题。原因是它拿到了可验证的输入输出,而不是一个模糊的目标。
提示:如果你连现象都描述不清楚,说明你自己还没定位到问题。这时候别急着让 Codex 改代码,先让它帮你分析日志或复现路径,把问题定位清楚再进入修改环节。
2.3 上下文注入:哪些文件、哪些历史要一起给
Codex 不是全知全能的,它只能看到你给它的上下文。所以提问时,主动注入相关文件比让它自己找效率高得多。我的习惯是:
- 给出入口文件:比如
UserController.java。 - 给出被调用的关键方法:比如
UserService.list()。 - 给出相关的数据结构:比如
PageResult的定义。 - 如果是修 bug,给出报错日志或堆栈。
有人会问:Codex 不是能自己读整个仓库吗?能,但读得越多越慢,而且容易把不相关的代码也纳入考虑,反而干扰判断。主动缩小范围,等于帮它做了一次信息过滤。
我试过一个对比:同一个分页 bug,一次只给入口文件,一次给入口+Service+数据结构。前者 Codex 改了 Controller 里的参数校验,后者直接定位到 Service 里分页计算越界。差距非常明显。
2.4 提问模板:我日常在用的五段式结构
经过多次调整,我固定下来一个五段式模板,基本覆盖了 90% 的编码任务:
- 背景:这个模块是干什么的,当前状态是什么。
- 目标:这次要达成什么,用一句话说清。
- 约束:不能碰什么,必须用什么,兼容什么。
- 验收:怎么验证做完了,最好有可执行的检查点。
- 参考:相关文件、类似实现、文档链接。
这个模板的好处是,写的时候你自己也会重新梳理一遍需求,很多模糊的地方在写的过程中就清晰了。我经常写着写着发现“哦,这个边界情况我还没想清楚”,然后先去确认再提问,避免来回返工。
3. Code Diff 检查:拿到改动后先别急着 apply
3.1 Diff 检查的第一遍:看“改了什么范围”
Codex 给出的 diff,第一眼不要看具体代码,先看改动范围。我一般关注三点:
- 改了哪些文件?有没有动到不该动的文件?
- 每个文件改了多少行?有没有大段删除?
- 有没有新增文件?新增的文件是否必要?
这一步能过滤掉大部分“跑偏”的情况。比如你让它改一个工具函数,它却顺手重构了调用方,这种范围蔓延在 diff 里一眼就能看出来。
我遇到过一次,让它修一个空指针,它把整个类的异常处理都重写了。虽然逻辑上没错,但 review 成本极高,而且引入了新的风险。后来我养成了习惯:范围不对,直接打回重问,不要试图在错误的 diff 上修修补补。
3.2 Diff 检查的第二遍:看“逻辑是否正确”
范围没问题后,进入逻辑检查。这一步要逐块看,重点关注:
- 条件判断:边界条件有没有覆盖,
>=和>有没有搞反。 - 空值处理:新增的变量有没有可能为 null。
- 循环边界:有没有 off-by-one。
- 异常路径:出错时是抛异常还是返回默认值,是否符合预期。
这些是 Codex 最容易出错的地方,因为它倾向于“让代码跑通”,而不是“让代码健壮”。我一般会拿几个典型输入在脑子里过一遍,或者直接写个快速测试跑一下。
3.3 Diff 检查的第三遍:看“风格与一致性”
逻辑对了,还要看风格。Codex 有时会用它自己的命名习惯,或者引入项目里没有的依赖。检查点包括:
- 命名是否符合项目规范(驼峰还是下划线,动词开头还是名词)。
- 有没有引入新的 import 或依赖。
- 日志、注释的风格是否一致。
- 有没有留下调试代码(
console.log、print、TODO)。
这一步最容易被忽略,但长期看影响很大。一个项目里混着几种风格,维护起来很痛苦。我一般会在提问时就说明“遵循现有代码风格”,减少后期调整。
3.4 一个真实的 Diff 检查案例
举个我实际遇到的例子。需求是“给导出功能加一个进度提示”。Codex 给出的 diff 大致是:
// 原代码 public void exportData(Query query) { List<Data> list = dataService.query(query); excelWriter.write(list); } // Codex 改动 public void exportData(Query query) { List<Data> list = dataService.query(query); int total = list.size(); for (int i = 0; i < total; i++) { progressService.update(i, total); excelWriter.writeOne(list.get(i)); } }范围没问题,逻辑上也能跑。但检查时发现两个问题:一是excelWriter.writeOne这个方法项目里不存在,是 Codex 假设的;二是进度更新在循环里同步调用,数据量大时会拖慢导出。这两个问题在 diff 里都能看出来,如果直接 apply 就会编译失败或性能下降。
后来我重新提问,明确说明“使用现有的excelWriter.write(List)方法”和“进度更新异步化”,才拿到可用的 diff。这个案例说明,diff 检查不是走过场,而是真正能拦住问题的环节。
4. 高频问题排查:Codex 提问与 Diff 的常见坑
4.1 提问类问题速查
| 问题现象 | 可能原因 | 解决方向 |
|---|---|---|
| 改动范围过大 | 约束层缺失 | 明确“只改 X 文件/方法” |
| 逻辑跑偏 | 意图描述模糊 | 补充现象、期望、复现步骤 |
| 用了不存在的 API | 上下文不足 | 注入相关文件或接口定义 |
| 风格不一致 | 未说明规范 | 提问时附上风格要求 |
| 反复改不对 | 需求本身没想清 | 先梳理需求再提问 |
4.2 Diff 类问题速查
| 问题现象 | 可能原因 | 解决方向 |
|---|---|---|
| 编译失败 | 引用了不存在的符号 | 检查 import 和方法名 |
| 边界出错 | 条件判断不严谨 | 重点看>=、>、null 判断 |
| 性能下降 | 循环内同步操作 | 检查是否有可异步/批量的点 |
| 引入新依赖 | 未限制依赖 | 提问时说明“不新增依赖” |
| 留下调试代码 | 未清理 | 搜索log、print、TODO |
4.3 我的三条避坑心得
第一,不要在一次提问里塞多个不相关的任务。比如“修这个 bug 顺便把日志格式也改了”,Codex 很容易顾此失彼。拆成两次提问,每次聚焦一件事,成功率更高。
第二,diff 检查要当成正式 code review。我见过太多人因为“是 AI 写的”就放松警惕,结果引入的问题比手写还多。AI 不会为线上事故负责,负责的是你。
第三,保留提问记录。同一个模块的多次提问,我会把有效的描述存下来,下次遇到类似任务直接复用。这比每次重新组织语言效率高得多,也能形成自己的提问库。
5. 把提问和检查固化成流程:我的日常操作清单
5.1 提问前的三分钟准备
在打开 Codex 之前,我会花三分钟做三件事:
- 打开相关文件,确认当前代码状态。
- 写下这次任务的背景、目标、约束、验收。
- 确认没有遗漏的边界情况。
这三分钟看起来是额外开销,但能省下后面反复沟通的时间。我算过,准备充分的提问,平均一次就能拿到可用 diff;准备不足的,往往要来回三四次。
5.2 Diff 检查的四步法
拿到 diff 后,我固定按四步走:
- 范围检查:文件、行数、新增文件。
- 逻辑检查:条件、空值、循环、异常。
- 风格检查:命名、依赖、注释、调试代码。
- 验证检查:跑测试、跑 lint、手动验证边界。
这四步走完,基本能拦住 95% 的问题。剩下的 5% 往往是需求本身理解偏差,需要在提问阶段解决。
5.3 一个可复用的提问模板
最后把我常用的模板贴出来,你可以直接改成自己项目的版本:
背景:<模块功能,当前状态> 目标:<一句话说清要做什么> 约束: - 只改 <文件/方法> - 不新增依赖 - 遵循现有代码风格 验收: - <可执行的检查点1> - <可执行的检查点2> 参考: - <相关文件路径> - <类似实现路径>这个模板不复杂,但能覆盖大部分场景。用久了之后,你会发现提问本身就是在梳理思路,很多问题在写模板的过程中就暴露出来了。
我个人在实际操作中的体会是,Codex 这类工具的上限很高,但下限取决于你怎么用它。提问越具体,diff 越可控;检查越细致,返工越少。把它当成一个需要明确指令、需要认真 review 的协作者,而不是一个许愿池,产出质量会稳定很多。