Teaser Image

mindwind

十日画一水,五日画一石




「bug正如程序里的寄生虫,它们一同生,一同死。」

bug 分类

bug 最早真的是一只虫子,后来才被用来比喻程序中的缺陷。 bug 的从分类上来说太广泛,但从解决的难度层次上来分,大概有以下几种:

固定条件下,不符合预期的程序行为

这里的固定条件很容易模拟并被重放,一般的黑盒测试就能发现这类bug。

外部环境的变化,导致不符合预期的程序行为

这类 bug 相对比较隐晦,有时会带给你 surprise,这是一种程序的过敏反应。 有经验的程序员和测试会对此有所预期并小心检查,避免此类 bug 也不太困难。

周期性的程序错误

与时间有关的 bug 相对比较困难。 但如果程序错误爆发的时间具有周期规律性,那么重现与解决还是相对容易。 这类 bug 的典型案例是内存溢出,一般功能性测试很难发现。 通常需要长时间压力条件下的稳定性测试来发现。

无规律性的程序错误,但重现条件相对简单

这类 bug 很多都是与时序相关的,例如线程的调度时序等。 错误现象出现的比较随机,但重现的条件相对还是容易,通常需要在压力测试条件下重现。 解决的难度则依赖具体的程序,无法一概而论。 我曾经写过一个网络服务端程序就发生过此类现象,最终定位的bug与网络协议收包的线程执行序列有关。

神出鬼没的 heisenbug

这个 bug 的由来很有趣,量子力学里有个海森堡不确定性原理,认为观测者观测粒子的行为会最终影响观测结果。 这类 bug 我也曾遭遇过,当我增加程序 debug 日志输出时,bug 就会消失无踪。 heisenbug 是一个双关语,指生产环境下不经意出现,费尽九牛二虎之力却无法重现的 bug。 heisenbug 的出现场景通常都是和分布式的并发编程有关。

bug 解决

这些年下来,解决 bug 的手段依然很有限

预防

预防之道最实用的方法有八个字 “保持简单,小心编码”。 代码越少自然 bug 越少,从个人的一些经验和一些开源项目的统计来看,代码行数和 bug 数的比例接近100:1。 也就是说每 100 行代码里可能就隐藏着一个 bug。

测试

测试依然是目前解决 bug 的最有效手段,对于上面提到的前4种 bug 都有对应的测试手段去发现。 只有 heisenbug,目前没有太好的测试手段能够去预防和重现。 还有些研发/测试经理比较喜欢提测试覆盖率这种指标,看着 100% 的测试覆盖率报告能让人心理产生一种虚幻的质量安慰。 我认为测试覆盖率要因项目而异,对于现在大量的 CRUD 类管理系统项目,最有效益的单元测试覆盖率是0%。 对这类项目追求覆盖率除了产生昂贵的成本,并不会带来更多的额外好处。 而对于类似协议栈、基础算法库类的库程序,单元测试覆盖率 100% 也还不够,代码路径的覆盖率和 case 覆盖率本质是不同。 有些程序bug很难被测试发现的原因是,编写测试程序的难度甚至超过了开发原始程序本身,这在很多分布式并发程序种尤为常见。

评审

现在很多软件开发流程中都增加了评审环节,很多时候是让一些资深程序员或架构师来对新手程序员的代码质量进行把关。 我最早知道的评审是来自CMM(软件成熟度模型)中的 Peer Review(同行评审)。 Peer 这个词体现了一种对等关系,而让老手来 review 新手其实已经是不对等了,这也是现在很多 review 越发形式化的原因之一。 Peer Review 本应成为软件质量保证的重要手段,在现下却因为种种原因(工期、绩效、非对等、KPI)执行的不太好,特别是在公司内部的私有项目中。 目前,我倒是觉得最有效的 Peer Review 方式是开源。

程序员不断的和 bug 对抗正如医生不断和疾病对抗,人总是会生病正如程序必然伴生 bug。 真正的程序员要正视 bug,bug means lived code, dead code has no bug. 最后从埋葬着无数代码和 bug 的坟场中走出来的都是真正优秀的程序员。