也谈软件调试
每年二月末到三月初,公司都会安排一批实习生到各个部门实习。虽说去年经济危机了,但公司的实习生数量似乎并没有减少。起码我们部门"新同事"的数量基本与去年持平。按惯例,每位新同事都会有一名导师,与此同时各个部门还会根据自身的业务特点对这批学生进行有针对性的集中培训和交流。比起我入司那会儿,现在的实习生已经算是幸福多了。我那会儿实习生人数少,部门没有安排什么培训,完全靠导师安排自己努力学习。此次培训的内容是经过老员工们讨论和对新员工的需求调查后才确定的。其中新同事们普遍对如何进行程序调试这块比较感兴趣,我负责准备和实施了这个题目。虽说有过几年调试程序的经验,但是自已也没有系统的总结过,这次培训后顺便在这里做一下总结和记录。
说到程序员,各位大脑中第一反应就是编码;但我们知道软件开发可不仅仅只有编码,调试也占据了程序员很大一部分精力。程序员"简单"的工作中,80%的时间都是在编码和调试(现在文档工作也不少)。调试的对象是BUG,BUG是什么呢?BUG就是编码过程的伴生品。既然将之诠释为"伴生品",那就意味着"凡是软件,内必有BUG"。也许有人不同意这样的观点,但无关大碍,因为如何看待BUG本身就可以看成是一个哲学范畴的话题,大可见仁见智。
调试前,首先做好心理准备。
调试BUG的过程可以用"艰苦卓绝"来形容;特别是一些"又臭又硬"的BUG,难于重现,难于定位,甚至在投入相当大的精力后仍然无法Fix。所以调试BUG前就要摆正心态,要保持清醒、保持耐心,甚至要做好打"持久战"的打算。要知道Unix下还有一些BUG也是在隐藏了几十年后才被Fixed。
预防BUG的发生,降低BUG发生概率。
我们还需要清醒的认识到:事实证明与编码相比,调试BUG的成本是更高的。BUG可简单分为产品Release前BUG和Release后的BUG。无论哪一种,你都要经历收集数据、重现BUG、定位问题、修正问题和Accepted等多个步骤后才能重新Release。这其中以Release后的BUG花费的成本为更高。既然如此,我们何不采用一些手段来相对的预防BUG的发生,降低BUG发生的概率呢!这里说说我想到的。从一个软件的整个生命周期来看,保证软件质量应从需求开始,但这里我们主要关注编码阶段。从个体开发者角度我们可以从以下三个方面考虑:
* 充分的静态代码检查
充分利用你手头上的工具对你编写的代码做严格的检查,这些工具包括编译器(尽可能将警告级别提升到你可以接受的最高级别)、LINT工具等。这将帮你将代码中潜在的细小问题发掘出来,避免这些问题在事后成为隐藏的大BUG。
* 调试版添加断言
充分利用断言Assertions这把发现BUG的利器,借鉴契约式编程的一些规则,在你的调试版代码中适当的地方添加Assertions。这样的方法同样可以帮助你及时发现代码中隐藏的缺陷。
* 充分的单元测试
充分的单元测试提高代码覆盖率,减少业务逻辑遗失导致的BUG;单元测试用例还可以结合断言发现更多程序潜在问题。如果能做到测试先行,也许效果会更好。
* 代码同级评审
让其他人来审核你的代码,提前帮你发现潜在的问题;如果能做到结对编程,也许效果会更好。
从组织的角度来看,持续集成的实践可以让组员更加及时的发现编码阶段的问题,不让问题遗漏到后面阶段成为严重BUG。
如果很好的实施了上述这些手段后,你的BUG发生率会大大降低,但是前面说过:BUG不能避免,一旦BUG发生了,该怎么办?其实与BUG做艰苦卓绝的斗争,也是有一定方法的。
* 收集"现场数据"
BUG是表象,我们要发现内部原因还是需要更多的表象数据去推理。我们需要收集到足够的"现场数据"。比较初级的、基本的、也是被大家常用的方法就加print语句,将BUG现场周围的"证据"输出到屏幕上或者文件中,这样你能更直观的看到;当然我这里是不推荐Print语句的,因为不够专业、不够高效;拿起你的源代码调试工具,诸如GDB来完成这一功能吧;有时候现场数据可以通过你的程序运行日志而直接得到这样就更简单了,调试阶段这种日志要保持尽量详细且必要;如果是运行在客户现场的程序,你无法添加Print,也无法GDB调试,那么你需要在测试环境下重现BUG表象,然后再利用工具收集数据。重现BUG表象多数情况较容易,但是也不乏找不到重现条件的时候;这时候你就只能根据已有的运行日志等通过"顺藤摸瓜"的业务逻辑分析去"猜测"可能的重现步骤,逐一尝试,做好尝试记录,隔一段时间对记录进行分析,找出一些"蛛丝马迹",也许会帮助你节省些时间。
* 定位问题所在
有了"现场数据",需要你用"火眼金睛"从这一堆数据中找出你真正需要的;如果无法直接识别出真数据,那么可以根据数据做几组不同数据组合的模拟测试,在数据变化中"去伪存真",找到那个"真悟空"。有了信赖的真实数据,你一般都可以根据代码逻辑推理出问题所在。但有些时候还是需要通过隔离代码、缩小嫌疑代码范围等方法才能锁定一些较难BUG的具体问题所在。
* Fix and Test
既然找到问题所在了,那剩下的工作就是修正它并重现验证;新用例同时也补充了你的单元测试用例库。如果修正失败,那就从头开始新一轮分析过程吧。
BUG真的是种类繁多,情况多样。 上面也仅仅是一些常见BUG解决过程的体会。如果你已经在一个BUG上整整消耗了一天时间了,那么建议你休息一会儿,小憩一会儿,甚至是睡一觉到天亮。也许梦中你会发现问题所在。要知道大脑潜意识是会帮助我们的,估计很多人都有过类似经历,不是吗?
定期回顾你自己"出产"的BUG列表,你会发现很多BUG是你在预防阶段做的不够好而导致的,特别是一些涉及内存操作的BUG,如果前期预防工作没有做好的话,那么后期解决BUG花费的精力会很多;定期回顾还有一个好处就是强化你的思维意识,让你以后尽量不再犯同一个错误。
评论