2451 字
约 8 分钟
12
S27-A27 · 否证:为什么"我验证过"不等于"它对"

一份工具实测报告写着:"连续跑 50 次全部成功,结论:稳定可用,建议推广。" 上线两周后出了两次 P0。复盘时把 50 次实验的参数翻出来看:同一个环境、同一批数据、同一个时间段、同一种并发度——这 50 次只证明了一件事:在这种条件下它能工作。

这不是"测得不够多",而是测的方向反了。找支持证据几乎总能找到:只要你愿意调参数,总有一组能让它通过。真正有信息量的是找它什么时候不成立——因为一次失败会告诉你原因,而一次成功只告诉你"这条路通"。


1. 两种逻辑:确认与否证

思路 做法 能得到什么 风险
确认(verification) 构造能通过的场景,跑多次 信心(可能虚高) 只覆盖"顺利路径"
否证(falsification) 构造它该失败的场景,看是否失败 边界与原因 需要知道"哪里该失败"

第二行的"信心"最容易骗人:50 次成功带来的信心与"这 50 次是否覆盖了真实形状"完全无关。而第三行要求你先有一个预期——"如果它在这里不失败,说明我理解错了"。

// 同一份实测,两种问法,结论完全不同
确认式: "它能处理 10 万条吗?"  → 跑 10 万条,成功 → 报告"能"
否证式: "它在多少条时会开始丢?" → 逐步加压到失败 → 报告"上限在 38 万条左右"
// 前者给出一个 yes/no,后者给出一个边界值与失败方式

2. 一次失败的信息量远大于一次成功

观察 信息量 为什么
成功 低 只说明"这条路径可行"
失败 高 携带原因、阈值、失败方式
时快时慢 高 暴露方差与外部依赖
报错但功能正常 高 暴露隐藏状态
// 同一份"50 次全绿"的两种读法
读法一(确认): 50/50 成功 → 结论"稳定"
读法二(否证): 50 次没有一次触发失败条件 → 说明这 50 次太像了
// 真问题:为什么 50 次这么像?因为参数、数据、并发度一次都没变过

第四行在实际排查里最有用:一个"能跑但每次都打一堆 warning"的工具,往往在某个边界上已经在退化,只是还没越线。


3. 三种主动否证的动作

动作 具体做法 能暴露什么
推到边界 参数取最大值/最小值/0/负数/超长 未处理的极端输入
找已知反例 搜 issue、看 changelog 的 fixed bug 同类问题的复发面
看退出者 谁用过后来不用了,问一句为什么 长期成本(最难自查的部分)

第三行是成本最低、最容易被跳过的一步。退出者不会主动说话,所以这一步需要你去问——而他们往往只需一句"当时哪里卡住了"就能给出你半个月的结论。"为什么不用了"往往比"为什么用"更有信息量——因为退出者已经付过学习成本,且没有立场偏差。

// 找反例的两个固定动作(十分钟能做完)
$ gh issue list -R owner/tool --state closed --search "crash OR hang OR memory" | head -20
$ gh issue list -R owner/tool --state open --label bug | wc -l      // 未修的 bug 数
// 判据:未修 bug 里有你正打算依赖的能力 → 这个能力现在不能依赖

4. 结论必须带条件清单

"稳定可用"不是一个结论,是一个在特定条件下成立的判断。条件不写下来,它就无法被正确使用。

条件维度 必须写清 漏写的后果
环境 OS、版本、容器/裸机、核数 "我这儿能跑"
数据 规模、形状、编码、异常比例 上线遇到脏数据就崩
并发 线程/进程数、连接数 压测通过、高峰雪崩
时段 是否与批处理/备份重叠 夜里的抖动没测到
版本 工具与依赖的精确版本 三个月后结果对不上

第三行是最常见的漏项:单线程跑 50 次全成功,与"200 并发下稳定"是两个完全不同的结论。而报告里只写"跑了 50 次",读者会默认它是后者。

// 一个可用的结论模板(三段,缺一段就不能算结论)
【结论】在 <环境> + <数据规模/形状> + <并发度> + <版本> 下,X 可以/不可以 <能力>。
【边界】已知失效条件:<A>、<B>;未验证:<C>。
【否证尝试】我试过让它失败的方式:<1>、<2>,其中 <1> 确实失败,原因是 <…>。
// 第三段是"我努力找过反例"的凭证,也是别人判断可信度的依据

5. "没发现问题"不是证据

这是全篇最需要强调的一句:没有观察到问题,与"没有问题",中间隔着一个"覆盖"。

说法 实际含义
"跑了 50 次没出错" 这 50 次的输入空间里没出错
"用了半年没事" 这半年的负载形状里没事
"团队都在用" 团队的用法范围内没事
"网上没人报这个 bug" 报的人可能没定位到
// "没发现问题"的三层覆盖,缺一层就等于没测
1 输入覆盖:规模、形状、异常值比例
2 环境覆盖:OS、版本、资源上限、并发度
3 时间覆盖:是否跨过批处理、流量高峰、版本升级
// 三层的共同点:它们描述的都是"你还没跑过的那些情况"

第二行尤其危险,因为时间会带来虚假的安全感:"用了半年"往往意味着"这半年没遇到触发条件",而不是"它不会被触发"。


6. 排错:五种"验证过了却翻车"

现象 八成原因 处理方向
压测通过、高峰雪崩 并发度不在条件清单里 按真实并发重测
测试环境正常、生产异常 数据形状/规模差异 用生产样本子集
单机能跑、分布式出错 时序/共享状态 加分布式场景
三个月后结论失效 版本已变 结论带版本号 + 复审期
没人能复现你的结果 环境未记录 出复现包

第五行对应 S27 里另一篇的主题(复现包),但这里要强调的是:"别人复现不出来"本身就是一次否证——它说明你记录的条件不足以描述你的实验。


7. 边界:什么情况不必追求否证

场景 为什么不必 说明
决策可逆且代价小 试错比验证便宜 直接试
边界已被上游保证 无需自己再证 依赖 SLA
只用于一次性任务 无长期成本 跑通即可
时间窗极短 验证成本 > 风险 记录风险后上
// 成本 × 可逆性:只有右上角真的需要否证
可逆 + 便宜    → 直接试,试错更快
可逆 + 昂贵    → 小规模验证即可
不可逆 + 便宜  → 记下风险就上
不可逆 + 昂贵  → 必须否证,且要写明边界与未验证项

判断口径一句话:只有当"错了的代价不可逆或很贵"时,才值得做否证——否则试错更快。而一旦决定要做,就必须做到"能说清它在什么条件下不成立",否则那不叫验证,叫体验。


8. 自测

  1. "确认"与"否证"两种思路分别能得到什么?为什么前者的信心容易虚高?
  2. 为什么一次失败的信息量大于一次成功?"报错但功能正常"暴露了什么?
  3. 三种主动否证的动作是什么?为什么"看退出者"信息量最大?
  4. 结论模板的三段分别解决什么问题?哪一段是"可信度凭证"?
  5. "用了半年没事"为什么是虚假的安全感?
  6. 什么情况下不必追求否证?判据是什么?

9. 一句话总结

"验证过"只说明"在那些条件下它能工作",除非你主动去找它不成立的地方。否证的三个动作——推到边界、找已知反例、问退出者为什么走——各自暴露不同层面的问题;而结论必须带条件清单(环境/数据/并发/版本),否则不可迁移也不可复审。判据:"没发现问题"与"没问题"之间隔着覆盖;只有错了代价不可逆时,才值得花这个成本。

S27-A27 · 否证:为什么"我验证过"不等于"它对"
https://www.clxhxhhr.top/posts/6741/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。