跳转至

Fix-Regression 防御 — 修 A 不要引入 B,修 B 不要引入 C

核心:修 bug 时没扫所有触发路径 / 假定单一原因 / 没多场景测试新代码引入新 bug = fix-regression 连环。

本项目 v0.10.117 → v0.10.122 出现 2 次连环 fix-regression,必须沉淀成规则强制防御。

反模式(红信号)

反模式 1:单点假定修复(最常见)

// ❌ 修 ISSUE-0083 时只想到一种原因
if (row.scrape_status === 2) {
  return <Tooltip title="日志已被环形 buffer 淘汰"></Tooltip>;
  //                    ↑ 假定单一原因 — 实际还有 batchDedupeByUrl / sourceRow 复用 2 种
}

修了表象,没扫所有触发路径 → tooltip 撒谎 → ISSUE-0085 诞生。

反模式 2:副作用未感知

// v0.10.117 修嵌套滚动
<Box sx={{ overflow: 'hidden' }}>   // 改这个为 hidden
  <Box sx={{ height: tableH }}>      // 但这个还是固定 height
    <DataGrid />                      // 当 tableH > 实际可用高度时,分页器被裁
  </Box>
</Box>

只看修复点没看父子约束 → 引入 ISSUE-0081 分页器被裁。

反模式 3:阈值往严调(修过严)

// ISSUE-0072: cleanupOrphan 误杀新标签页
const SIZE_TOLERANCE = 80;  // v0.10.101 设的,太宽
// v0.10.103 修:±80 → ±20
// 但用户新标签页尺寸恰巧 ±20 内的也被误杀 — 又一个 fix-regression

修阈值时没列"宽 vs 严的副作用矩阵"。

反模式 4:连环 fix-regression(最严重)

v0.10.117 修 ISSUE-0078 嵌套滚动  → 引入 ISSUE-0081 分页器被裁
v0.10.120 修 ISSUE-0081 + 加兜底  → 引入 ISSUE-0085 tooltip 撒谎
v0.10.122 修 ISSUE-0085           ← 还在收拾

同一文件连续 N 次 commit 都在修上次引入的问题 → 强烈信号:当时根因没分析透。


正模式(绿信号)

正模式 1:修前 impact-trace(必做)

修任何代码前先 grep,列出所有相关写入/读取点:

# 修 HTTP 列空显示前,先 trace 谁能让 scrape_status = 2
$ grep -rn "scrape_status: 2" src/utils/
src/utils/scraper-executor.ts:115     # 路径 A:社媒短路
src/utils/scraper-executor.ts:157     # 路径 B:查重复用 sourceRow(不写 page-log)
src/utils/scraper-executor.ts:174     # 路径 B 续
src/utils/scraper-executor.ts:291     # 路径 C:tab 抓成功
src/utils/scraper-executor.ts:431     # 路径 D:pipeline success
src/utils/scraper-executor.ts:453     # 路径 E:pipeline skip
src/utils/engine-manager.ts:171       # 路径 F:blacklist 跳过
src/utils/engine-manager.ts:194       # 路径 G:batchDedupeByUrl(不写 page-log)

8 个路径里有 2 个不写 page-log — 这是修复必须考虑的所有场景。

正模式 2:列举触发场景(写进 commit / ISSUE)

修复 commit message / ISSUE 文档里写明:

## 触发场景列举(修前调查)
1. 5000 条 buffer 淘汰
2. batchDedupeByUrl 复用(v0.10.114 路径不写 page-log)
3. sourceRow 复用(scraper-executor:152-178 不写 page-log)

## 修复策略
对每个场景都成立的兜底:用启发式区分(hasContact 判断)+ 措辞"可能是 X 或 Y"

正模式 3:兜底文案诚实化

兜底分支显示的文案绝不假定单一原因

// ❌ 反例
<Tooltip title="日志被淘汰"></Tooltip>

// ✅ 正例 — 用启发式 + 措辞"可能是 / 或"
const hint = hasContact
  ? '可能是同 URL 其他商家先采过,通过批量复用获得'
  : '可能是:批量复用、缓存命中、或日志已淘汰';
<Tooltip title={hint}></Tooltip>

正模式 4:副作用矩阵(layout / 阈值 调整时)

改 overflow:'auto' → 'hidden' 的副作用矩阵:
              | 子元素 height < 父 | 子元素 height > 父
              |-------------------|-------------------
overflow:auto | OK                | 出滚动条(嵌套)
overflow:hidden| OK                | 被裁掉(看不到分页器)   ← 新引入的问题

→ 修复前必走完矩阵 4 格

fix-regression 检查清单

修任何 bug 前必问:

[ ] 这个 bug 的所有触发路径都列了吗?(grep 字段/变量/状态写入点)
[ ] 我的修复方案对每个路径都成立吗?
[ ] 兜底分支文案/逻辑假定了哪些前提?
[ ] 改 layout/阈值时画了副作用矩阵吗?
[ ] 同一文件上次 commit 是修啥?是不是同一类问题?

同文件改动追踪(连环 fix-regression 预警)

git log 看同一文件最近 3 commit:

$ git log --oneline src/sections/data/data-view.tsx | head -5
7d7b9b4 v0.10.122 HTTP  tooltip 诚实化
0f15566 v0.10.120 UI 3 处修复
41eb0c1 v0.10.117 UI bug 修复
8209b50 v0.10.115 补沉淀

如果连续 3 次都是 fix-regression → 强烈信号停下来重新设计,不要继续打补丁。


本项目历史 fix-regression 案例

起始 ISSUE 引入的 regression 第几次 fix
ISSUE-0008(v0.10.2 jsstore where:taskId) ISSUE-0071(v0.10.103 重蹈) 2 次
ISSUE-0070(v0.10.101 cleanupOrphan) ISSUE-0072(v0.10.103 误杀新标签) 2 次
ISSUE-0078(v0.10.117 嵌套滚动) ISSUE-0081(v0.10.120 分页器被裁) 2 次
ISSUE-0083(v0.10.120 HTTP 列空) ISSUE-0085(v0.10.122 tooltip 撒谎) 2 次
ISSUE-0081 + 0083 + 0085 是同一连环 3 次连续 ⚠️

工程实践

grep 路径列表(按场景)

修复主题 必 grep
字段写入 / 状态变化 grep -rn "<field>: <value>" src/
UI 列文案 grep -rn "<field>" src/sections/ + grep -rn "renderCell" 同文件
layout overflow grep -rn "overflow:" src/sections/
阈值调整 找上次调阈值的 ISSUE 看场景
状态机降级 grep -rn "setState\|outcome ===" src/utils/<state-machine>

Commit message 模板(修 bug 时)

v0.X.Y 修 ISSUE-NNNN — <简短描述>

## 触发场景列举(修前 impact-trace)
1. <场景 A>:xxx 路径
2. <场景 B>:yyy 路径

## 修复对每个场景的处理
- 场景 A: <怎么处理>
- 场景 B: <怎么处理>

## 副作用矩阵 (如改 layout/阈值)
| 配置 | 场景 1 | 场景 2 |
|---|---|---|
| ... | ... | ... |

## 沉淀
- ISSUE-NNNN 文件
- 教训: <下次怎么不犯>

自动化(待做)

可加的 pre-commit / commit-msg 检查: - 连环检测:同一文件最近 3 次 commit 都含 "fix" / "regression" → warn - scrape_status 写入审计:改 page-log/HTTP 列时 grep scrape_status: 2 必跑 - 待 v0.10.123+ 加 scan 脚本


教训

  • fix 不是修了就完 — 上线后场景暴露才能算修完
  • 「假定单一原因」是 fix-regression 第一根因 — 95% 的连环 fix-regression 都源自此
  • 同文件连续 commit 是黄信号 — 第 3 次该 review 而非继续打补丁
  • 诚实文案 > 简洁文案 — 兜底分支"可能是 / 或"比"日志被淘汰"安全 10×
  • 本项目过去 v0.10.117-122 6 次发版有 3 次是 fix-regression — 不能再来