再论:如何解Bug

前言:

这篇文章是基于Android系统定制和维护而写的,所以里面的内容与Android相关也就是说系统是Android,语言是Java,特点就是没有明确的需求行为规格定义,只有代码,很多行为也比较诡异,代码架构不是很完美,崩溃,异常比较常见(RuntimeExceptions)。虽然是基于Android的,但是方法是通用的。

什么是Bug

首先要明白什么是Bug,这点可能不同的人(测试和开发)有不同的理解,不同的公司也会有不同的标准。但是通俗的来讲,Bug就是指未预期的行为(unexpected behaviors),比如系统崩溃(Crashes),发生异常(Exceptions),或功能失常(Malfunctions),比如说点击了保存,但实际上没有保存,点击了发送却没有发送成功。
那什么又是预期行为(Expected behaviors)呢?预期行为通常是指需求说明书中规定的行为,但是很多系统(比如Android)完全没有需求或规格说明书,所以更多的时候判断的标准就是常识和经验,或与其他软件系统进行对比。比如说虽然没有需求规格书,但是通常意义上保存应该能把文件或用户所编辑的内容写进磁盘文件,如果没有成功,那行就可以认定为Bug。所以说Bug也是受主观因素影响的。

与Bug打交道的人

软件的理想情况是程序员所写的程序(算法)都是正常的,但是有理论证明这是不可能的,没有任何一个算法是完全正确的,另外只要是人就是犯错误。所以现代大多数的软件都这样来保证它的正确性,开发人员交给测试人员软件,测试人员来保证它的质量,通过测试Bug,当软件的Bug在一定的允许的范围内(小于10个)就可以认定软件已达到预期了,能正常工作并且可以发布。
在这一过程中涉及三组人:一个是开发人员,一个是测试人员,另外就是管理层。很多大公司的开发人员与测试人员都分属不同的部门。开发人员负责开发出可测试的软件版本,测试人员对软件进行测试,测试出Bug后提交给开发人员,开发人员再解决Bug。通常他们都有来自管理层的压力和绩效考核的压力,也就是说管理层对测试人员说:你要尽可能多的提交Bug,无论用什么方法,或者你每天必须提交3个Bug;另外测试人员的绩效也与其所提交的Bug数量直接相关。管理层也会对开发人员说:你要在Milestone前把所有的Bug都解掉,上个星期的Bug怎么还没解掉,你想不想干了?另外开发人员的绩效也会与Bug数量有关,如果有Open的Bug会扣新水或奖金等。
这样一来,原本比较简单清纯的关系变的就有些复杂了,无论是测试人员还是开发人员会把人的本性加进来,把政治和功利也带了进来,像测试提交了不是Bug的Bug,或是开发人员强行关闭Bug都是常有的事儿。因此,测试与开发人员便有些战争:
      开发:你这不是Bug,这个行为就是这样设计的?
      测试:但是这样设计很不合理!
      开发:那这个要去找UX设计的人?对于行为的修改我们不能做主
      测试:我不管那么多,我只管测试
      开发:#%^@&$...
      ....
      测试:你为会么关我的Bug
      开发:因为它无法复现
      测试:但当时我的确是遇到了
      开发:那你再复现出来吧
      测试:@#$%^...

Bug的常见分类

如前所说Bug就是未预期的行为。所以Bug常见的类型有:崩溃(Crashes),异常(Exceptions),功能失常(Malfunctions),差劲的用户体验(Bad user experiences),未实现的行为和功能。

Bug的复现机率

这个是特别是值得注意的,首先Bug描述上面写的复现机率只是测试人员在当时的现场Bug出现的机率,另外这个也是比较主观的,比如试了三次出一次,有的人认为是必现的,也有的人认为是有一半机率(Above 50%)。
但是经过这几年解Bug总结的经验发现,真正的随机出现的Bug只有与线程时序有关的逻辑才会有。其他的随机Bug并不是随机,只可能是测试不太清楚操作步骤和所有的数据或当时的环境现场。

如何解决Bug

1.弄清楚Bug究竟是什么样的问题

拿到Bug后,不要着急去复现,首先要弄明白Bug究竟涉及到哪些行为,哪些操作,当时的现场和环境是什么样的,用了什么样的数据,操作到哪一步出现了问题等等。因为测试人员对Bug的描述是很主观的,可能不太清楚,所以就要与测试人员进行沟通,弄明白这些问题:
      当时的配置,环境,现场是什么样子的?
      涉及到哪些行为?
      进行了什么样的操作?
      操作到哪一步出现了问题?
      出现了什么问题?
      如果说是功能失常,或差劲的用户体验或未实现的行为,那么测试所期待的行为又是什么样子的?
      测试人员有没有附上日志,截屏或其他调试信息。

2.如果Bug是崩溃或异常
先不要忙着去复现,因为对于崩溃和异常,日志当中肯定会有崩溃和异常的直接相关信息,通常这就能定位出问题,比如对于Java语言来说如果出现未Handle的异常,那么日志中肯定会有异常相关的信息调用栈等。所以通过日志文件就可以定位出问题出现的源代码。这样就多了一个线索,因为你可以通过源代码来推测是什么导致了这个异常,可能做了什么操作,环境现场发生了什么变化等,通常来讲这比测试人员提供的信息更有参考性。
3.如果是差劲的用户体验或是未实现的功能
这个要与测试人员进行沟通,甚至是与管理层和设计者进行沟通,如何修改行为或如何实现行为,并且什么时候进行修改等,通常这个开发人员是没有决定权的。
4.尝试复现Bug
这个步骤也是很有必要的,因为即使你修复了Bug,也是需要复现Bug来进行验证以保证你真正的修复了。所以,如果无法复现Bug那么就说不清是因为一直都没有复现还是被你修复了。
如前所述,如果你完全掌握了Bug所发生的条件,相关的操作步骤,那么复现Bug应该很容易,相关的操作步骤应该Bug描述都会有,但是Bug的相关条件(环境,配置,上下文和数据)这些东西可能要去挖掘才能知道,因为测试可能没有注意,也可能忘记添加到Bug描述中去,所以为什么第一步是弄清楚问题。
如果是真正的随机问题,那肯定是与线程时序相关的,对于这类问题,发生的原因通常是线程应用不当,产生的Bug也相当的隐蔽。对于这类问题的复现方法是,找到相关的代码逻辑和线程,进行人为的干预,以加大问题出现的机率,比如可以用Thread.sleep()来阻塞线程,以让其以某种特定的时序来运行,以加大Bug复现的机率。
另外,如果难以完全复制Bug出现的条件,比如特定数据或特定的条件等,也可以通过修改代码来实现,比如某一逻辑只有当对象为空的时候才会进,但是99%的情况下它都不为空,且没有合数据让它为空,那么就在代码中直接用空的对象,以复现Bug。
5.迭代:猜测问题并定位问题
这一步的目的就是定位出问题究竟出现在哪一个源文件,哪一行或哪一逻辑中。当然,这一步要建立在Bug能复现的基础上。
定位问题也有很多方法,比如设断点调试,添加日志,一个很常用的方法是加上Thread.dumpStack(),以了解函数的逻辑调用关系。这个主要与软件的类型和开发环境有关,很多软件系统无法进行实时的调试。所以添加日志文件是最通用的方式。
6.迭代:修复问题并进行验证
如果定位出问题了,就可以进行修复,有些问题可能容易修复,有些问题可能比较复杂,这要视具体的软件和具体的问题而定。还有可能是,特别是那些没有需求规格的软件,会发现代码本来就是这样子工作的,这时就要与管理层和测试和设计者讨论这到底算不算Bug且要不要修复。当然最悲剧的就是发现引发这个Bug的原因是系统的架构设计不合理导致的,这个时候就只能用其他方法而不是修改整体架构设计了,当然这个是前期设计造的孽,是否要改要看管理层了和时间预算允许不允许了。
这也是一个迭代过程,进行修复和验证,直到Bug被完美的修复。也就是说Bug被修复,所做的修改最小,且不会引发其他的Bug。
7.提交CodeReview并进行CodeReview
像开发一样,解Bug也是开发周期的一个部分,所以为了质量最好还是要进行CodeReview。
Review的目的就是让修改达到最小,且不会引发其他问题。为什么让修改最小,因为你改的越多,产生的影响也就越多,如果没有足够的单元测试来验证,那么引发其他问题的可能性就越大,所以对于修复Bug来讲,修改越少越好,改动的代码越少越好,修改的文件越少越好。(当然,最理想的情况是不用修改代码也能把Bug给解了^_^)。
8.提交Patch和关闭Bug
这个是解Bug的最后一步,但是也要小心,特别是对于公司所用的开发工具和版本管理工具不熟悉的人,一定要小心,因为即使你的Patch已达到最优化,但是如果在Checkin代码时发生了问题,那就是悲剧中的悲剧,如果引起了Build error那就等着挨骂吧!
要注意的是把所有的改动都提交到版本控制中去,为了以防万一,在提交之后最好再从版本中拉出来编译验证,一是看是否有Build error(通常是由于提交不全导致的),二是看是否有漏提交(结果就是Bug没有解掉)。

总结

在《The Practice of Programming》(《程序设计实践》)有一句关于解Bug的话说的很好:Debugging involves backwards reasoning, like solving murder mysteries. Something impossible occurred, and the only solid information is that it really did occur.(解Bug/调试需要逆向推理,就像侦破离奇的谋杀案。貌似不可能的事情发生了,而且唯一确定的线索是它的确发生了)。仔细品味这句话,解决Bug的过程的确是这样。我们拿到是就是Bug---一个结果,不断的尝试寻找线索,推敲去寻找它的原因,找到了原因了后再想办法修复它,我们是二十一世纪的福尔摩斯,我们是软件工业中的柯南。回头再看那些解决掉的神奇的Bug,与福尔摩斯破案后的感觉类似,也有相当的成就感和自豪感。

后记:

关于解Bug有一本经典的书《The Science of Debugging》(《程序调试思想与实践》),这本书写的相当的实在,内容以Bug为主题,覆盖了解Bug所需要的所有话题,内容详实,完全是作者几十年作为Debugger的总结。在我看来这本应该与《代码大全》之类的平起平坐。感兴趣的朋友不妨读一读。

你可能感兴趣的:(android,架构设计,单元测试,测试,patch,debugging)