本文转载自微信公众号「 跨界架构师」,作者Zachary 。转载本文请联系 跨界架构师公众号。
大家好,我是Z哥。
可以不夸张地说,程序员可能有一半的时间都在修bug。虽说,根据28原则大部分bug都可以在搜索引擎上搜到(业务性bug除外),但是往往剩下的那20%bug会花费我们80%的时间。
虽然解决这个问题最好的是方式减少产生bug,但是再怎么减都不可能减到0,我们还是有必要提高自己Debug的效率。
因为在Debug方面,水平高的人可能在效率上能领先至少一个数量级。我在这个行业从业将近9年,我见过太多这样的案例了。网上也流传过一个传播度很高的案例,有一个阿里内部的团队排查好几天未果的一个问题,去请教多隆大神,分分钟就搞定了。
很多人的Debug能力之所以长期止步不前,在我看来主要原因是两个。
- 一是对IDE的功能不够了解,只会用平时一直在用的基础功能。
- 二是没有掌握一个合理的Debug思路,属于“运气型”选手。
不同的编程语言,主流的IDE都不同,所以我主要就第二点展开说说我的经验。
有些经验的程序员都知道,Debug的过程最重要的不是怎么修复bug,而是怎么找到产生bug的地方。所以,下面要讲的思路主要也是围绕排查bug相关。
缩小问题范围
缩小问题范围的方式有很多,本质上其实是从当时的环境中找到与问题更高相关的变量。最常见的变量主要在以下这些:
- 运行环境
- 所操作的数据
- 浏览器
- 对应的源码版本
建议你先从这几个变量进行验证。然后再弄清楚上一次正常操作与当前出现bug的操作之间的这段时间发生过什么。大多数情况下,问题的根源就藏在这里面。那种潜藏很久才遇到的疑难问题,毕竟是少数。
提炼并优化每一类bug的标准处理流程
工作中很多流程需要SOP,在修复bug这件事上也可以这么做,如此可以将每一次修复一个疑难bug的过程给沉淀下来。
常见的bug类型主要有4类:
- 输出与预期不符
- 程序报错
- 程序明显响应慢
- 程序crash
每一类都有适合它们的排查方式,如果你总是用同一个套路去排查这4类问题,效率自然不会太高。
01 输出与预期不符
这种bug最头疼,为什么呢?因为它不像那种异常、报错的bug,有堆栈信息,可以快速缩小排查范围,甚至直接定位到产生的地方。
那么怎么办呢?如果这个问题在测试环境,那么最简单,直接单步调试走起,这个时候如果对IDE的调试工具掌握得越深入,效率也会越高,比如条件变量、多线程调试等等。
这里多说一句,强烈建议每个人掌握自己所用IDE的条件变量和多线程调试这两种方法,在当前的大环境下,整个软件开发领域的大型项目和多线程运用都比几年前高得多。
如果没法单步调试的情况,那么只能通过多打一些日志,来达到接近单步调试的效果。不过这点需要你做一些预判,在一些代码分支、可疑位置打上日志即可,毕竟编写记录日志的代码也需要时间不是。
02 程序报错
这种bug对有些经验的程序员来说是最简单了,因为直接告诉了你产生异常的代码位置。
但是对新手就不同了,很多新手会拿着描述异常的一堆文字去搜索引擎搜,比如(NullPointer Exception),搜出来N多文章,一篇一篇看下来并尝试都发现不能解决自己的问题,其实就是由于自己还没习惯于去看堆栈信息。因为别人的NullPointer Exception和你的NullPointer Exception产生的原因并不一样。
堆栈信息中记录了整个调用链路,所以通过这里你可以看到完整的方法调用顺序。
不过值得提醒的是,在日常编写的代码的时候,千万不能随意的try catch代码块,然后throw一个new exception,因为这会导致堆栈信息不完整。
03 程序明显响应慢
这种问题一般是在产生资源竞争,或者资源紧张的时候发生。排查他们的难度也比较高。
如果说前面两类问题中,高水平和低水平的区别只在于解决效率的高低上,那么这个问题对低水平的程序员们来说可能是不管花多少时间都找不到问题的原因。
不过不要紧,我建议你以后遇到这种情况,优先从以下这几个指标入手。
- TCP连接数
- 内存占用率
- 线程数
对于TCP连接,你身边得常备一份netstat命令手册,然后敲入命令,分别查看连接数是否接近到了65535?TIME_WAIT、CLOSE_WAIT状态的连接是不是过多?
大多数情况下,TCP连接相关的问题主要就是两个:
- 连接使用完后未及时释放
- 针对高频调用未使用长链接,而使用了短链接。此时一旦下游服务响应慢就会快速打满65535个连接。
对内存问题的分析,主要是就是通过分析GC来进行,主要关注是否有什么类型的对象占用内存过大了。如果存在过大的情况,主要原因是以下两个:
- 某个大对象应该是共享使用的,在代码里不小心写成了每一个实例各自一份。
- 某个对象分配的时候不小心带上了static关键字,导致GC一直无法回收为其分配的内存。
不同的编程语音有不同的GC分析工具,需要熟练掌握。
对线程的分析,和TCP连接类似,主要集中在线程的数量和状态上。线程并不是数量越多、性能就越好。数量越多,可能花在线程之间的上下文切换上的时候就比实际执行代码逻辑还要多。
另外,是不是有大量线程blocked或者deadlocked了?随便挑其中一个线程,分析其当前的堆栈信息,就是问题所在。
04 程序crash
导致crash的主要原因有两点。
- 是由于前面提到的原因3未及时察觉,导致程序运行直到资源耗尽,由操作系统干预强行终止运行。
- 代码中存在未捕获的exception。
第一点参照原因3的处理方式。
第二点就很简单了,在代码的最外层做一个大大的try catch,然后打上日志将堆栈信息记录下来,发布到线上,自然就能看到是那里出现的问题,然后转到原因2的处理方式。
最后,提高Debug能力必须要多实践。所以,我是非常建议你在条件允许的情况下,勇敢地去挑起排查线上疑难杂症的任务,甚至并不是你直接负责的功能模块。
可能在外人看来你在帮别人“擦屁股”,但这会让你的Debug能力得到明显的提升,并且容易形成对你的依赖,让你越来越强。
好了,总结一下。
这篇呢,Z哥和你分享了我对提高Debug效率这件事的看法。
想要提高Debug的效率,一是要对IDE工具有熟练的了解,知道一些高阶的使用方式。二是要对Debug有一套自己的思路。
对于第二点,我建议的步骤是:
- 缩小问题范围
- 提炼并优化每一类bug的标准处理流程
Bug主要分为4类,我在文中给出的思路也区分了这4类:
- 输出与预期不符
- 程序报错
- 程序明显响应慢
- 程序crash
希望对你有所帮助。
在我看来,Debug是一件很好玩,也很有成就感的事情,不亚于设计一个项目的框架。而且Debug还能让你真正地体会到什么是“魔鬼藏在细节里”。