【开发第一性原理】01 · 越诡异的 Bug,越可能是低级错误

《开发第一性原理》系列第 1/12 篇 · 上一篇:无 · 下一篇:02 · 复杂问题,要先学会”切”
系列说明:这个系列写我在开发里反复验证过的基本规律。不追新概念,只讲那些早晚会再次应验的东西。
TL;DR:Bug 越诡异,病根越可能是低级错误。这篇不讲新技巧,只讲一条排查顺序,外加两份清单——排障先查什么,编码少埋什么雷。
一、先讲一件事
有一次,我的一份自开发报表出了问题:金额和业务部门手工核对的结果对不上。差异毫无规律,有时大、有时小,有时干脆是对的。
我的第一反应,是把”高级”的可能性挨个怀疑了一遍:
- 标准表里是不是有脏数据?
- 是不是有并发操作,两个人同时改了单据?
- 是不是货币汇率换算出了问题?
- 甚至一度怀疑是 SAP 标准逻辑本身有 Bug。
顺着这些方向查了整整一天,一无所获。最后靠单步调试脱困:逐行盯着变量看,发现问题出在一个累加金额的语句上——符号写反了。改完,问题消失,前后不到一分钟。
查了一整天,改了不到一分钟。这种体验,你应该不陌生。后来我刻意回顾过几次类似的”悬案”,规律相当稳定:现象越不可思议,病根越可能出在最基础的地方。
二、为什么诡异的 Bug,原因反而简单
先看 Bug 这边。并发冲突、内存溢出、系统缺陷这类复杂问题,往往带着明显的症状——dump、超时、日志报错。它们很吵,反而活不长。低级错误正好相反:符号写反、工作区忘了 CLEAR、SY-SUBRC 没判断……程序不报错也不中断,只是安静地给出错误的结果。安静的错误,才有机会活成悬案。
再看我们自己。正因为现象”诡异”,潜意识会认定原因也一定”高级”,于是先查并发、查锁、查系统,最后才不甘心地回头检查自己写过的最蠢的那一行。而那一行,恰恰因为看起来”显然没错”,调试时会被直接跳过。
两头一凑,陷阱就成了:低级错误制造安静的异常,安静的异常又把我们引向复杂的原因。
所以排查顺序应该倒过来——按错误的常见程度排,而不是按现象的诡异程度排。
三、这个道理,医生早就总结过
医学诊断里有一条经典原则:听到蹄声,先想马,别想斑马。症状再离奇,医生也会先排查常见病,而不是一上来就怀疑罕见病——症状的离奇,不代表病因的罕见。
排查 Bug 也是一样:能解释现象的假设有好几个时,先验证最简单的那个——奥卡姆剃刀最实用的场景。复杂现象,绝大多数时候只是一个平凡原因的简单叠加。
四、两份清单
光有道理没用,我把它拆成了两份清单。
排障时先查什么(按顺序):
- 自己刚改的代码——改动即嫌疑
- 基础层面——数据、配置、符号、类型、前导零、边界值
- 环境差异——测试机和生产机的差异、传输是否到位、版本是否一致
- 最后才轮到——平台缺陷、标准程序 Bug
顺便给自己立条规矩:排障超过两小时毫无头绪,就回头做一次最基础的验证——单步调试,逐行看变量的实际值,不许跳过任何一行”看起来肯定没问题”的代码。那一行往往就是答案。
编码时少埋什么雷:
- 声明即初始化。变量和内表声明时给初始值,不依赖隐式行为。经典坑:循环里 APPEND 前忘了 CLEAR,上一次的数据残留在工作区里,现象极其诡异。
- 关键判断显式化。每个 SELECT、每次调用之后,明确检查 SY-SUBRC,不默认成功。
- Code Review 请同事专盯”低级错误”。评审者和你一样,也会下意识跳过那些”太简单”的地方——专门找,会发现比预想的多。
五、写在最后
下次再被诡异 Bug 卡住,先别急着怀疑并发和系统,回头看看自己写过的那几行——会 dump、会超时的 Bug 反而好查,安静的错误才有机会活成悬案。
下一篇聊”复杂问题,要先学会’切’“:遇到一团毛线,怎么切才不伤手。
(完)
系列导览
《开发第一性原理》 共 12+ 篇,按 4 大主题展开:
- 排障方法论(01-04):先查什么 + 在哪查 + 怎么用工具
- 失败处理(05-06):事后保留 + 事前防御
- 开发者测试(07):测到边界
- 信息可被找到(08-12):字面量 / 配置表 / 代码 / 文档 / 包关系