Skip to content

用 Codex 复现并修复 Bug

嘿,朋友,今天聊聊怎么用 Codex 修 Bug。说起来你可能不信,我刚入行那会儿修 Bug 的方式就是:看到报错 → 直觉告诉我哪里有问题 → 改一行代码 → 祈祷。结果呢?十次有五次修出了新 Bug,还有三次改了跟现象八竿子打不着的地方。后来踩的坑多了才琢磨出一个道理:修 Bug 的核心,是先拿到一个能稳定区分"修之前挂、修之后好"的失败证据。 没有复现就动手,Codex 再聪明也容易跑偏——它可能会帮你修掉一个"看起来可疑"的位置,但那个位置跟真实问题半毛钱关系都没有。

好了,下面我按自己踩坑之后沉淀下来的流程,一步步跟你走一遍。

准备一张问题卡

动手之前,先把信息拢一拢。我习惯整理成下面五项,缺哪项就先去补哪项:

  1. 原始现象:保留准确的错误文本、状态码、用户看到的实际行为。别用"它报错了"一笔带过。
  2. 环境:版本号、分支、操作系统、浏览器、数据条件——能写多细写多细。
  3. 复现步骤:从干净状态开始,一步一步可执行。你让一个刚入职的同事照着做,他能复现出来,这就算合格。
  4. 期望与实际:期望是什么行为,实际发生了什么。两个都要写清楚。
  5. 最近变化:相关提交、配置改动、依赖升级,不知道就老实写"未知"。

⚠️ 日志先脱敏。Token、Cookie、邮箱、手机号、订单号和生产数据别直接往里贴。我曾经手滑把带 token 的日志扔进 issue,回想起来一身冷汗。

第一步:只复现,不修改

这一步克制住你那颗想改代码的心,先老老实实复现。

text
Bug:[原始错误或现象]

环境:[版本和必要条件]
复现步骤:
1. ...
2. ...

期望:[正确行为]
实际:[错误行为]

先不要修改。请实际执行最小复现,保存失败命令、状态码、堆栈或页面行为。然后沿真实调用链定位第一个错误状态产生的位置。把证据、推断和未验证项分开。

我自己的教训:有次一个接口报 500,我一眼就"看出"是缓存 key 拼错了,改完上线照旧挂。后来老老实实加日志沿着调用链追,才发现是上游服务在某个特定参数下返回了 null,缓存那层根本没问题。所以,复现不通,别改。

如果怎么都复现不了:

text
当前未复现。请比较复现环境与本机环境,列出最可能缺失的前置条件,并给出下一轮最小采集方案。不要提交猜测性修复。

让 Codex 帮你排查环境差异,比你一个个去猜高效多了。

第二步:缩小问题层级

复现出来之后,别急着跳进代码细节。先让 Codex 帮你判断错误最先出现在哪一层:

  • 输入或前端状态
  • 网络与协议
  • 路由和参数解析
  • 业务规则
  • 数据库或缓存
  • 外部服务
  • 并发、时序或重试
  • 配置与环境差异

我常用这个追问,效果不错:

text
请找"第一个错误值或错误状态"产生的位置,而不是最后抛异常的位置。列出它进入下一层前的输入和输出。

为什么强调"第一个"?因为异常往往被层层包装,最后抛出来的地方早就离案发现场十万八千里了。在最外层加个 try/catch 或者补个默认值,只是把问题埋得更深,后面排查成本翻倍。

第三步:先写失败验证

改代码之前,先搞一个能"证明它确实坏了"的东西。优先级从高到低:

  1. 现有测试能直接稳定失败
  2. 补一个最小回归测试
  3. 写一个临时复现脚本
  4. 保存一条可重复的 HTTP 请求
  5. 记录明确的 UI 操作和可观察结果

提示模板:

text
在修改生产代码前,先添加或找到一个能稳定暴露该问题的最小验证。运行它并确认修复前确实失败。测试必须验证用户可见行为或真实数据变化,不要只测试内部实现细节。

这个习惯帮我省过无数次返工。没有失败验证就改代码,就像蒙着眼睛扔飞镖——你以为命中了,其实完全不知道扔到了哪里。

第四步:实施最小根因修复

终于到改代码了,但要管住手:

text
根据已经确认的根因实施最小修复。

约束:
- 不改变无关公开接口;
- 不通过跳过校验、吞异常、延长固定等待或删除测试绕过;
- 不顺便重构相邻模块;
- 需要数据迁移或兼容分支时先说明;
- 保留失败时可诊断的信息,但不记录敏感数据。

我踩过最大的坑之一就是"顺手重构"——修一个空指针,顺便把一个 200 行的函数拆了。结果重构引入了三个新 Bug,排查了一整天。修 Bug 就是修 Bug,重构的事另开 PR。

改完之后先看一眼 diff:

bash
git diff --stat
git diff

确认改动范围只跟你预期的根因相关,没有"意外地"多改了五个文件。

第五步:按证据阶梯验证

验证要有层次,从最小范围到最大范围逐级走:

  1. 新增的失败测试现在通过
  2. 同模块测试通过
  3. 相关集成测试通过
  4. 构建、类型检查和 lint 通过
  5. 原始复现步骤不再失败
  6. 相邻正常路径没有回归

让 Codex 用表格给你汇报:

text
列出每项验证的命令、结果、耗时摘要和覆盖的风险。单独列出没有运行的检查及原因。

表格一目了然,比扫终端日志高效得多。

第六步:检查修复是不是"碰巧好了"

这个坑我掉进去不止一次。时序、缓存、并发相关的 Bug,单次跑通说明不了任何问题。可以这样要求:

text
把最小复现连续运行 20 次,记录失败次数。不要通过无限重试掩盖失败;如果结果不稳定,继续定位共享状态、竞态或时间依赖。

具体次数看你的测试成本,涉及外部付费 API 就别傻傻地重复调用。核心原则是:多次验证,确认它不是碰巧绿的。

不同 Bug 的额外检查

不同类型的 Bug,有各自的坑点:

数据错误

检查受影响数据范围、幂等性、旧数据修复和回滚脚本。先在副本或事务中验证,别直接上生产数据。我曾经一条 UPDATE 没加 WHERE 把整张表改了,幸好有备份,不然那天就不用下班了。

权限错误

同时测允许路径和拒绝路径。确认"前端按钮隐藏了"不等于"后端做了鉴权"——直接拿 curl 调接口,绕过前端,你就知道后端到底有没有拦住。

性能退化

记录修复前后的对比:相同输入、相同环境、相同统计口径。一句"感觉变快了"不能当性能证据——你那会儿可能只是网络好。

UI Bug

桌面端、移动端、键盘操作、loading 态、空状态、错误状态,全走一遍。截图对比只能证明"看起来一样",不能证明交互没坏。

常见伪修复

这些是我见过(甚至干过)的"修了等于没修",供你避坑:

  • 捕获所有异常后返回成功
  • 把超时从 5 秒改成 60 秒
  • 删除失败测试
  • 用随机 sleep 解决竞态
  • 给空值补默认值,却不解释为什么为空
  • 只改错误消息,不改错误状态
  • 只在开发环境验证配置问题

看着像修好了,其实只是把问题埋得更深。

完成报告模板

修完之后,用这个模板收个尾:

text
根因:
[第一个错误状态在哪里产生,为什么]

修复:
[修改文件和行为]

回归测试:
[修复前怎样失败,修复后怎样通过]

验证:
- [命令/步骤]:[结果]

剩余风险:
[未覆盖环境、历史数据或外部依赖]

写清楚"剩余风险"尤其重要——没有哪次修复是完美的,把你知道的盲区坦白写出来,后面接手的人会感谢你的。

下一步

如果这次修 Bug 的过程中发现需要新增完整能力,去看看 用 Codex 开发一个完整功能

参考