Skip to content

用 Codex 复现并修复 Bug

修 Bug 的核心不是“尽快改代码”,而是先得到一个能够稳定区分修复前后的失败证据。没有复现就修改,Codex 很可能修掉一个看起来可疑但与现象无关的位置。

准备一张问题卡

把信息整理成下面五项:

  1. 原始现象:保留准确错误文本、状态码或用户可见行为。
  2. 环境:版本、分支、操作系统、浏览器、数据条件。
  3. 复现步骤:从干净状态开始,逐步可执行。
  4. 期望与实际:不要只写“失败了”。
  5. 最近变化:相关提交、配置或依赖变化,未知就写未知。

日志先脱敏。Token、Cookie、邮箱、手机号、订单号和生产数据不应直接粘贴。

第一步:只复现,不修改

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

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

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

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

如果无法复现,不要直接改:

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

第二步:缩小问题层级

让 Codex判断错误最先出现在哪一层:

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

实用追问:

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

这能避免只在最外层加 try/catch 或默认值,掩盖真正原因。

第三步:先写失败验证

优先级从高到低:

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

提示:

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

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

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

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

修改后先看:

bash
git diff --stat
git diff

第五步:按证据阶梯验证

从最小到最大执行:

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

让 Codex用表格报告:

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

第六步:检查修复是否只是偶然

对时序、缓存和并发 Bug,单次通过不够。可要求:

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

具体次数按测试成本调整。涉及外部付费 API 时不要盲目重复。

不同 Bug 的额外检查

数据错误

检查受影响数据范围、幂等性、旧数据修复和回滚脚本。先在副本或事务中验证。

权限错误

同时测试允许和拒绝路径,确认前端隐藏不等于后端授权。

性能退化

记录修复前后相同输入、相同环境和相同统计口径。一次主观体感不是性能证据。

UI Bug

检查桌面、移动端、键盘操作、loading、空状态和错误状态。截图对比只能证明外观,不能证明交互。

常见伪修复

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

完成报告模板

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

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

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

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

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

下一步

如果问题修复需要新增完整能力,继续学习 用 Codex 开发一个完整功能

参考

程序员小枫同学:用好新工具,练好工程内功,做出可靠交付。