适用读者:收到告警先改代码的人;用 AI 工具修 bug、被"报错消失但问题还在"坑过的人。
前置:S16-J03、S15(Linux 排查)、S17(分布式)。
覆盖旧文:A-228 → 160 点(四类活儿的分工、修 bug 四步模板、重构与写测试的边界);A-560 §18 的实际排错案例。
扩展点:E-S16-008(排障证据链)。
上一篇:S16-J03 · SkyWalking:Agent / OAP / UI 与服务拓扑
下一篇:S16-A01 · 可观测性架构。
说明:A-228 的原文是 AI 编程工具的提示词模板,其主讲在 S24(
duplicate_ref)。本篇只保留"验证与排障"的视角:模板长什么样不重要,证据顺序才重要。
1. 四类活儿:先分清"动没动代码"(§1)
原文把日常高频任务分成四类,分界线是这活儿动不动代码:
| 活儿 | 动代码吗 | 你要盯的 | 失败形态 |
|---|---|---|---|
| 探索代码库 | 不动(只读) | 它讲的对不对 | 编造调用关系、漏掉入口 |
| 修 bug | 动 | 根因找对没、回归测试有没有 | 报错消失,根因还在 |
| 重构 | 动(行为不变) | 改完行为变没变 | 顺手改了行为 |
| 写测试 | 动(只加测试) | 测试能不能抓错 | 断言恒真 |
判据:动手前先回答"这是哪一类"。修 bug 与重构混在一句话里,是最常见的翻车起点——两类活儿的目标相反(一个改行为,一个不改)。
2. 修 bug 的四步模板(§2)
原文的四步(来自 Coding Agent 的官方最佳实践:"解决根本原因,不要抑制错误"):
① 贴完整报错 + 复现步骤(必现还是偶发、我做了什么才触发)
② 先定位根因,解释为什么会错——此时不要改代码
③ 给修复方案:解决根因,不要只把报错盖住
④ 补一条能复现该 bug 的回归测试,跑一遍确认通过
| 步 | 证据 | 不合格信号 |
|---|---|---|
| ① | 报错原文 + 触发条件 | "有时候会报错"(无复现路径) |
| ② | 一段能解释"为什么"的话 | 直接给 diff |
| ③ | diff 最小、只改根因处 | 顺手改了 5 个文件 |
| ④ | 一条先红后绿的测试 | 没测试;或测试恒绿 |
为什么第 ④ 步最值钱:原文举了日期解析 bug 的例子——当时改好了,两周后同事重构把那行改回去,同一个 bug 原地复活,因为没留测试,没人知道那行碰不得。
判据:把修复回滚,测试会不会红? 不会红 → 测试没锁住这个 bug。
3. 排障的证据链:指标 → 链路 → 日志 → 代码(E-S16-008)
A-560 的案例是最好的样本:用户投诉"下单特别慢"→ SkyWalking 看到 Create Order 7200ms → 打开 trace:Gateway 10 / Order 200(User 50 / Inventory 100 / Payment 6800) → 进 Payment 看到 Third Party HTTP 6500ms → 定位第三方支付 API 慢。几分钟从"感觉慢"到"具体哪一跳慢"。
四步证据链(顺序不能反):
| 步 | 看什么 | 回答的问题 | 常见误判 |
|---|---|---|---|
| ① 指标 | P95、错误率、QPS、饱和度 | 是"整体变慢"还是"个别接口" | 用均值 → 长尾被隐藏(J03 §4) |
| ② 链路 | 一次慢请求的 span 树 | 慢在哪一跳 | 只看总耗时,不看子树 |
| ③ 日志 | 该 traceId 的日志 | 那一跳为什么慢/错 | 换了机器,日志不在同一文件 |
| ④ 代码 | 对应分支 | 逻辑是否成立 | 没复现就改代码 |
反例(顺序错乱):先看代码猜原因 → 改一版上线 → 指标没变 → 再猜。每轮 30 分钟,三轮过去还没定位。
3.1 两个高频分叉
| 现象 | 分叉判断 |
|---|---|
| 指标显示只有 P95 涨、QPS 平稳 | 查依赖(DB/缓存/第三方)的 P95,而非应用 CPU |
| 指标显示 QPS 也涨 | 先判定是否流量型(扩容可解),再查代码 |
| 链路里某一跳耗时≈超时阈值 | 查超时与重试配置(重试风暴常伪装成"变慢") |
| 日志无异常但链路错误率高 | 查被吞掉的异常(catch 后没打日志) |
4. 三个模板的边界(§3)
| 模板 | 适用 | 不适用 |
|---|---|---|
| 修 bug 四步 | 有明确报错与复现路径 | 性能问题(无报错,走 §3 证据链) |
| 重构"先讲现状→定目标→小步改→改前改后都测" | 行为可被测试覆盖 | 无测试的遗留代码(先补特征测试) |
| 写测试"先写失败用例" | 需求明确 | 探索性需求(先写主干 E2E,B04) |
重构的前提(原文的判断):"改前改后都测"——没有测试的重构不是重构,是重写。遗留代码先写特征测试(characterization test):把当前行为(哪怕是 bug)固定下来,再动结构。
5. 复盘:把一次排障变成资产
一次排障结束后,必须留下三样东西(否则同样的故障会再来一次):
| 产物 | 内容 | 去处 |
|---|---|---|
| 时间线 | 告警时间、定位时间、恢复时间、影响面 | 事故记录 |
| 防复发 | 检测(新告警)、防御(超时/降级)、流程(评审项) | 监控配置 / 代码 |
| 可复用查询 | traceId 怎么查、哪个面板看哪条曲线 | runbook |
判据:同类故障三个月内是否复发? 复发 → 防复发项没落地。
6. 常见误区
| 误区 | 纠正 |
|---|---|
| "报错消失=修好了" | 根因是否被解释、是否被测试锁住(§2) |
| "先改代码再看效果" | 先复现,再定位,再改(§3 顺序) |
| "平均值正常就没问题" | 看 P95/P99(J03 §4) |
| "日志没报错就是没问题" | 异常可能被 catch 吞掉;看链路状态 |
7. 自测
- 你上一次修 bug,有没有留下"回滚修复就会红"的测试?
- 把最近一次"感觉慢"的投诉,按 §3 四步走一遍,能在 10 分钟内定位到具体一跳吗?
- 你的项目里有多少
catch (Exception e) {}?它们是排障的黑洞。
8. 下一篇
S16-A01 · 可观测性架构:采样、成本、保留与故障域 —— 从"能排障"到"可持续地排障":采多少、存多久、花多少钱、坏了怎么办。