在《Learning From Your Bugs》一文中,我写了关于我是如何追踪我所遇到的一些最有趣的bug。最近,我回顾了我全部的194个条目(从13岁开始),看看有什么经验教训是我能够学习的。下面是我总结的最重要的经验教训,包括编码,测试和调试三个方面。html
下面这些都是我经历过的会致使难点bug的问题:web
1.事件顺序。在处理事件时,提出下列问题会颇有成效:事件能够以不一样的顺序到达吗?若是咱们没有接收到此事件会怎么样?若是此事件接连发生两次会怎么样?哪怕一般不会发生,但系统(或交互系统)其余部分的bug可能会致使事件发生呢。数据库
2.过早。这是第一点“事件顺序”的一个特例,但它确实会引发一些棘手的bug,所以我把它单独拎出来讲明。例如,若是信令消息在配置和启动程序完成以前就被过早接收,那么可能就会有不少奇怪的行为发生。另外一个例子:链接在被放进空闲列表以前就被标记为down。在调试这类问题时,咱们老是假定在空闲列表中的时候链接被设置为down(但当时为何不把它放到列表外面呢?)。这是咱们思考的不足,没有考虑到有时候事情会过早发生。less
3.悄无声息的故障。一些最难跟踪的bug有部分是由那些静静失败并扩展而不是抛出错误的代码所致使的。例如,没有检查代码却返回错误的系统调用(如bind)。又如:解析代码在它遇到错误元素的时候只是返回而非抛出错误。在错误状态中持续了一段时间的调用,会使调试变得更难。最好一旦检测到故障就返回错误。工具
4.If。有若干条件的if语句,if (a 或 b) ,特别是当有连接的时候, if (x) else if (y),都给我引起了不少bug。即便if语句在概念上很简单,但当有多个条件要跟踪的时候依然很容易出错。这些天,我尝试重写代码使之更简单,以免处理复杂的if语句。单元测试
5.Else。有一些bug是由于没有正确考虑到若是条件为false时会发生什么而引发的。几乎在全部的状况下,都应该有一个else部分来应对每一条if语句。此外,若是你在if语句的分支中设置变量,那么或许你在另外一个分支中也要设置。与此种状况相关的是标记被设置的状况。只添加用于设置的标记的条件不难,可是很容易忘了添加当标记应该再次重置时的条件。留下一个永远设置的标志可能会致使以后接连不断的bug。学习
6.改变假设。许多一开始最难预防的bug是由于改变了假设所形成的。例如,在开始时,可能天天只有一个客户事件。因而不少代码是在这样的假设下写下的。可是后来,设计改变了,容许天天有多个客户事件了。发生这种状况时,很难改变新设计影响到的全部状况。找到关于改变的全部显式依赖关系不难,难的是要找到全部隐性依赖于旧的设计的状况。例如,可能会有获取给定某一天全部客户事件的代码。其中的隐含假设是结果集永远不会超过客户的数量。关于这方面的问题我也没有很好的策略方法,若是各位有的话,还请不吝赐教。测试
7.日志记录。可视化程序作什么相当重要,特别是当逻辑很复杂的时候。确保补充足够多的(但不要太多)日志记录,这样你就能够说明为何程序要这么作。若是一切正常,那也不要紧,但要是有问题发生,你会很庆幸本身添加了这些日志。google
做为一个开发人员,直到要测试了我才会去处理功能。至少,这意味着每一行新的或改变了的代码行至少已经被执行过一次。此外,单元测试和功能测试都很不错,但还不够。新的功能也必须进行测试,并在相似于产品的环境中探索。只有这样,我才能说我完成了一个功能。下面是我经历过的bug所教会个人关于测试的一些重要的经验教训:编码
8.零和null。若是可行的话,确保老是用零和null来测试。对于字符串,这意味着要测试长度为零的字符串以及字符串为null两种状况。又如:测试TCP链接的断开,要在发送数据给它发送以前。不使用这些组合方法测试是致使bug出现的首位缘由。
9.添加和删除。一般,新的功能包括可以添加新的配置到系统中——例如,一个用于手机号码转换的新的配置文件。测试它可否添加新的配置文件是很天然的。可是,我发现咱们很容易忘记去测试删除配置文件是否是一样ok。
10.错误处理。处理错误的代码每每是难以测试的。最好有能检查错误处理代码的自动测试,但有时这是不可能的。我有时会使用的一招是临时修改代码,使得错误处理代码运行起来。要作到这一点最简单的方法是反转if语句——例如,从if error_count > 0改为error_count == 0。另外一个例子是拼错数据库列名,从而致使指望的错误处理代码运行。
11.随机输入。一般,揭露bug测试的一种测试方法是使用随机输入。例如,H.323协议的ASN.1解码使用二进制数据操做。经过发送随机字节去解码,咱们发现了解码器中的几个bug。另外一个例子是用测试呼叫来生成脚本,此时呼叫持续时间,接听延迟,第一方挂断等等都是随机生成的。这些测试脚本会暴露许多bug,特别是一块儿发生的事件会产生并拢干扰。
12.检查不该该发生的动做。一般测试包括检查指望动做是否是发生了。但咱们很容易忽视相反的状况——忘记检查不该该发生的动做是否是的确没有发生。
13.拥有工具。我建立了本身的小工具,以使得测试更加简单。例如,当我用VoIP SIP协议工做时,我写了一个可以用正是我想要的标题和值回复的小脚本。这个工具使得测试不少边界状况变得容易起来。另外一个例子是能够进行API调用的一个命令行工具。经过启动逐渐添加所需小功能,我获得了一些很是有用的工具。本身写工具的好处是,我获得的正是我想要的。
在测试中发现全部的bug,那绝对是不可能的。有一个案例中,我更改了数字相关性的处理,数字由两个部分组成:路由地址前缀(一般是不变的),以及从000到999动态分配的数字。问题在于当找到相关性时,动态分配的数字的第一个数字会在呈如今表格中以前遭到误删。也就是说637变成了37。这意味着,到100以前它都是能够工做的,所以,前面100个电话是正常的,可是接下来的900个都是失败。因此,除非我在从新启动以前可以测试超过100次(事实是我没有),不然我在测试时就不会发现这个问题。
14.讨论。帮助我最多的调试技术是与同事讨论问题。一般状况下,只是和同事说明问题,就会让我意识到问题的症结。此外,即便他们不是很熟悉有问题的代码,他们也每每能提出一些好点子。与同事讨论在处理最难的bug时特别有效。
15.密切关注。一般,若是调试问题花了很长时间,每每是由于我作了错误的假设。例如,我认为问题发生在某一方法中,但事实倒是它甚至历来没有到达那个方法。或者,被抛出的异常不是我觉得的那个。或者,我认为软件的最新版本上正在运行,但实际上是一个旧版本。所以,必定要核实细节,而不是假设。人们更容易看到本身但愿看到的东西,而不是事实。
16.最近的变化。当曾经能够正常工做的东西中止工做,那么这一般是由于最近改变的东西所致使的。在一个案例中,最近的改变只是日志记录,可是日志中的错误却致使了一个更大的问题。为了更容易找到这种回归,认可不一样的提交会致使不一样的变化,以及清楚说明这些更改会有所裨益。
17.相信用户。有时,当用户报告问题的时候,个人本能反应是,“这是不可能的。必定是他们作错了什么事”。但我学会了再也不用这种方式去回应。更多的时间,事实每每证实,他们所报告的的确是实际发生的状况。所以,这些天,我开始接受他们所报告的内容的代表价值。固然,我依然会仔细检查一切是否被正确地设置等等。我见过不少这样的状况,让我明白,由于不寻常的配置或意料以外的用法而致使难以想象的事情的发生,而我默认的假设是,他们是正确的,程序是错误的。
18.测试修复。若是bug修复已准备就绪,那就必须进行测试。首先在修复前运行代码,并观察该bug。而后应用修复并重复测试案例。到此为止错误行为应消失。遵循这些步骤能够确保它确实是一个bug,而且这次修复的确能够解决这个问题。简单而有必要。
在这13年来我一直在跟踪我所遇到的最棘手的bug,不少事情由此而改变。我工做太小的嵌入式系统,大的电信系统以及基于web的系统。我使用过C ++,Ruby,Java和Python。在工做于C++时所遇到的几类bug已经彻底消失,像堆栈溢出,内存损坏,字符串问题和某种形式的内存泄漏。
其余问题,如循环错误和边界状况,我看到的要少得多。可是,这并不意味着那里没有bug。这篇文章中的经验教训旨在帮助减小编码,测试和调试三个阶段的bug。若是你们有什么有用的预防和发现bug的技术方法,欢迎不
译文连接:http://www.codeceo.com/article/13-years-bug.html
英文原文:Lessons From 13 Years of Bugs
翻译做者:码农网 – 小峰