欢迎收藏。当你遇到非常难缠的
Bug时,不妨回来这里看看...
编程本来是一项非常优雅的工作,而程序员,也理应是那种如艺术家一般,不断写出极具观赏性和创造力的作品的职业。 然而自从工作之后,却发现
身边的程序员大多深陷赶迭代出口和修复紧急Bug的泥潭。经常可以看到下面这些场景:
· 修复了一个Bug,却引入了更加致命的Bug
· 不停的修改代码,重启服务器,Bug依然没有修复
· 补丁打上去后,Bug依旧存在,定位了很久,最后发现是补丁打错地方了
虽然我们应该尽可能保证提交上去的代码里Bug越少越好,但是当Bug真的出现时,我们往往十分慌乱,像一只无头苍蝇一样,调试的过程
毫无章法。
直到最近偶然看到了这本书——《调试九法》,这是我看到的第一本讲
调试方法论的书,回想自己之前在解决Bug时的经常遇到的手足无措,像无头苍蝇一般四处乱撞,最后瞎猫碰上死耗子般地解决了Bug,我毫不犹豫地买下这本书。
这是一套系统的调试理论。
希望本文介绍的这套调试学的知识,能够帮助你在今后遇到Bug时,思路更加清晰,心情更加淡定,从容不迫地去解决问题。
调试法则总览
这套方法论可以总结成一句话:
调试是一门科学,任何不懂原理就进行的操作都是耍流氓。
这句话可以分为两个步骤进行实践:
准备工作:
· 如果你要调试这个系统,首先你必须先理解它。
· 在开始调试之前,检查一下“插头”,别因为一些简单的问题而瞎忙活半天。
· 确定“插头”没有问题之后,你需要重现Bug。
寻找原因并解决Bug
· 不要认为你猜测的原因是对的,要去观察,验证你的猜测。
· 采用分而治之的方法,定位多模块的系统问题和修复由多个子问题引发的问题。
· 一次只修改一个地方
· 做好修改
记录
· 如果正向调试走不通,不妨试一下反向调试法
· 最后一招:求助他人
下面对这些法则逐一进行详细地介绍。
理解系统
理解系统是定位Bug的前提条件。
我之前遇到程序抛出异常时,经常就把异常信息贴到网上搜,然后把网上的解决方案执行一遍,有时work了,就大吉大利,可大多数时候,是不work的,原因很简单,也许对方的JDK版本跟你的不一样,也许你们俩只是报错信息相同,但是抛异常的原因不同,更大的可能,对方的解决方案本来就行不通。
这就能理解为什么理解系统是定位Bug的前提了,如果你在定位一个JDK异常,那么至少你要掌握Java SE吧,如果你能掌握JVM的垃圾回收原理、类加载机制,自然更好;而如果你在定位一个支付系统为什么没有把账打到客户的账户,那么你得了解支付的流程吧。
总之,在开始调试之前,我们得弄清楚,
调试是一门科学,而不是一门概率学,你需要理解整个系统,才能够进行调试。
有以下方法可以帮助你理解这个系统:
· 阅读手册:阅读需求设计文档、产品文档、使用手册等
· 仔细阅读手册的每一个细节:说不定解决Bug的方案就在某个段落里
· 掌握基础知识:最后你总是要看源码的,至少你要掌握相应的编程语言把
· 了解工作流程:从整体的角度来观察,而不是做井底之蛙
可以说,理解系统的最终目的就是为了了解工作流程,
了解了工作流程,你才能够从整体的角度来观察,不然就像井底之蛙,以为问题一定出在自己这个模块,而其实问题是在上游的某个模块里。这一点,和《程序员的思维修炼》中提到的,“专家从整体进行思考”的观点不谋而合。
检查插头
这里当然不是让你去问人家插头插了没,插头在这里是泛指一切让产品正常运行的基本要求。这些基本要求通常我们都认为理所当然是正常的,可事实有时并非如此。
比如你的一个系统,需要在配置界面配置白名单,不然上游的请求就会被拒绝,那么当出现问题时,你应该首先去检查一下这个白名单配置了没,因为对方有可能是个新手。
甚至当出现一些不可理喻的错误时,你要去软件的运行目录下,比如Tomcat的webapps目录,看看软件包是不是完整。
当你替换新代码上去后,发现Bug依然存在时,不妨上去看看正在运行着的,是不是还是旧代码。
书中把这条规则放到了倒数第三条,我这里把它放到第二条,原因很简单,通常我们在发现Bug或者别人跟自己说这里有Bug时,心里都会慌,都会紧张,所以不妨先检查一下插头,缓解一下自己紧张的心情,同时也强迫你从整体的视角进行观察,不会局限在一个小模块里。
重现失败
这几乎是一个下意识的动作,就算你之前没读过这本书,在遇到Bug时,你也会去尝试重现它,原因很简单:
· 重现失败让你可以观察失败发生时的上下文信息,进而找到失败的原因
· 重现失败让你可以判断是否已经修复了问题
有些问题很好重现,而有些呢,却是要在特定的输入的情况下才会出现的。
我们犯的绝大多数错误是在重现的方式上,作者对重现提出了两条原则:
· 模拟失败发生的条件,但是不要模拟失败的机理,因为你认为的导致失败的机理很可能是错误的。举个例子,你认为是高并发导致的bug,于是你模拟了高并发的环境,问题重现了,然后你就说是高并发导致的,其实呢,只不过是高并发提高了问题发生的几率。
· 只影响错误发生的频率,不影响错误发生的方式。其实高并发的环境可以用来提高错误发生的频率,只不过你要在问题重现时,要找到相应的日志信息,然后定位出问题发生的原因,而不是直接认为就是并发导致的。
不要猜,要观察
现在我们可以重现Bug了,直觉告诉我要在那个地方进行一个字符串编码的转换,且慢,在进行这个武断的尝试之前,先来看看《福尔摩斯》是怎么说的:
主观臆断的人,总是为了套用理论而扭曲事实,而不是用理论来解释事实。
猜测只是为了确定搜索的重点,但是在开始修复之前要观察确认你的猜测。所以在我们修改代码之前,还是看一下发生错误时的日志信息,还可以调试一下代码,在必要的时候打开源码深入研究一下,确定确实是字符串编码的问题,再去修改代码。
有人说,那我直接改代码,然后看结果不就知道是不是字符串编码的问题了。当然不是,要知道一个问题的产生可能是由多处地方的代码引起的,也许解决完这个字符串编码的问题,还需要解决另一个问题,才能把整个问题解决呢?
如果你在修改前就没进行观察,就会认为这次修改毫无意义,这样整个调试过程就会陷入死局。
记住,调试是一门科学,任何不懂原理就进行的操作都是耍流氓。
分而治之
系统通常都是由很多个模块组成的,这也就要求我们要检查很多个模块的日志才能够确定问题发生的原因。尤其是现在流行的微服务框架,一笔业务出现问题,你需要到很多个服务的机器上去找日志。
但是,如果你的业务执行是线性的,也就是说如果节点A执行失败,那么节点A之后的也都会执行失败,那么你就可以采用二分法的方式来定位了。要知道,在1到100里猜一个数字,最多也就需要7次。
采用二分法的方式,你将逐步缩小嫌疑的范围,最终找到问题的根因。
当然,如果问题是由多个子问题引起的,那么记住,找到一个,消灭一个,这就是所谓的
分而治之。
一次只修改一个地方
通过观察,你认为你的修改方法会起作用,但如果实际上你修改完代码之后,并没有起到任何作用,那么请你马上改回去,
以免这个修改引入了新的Bug。
做好修改记录
把你调试过程中的操作和结果按顺序全部记录下来,方便你在发现做了那么多处修改依然没有解决问题时,进行回溯,反思自己的操作有没有不对的地方。
反向调试法
上面的调试规则,都是从问题出发,去寻找犯错的代码。但有时候反过来也许会更好。
你可以找到最新一个可以正常运行的版本,然后对比现在这个版本和那个版本之间的差别,通过分析改动的代码,来分析是哪块代码导致的问题。
求助他人
有时候问题比较紧急,这时候不妨问一下专家,正如《程序员的思维修炼》中提到的,专家依靠直觉,他们往往会一针见血的给你指出问题的地方。
如果你对系统有一定理解的情况下,可以上软件供应商的官网、
谷歌、StackOverflow等网站寻找相关的资料。
在求助的过程中,你只需要描述问题的症状,如果对方没有要求,那么不要给他讲自己的理论,以免将对方带入自己的思维定式。
而在给他人描述问题的同时,你自己可能也会得到启发。
总结
以上就是我看完《调试九法》这本书之后总结的一套调试方法论,当然还是建议大家看一下原著,说不定会有新的收获。不过书中列举了大量的例子,多的让我感觉有些冗余,建议大家看的时候,先看每一章节的开头,和每章结尾的小总结,看完之后有不理解的,再去看每章中间的案例。
本书的例子虽然大多数都是关于工程
技术的,但是里面的一些想法还是可以借鉴到
生活中去。比如,夫妻吵架了,表面看上去是因为丈夫不愿意洗碗,但是如果你能从全局的角度去观察,你就知道,其实是因为丈夫情人节时没有给夫人买礼物。
回过头来看这些规则,其实我们在工作和生活中时不时都会用到,但是我们之前一直没有一个系统的理论体系,在掌握了书中介绍的调试规则之后,我们在今后定位错误根源时,会更加井井有条,从容不迫。
最后必须说一句,
专业人士应该在开发时就尽可能地保证软件的质量,而不是经常靠调试来弥补缺陷。完善而充分的
单元测试是保证代码质量的关键。当你发现Bug时,也就说明你的
测试用例不全,在修复完Bug之后,要及时补上测试用例。
如果有任何疑问,欢迎添加qq群测试入门大神 755431660 共同学习~