调试九法

56 阅读9分钟

最近读到一本旧书,书旧但思想不旧,学习并应用这些思想绝对能让你的调试(找bug)能力大幅提升!

image.png

下面内容为概要笔记

如果查找一个bug花费了大量时间,那么原因可能是忽略了某个最基本的、最重要的规则,一旦应用了那条规则,很快就会找到问题。 擅于快速调试的人已经深刻理解并应用了这些规则,而那些很难理解或使用这些规则的人则很难找到bug。

1. 理解系统

"人们要想掌握本书中所有有用知识也并非完全不可能,事实上我就是尽全力这样做的。" 一一福尔摩斯,《杀人的五个楠核》

知道什么是正常的可以帮助你注意到什么是不正常的。

  • 如果你不知道缓存是干什么的,就会非常奇怪有些数据为什么没有马上写入内存。
  • 如果你不了解三态(tri-state)数据总线的工作原理,你将会认为它们可能是主板上的故障信号。
  • 如果你从未听说过电锯,你可能会认为那个发出讨厌的嗡嗡声的东西一定是出了什么毛病。

总的来说,理解系统就是:你必须掌握系统的工作原理以及它是如何设计的。 在某些情况下,还要知道为什么这样设计。如果你没有理解系统中的某个部分,那么这通常就是出问题的地方。 理解了你自己的系统后,还会获得一个额外的好处。当你找到bug时,必须在不破坏其他地方的前提下修复它们。理解系统行为是不破坏系统的第一步。

2. 制造失败

"什么也比不上直接取得的证据来得重要。" 一一福尔摩斯,《血字的研究》

很多时候,开发人员在修复 bug 时会修改软件,然后在一个与当初发现 bug 的不同条件下测试新软件。软件当然能运行,即使他在代码中输入一行打油诗,而他也高兴地回家了。然而,几星期后,在测试过程中,或者更糟,在客户现场,软件再次失败。

我们之所以制造失败,是出于3个目的:

  • 一是观察错误。要观察错误,就必须使它发生。我们必须尽可能有规律地制造失败。
  • 二是查找线索。准确地知道问题在什么条件下会发生,有助于集中精力查找原因。
  • 三是确认是否已修复。当问题没有修复时,如果你执行X操作,失败率为100%; 在修复问题后,再执行X操作,如果失败率为0,那么你知道bug确实已被修复。

引发失败,不要模拟失败。 引发失败可以采用“自动化”或者“放大”措施,如果车胎漏气,我们可以把车胎放在肥皂水里,寻找气泡。

3. 不要想,而要看

"在没有事实作为参考以前妄下结论是个很大的错误。主观臆断的人总是为了套用理论而扭曲事实,而不是用理论来解释事实。" 一一福尔摩斯,《波希米亚丑闻》

亲眼看到底层的失败是非常重要的。如果你猜测失败是如何发生的,那常常会修复一些根本不是bug的问题。这样的修复不仅不会解决问题,而且还会浪费时间和金钱,甚至会破坏其他地方。

  • 不要害怕深入研究
  • 插装:天然气中加入了臭鸡蛋气味,目的就是当泄漏时能够被发现。
  • 注意海森堡测不准原理:海森堡是量子物理学的开拓者之一。他致力于研究质量和体积极小的原子内的粒子,他发现你要么测量一个粒子的位置,要么测量它向哪个位置运动,但这二者当中有一个测量得越精确,另一个就越测不准。无法得到准确测量的原因是探针成为了系统的一部分。换言之,测试工具影响了被测系统。

一定要亲眼看到实际错误是如何发生的。观察往往比猜测能够更快地找到问题。因为猜测虽然看起来是捷径,但这条捷径并不会带你找到问题的根源。

4. 分而治之

"当你排除了所有的不可能,不管留下了什么,也不管看起来多么不可思议,那必定都是事实"。 一一福尔摩斯,《四签名》

  • 确定范围,从有问题的支路开始查找问题。
  • 通过反复地把问题分成好的一半和坏的一半,来缩小搜索范围,然后进一步研究有问题的那一半。 如果同时出现了多个问题,当你确实查明了其中的一个问题时,应该立即修复它,然后再查找其他问题。有时修复了一个问题,另一个问题也解决了,两个问题实际上是同一个bug

5. 一次只改一个地方

"有人说天才就是无止境地吃苦耐劳的本领。这个定义下得很不恰当,但是在侦探工作上倒还适用。" 一一福尔摩斯,《血字的研究》

核心:控制变量法,与正常系统进行比较。

有时,改变测试序列或一些操作参数可以使问题更加有规律地出现,这有助于观察错误,而且可能会帮助我们找到问题的线索。但我们仍然应该一次只改变一个地方,以便判断哪个参数有影响。如果做了一个改变后看上去没有什么效果,应立即把它改回来。它们可能会产生无法预料的影响

找到自从上一次能够正常工作以来你更改了什么:有时,正常的系统和错误的系统之间的区别是由于一项更改造成的。但还有一种情况,一段新的代码或新的硬件修订设置了新的条件,结果使得原来一直很可靠的子系统出了问题。子系统有一个漏洞,只是你以前从未遇到它。你可能试图追踪由那个漏洞引起的bug,而有时这样只能暂时修复问题,而你实际需要做的是解决那个漏洞。

6. 保持审计跟踪

"在侦探学的所有分支中,没有比足迹学这门艺术史重要而又最易被人忽视的了。" ——福尔摩斯,《血字的研究》

有时看起来最不起眼的事情实际上却是导致发生bug的关键。在测试人员看来不重要的细节(格子衬衫)可能对于bug修复人员很重要。而在测试人员看来很明显的事情(芯片需要重启)可能在修复者看来无关紧要。因此,你必须记录下每一件事情,不起眼的事情可能会很重要。

在细节方面,永远都不要相信你的记忆,而要把它写下来。如果你相信你的记忆,将会制造很多麻烦。你会忘掉一些你认为不重要的细节,当然,这些细节将会被证明是非常重要的。你会忘掉一些在你看来不重要的细节,而这些细节对于后来解决另一个不同问题的人可能很重要。

不要只是在心里记住"保持审计跟踪"这条规则,而要把它写下来。

7. 检查插头

"没有什么比一个显而易见的事实更能迷惑人了。" 一一福尔摩斯,《博斯科姆比溪谷秘案》

永远不要相信自己的假设,特别是当这些假设在一些无法解释的问题中是核心因素的时候。应该问自己一个古老的、看似愚蠢的问题 "插头插上了吗? " 虽然这个问题看上去很愚蠢,但它经常发生。你可能费尽周折检查调制解调器软件为什么不工作了,事实证明你只是把电话线踢掉了。

当我们看到一个问题时,通常在某个特定位置看到了问题,但导致这个问题的原因却在上游或者是一个基础性的问题。系统不具备正确操作的条件,于是出现了非常奇怪的行为。当你看到完全来自另一个世界的问题时,应该停下来,看看你是不是还在地球上。

8. 获得全新观点

"要想重新理清一个案子的头绪,最好的方法就是把它讲给别人听。" ——福尔摩斯,《银色马》

我们按照自己老一套的思路是很难看清全局的。我们都是普通人,对任何事情都有偏见,包括对bug隐藏在哪里的看法。这些偏见可能导致我们无法看清实际情况。而其他人则会从一个无偏见的角度来看问题(实际上他们只是有另一种不同的偏见),这可能会给我们很大的启发,帮助找到新的方法。

无论你想要获得什么样的帮助,在向别人描述问题的时候,一定要记住一件事:报告症状,而不要讲你的理论。之所以要从别人那里获得全新的观点,就是因为你的理论起不到任何作用。如果你找了一个人,把你的理论告诉他,那么也会把他拉到你原来的思维定式中。

9. 如果你不修复bug,它将依然存在

"当危险已经离你很近时,拒绝承认它并不是勇敢的表现,而是愚蠢。" 一一福尔摩斯,《最后一案》

当你认为你已经修复了一个设计问题时,取消这个修复,确定系统再次失败。然后再应用这个修复,再次验证问题已修复。直到你经过从修复到失败,再从失败到修复这个过程之后(只应用和取消修复,而不改变其他地方) ,才能够证明你确实已经修复了问题。

对过程进行修复:在一家工厂里,油漏到了地上。你的解决办法可以是把油擦干净,但这并没有解决问题的根源。如果有一个设备漏油,它仍会继续泄漏。因此,你可以把它固定得更紧一些。但问题修复了吗?没有,它会再次变松,因为机器振动得很厉害。这是由于它只是用两个螺栓固定的,而没有用4个。看,这才是真正的漏油bug。

现在你已经掌握了所有的技术,没有理由再让bug存在了。