前言:工作三年了,工作内容主要是嵌入式软件开发和维护,用的语言是C,毕业后先在一家工业自动化控制公司工作两年半,目前在一家医疗仪器公司担任嵌入式软件开发工作。软件开发中,难免不产生bug;产品交付客户使用后,难免不产生问题,那么关于bug分析和异常处理则是软件开发和维护中无法躲避的工作内容。工作至今,我一直在思考关于bug分析和异常处理,有没有一些原则性、规律性的东西可循,以减少bug,提高bug分析的效率,对于一些异常,基于什么原则进行处理,才能达到客户的要求。这些问题每个行业、每个职位上的人都会有不同的想法,但是大家的目的是一样的,即提高产品的稳定性、可靠性、可用性和易用性。我暂且说说,自己的一些想法吧,不一定正确,但是算是自己想法的一种梳理吧,其实自己希望通过这种梳理增强自己关于这些问题的认识,促进自己的进一步思考。
备注:因为只是自己的一些想法,就暂且不考虑行文了,呵呵
原则一:自己写的软件中肯定存在bug
这一原则适用于软件开发和编码阶段,她让自己保持一颗谦虚谨慎的心,那么自己在设计和编码阶段就会考虑可能出现的问题,对于可能出现的问题,采取防御式编码。譬如:
- 在嵌入式软件中,不使用动态分配内存,这样就杜绝了内存泄露的问题,虽然这样做浪费了内存资源,但是相比后续分析内存泄露的问题带来的资源消耗和产生的严重产品问题,浪费的这点内存还是很划算的;
- 不要相信自己是个没有“笔误”的家伙,因此,在书写if判断的时候,自己总是会按照if(42 == a)这样的方式去写;
- 不要相信自己的是个“仔细”的家伙,因此,每写完一段代码,我都会习惯性地ctrl+s和编译,这样就可以及时发现语法错误和警告,此时,代码修改的不多,因此问题很好被定位和解决,如果等到自己码完几百行代码以后,再去编译,处理编译错误和警告,可能会花费更长的时间。我相信迭代式的编译,步步为营是个不错的想法,呵呵;其实,也要采取迭代式的测试,即每实现一个功能就要去测试,待测试通过后,再去实现其余的功能,最好不要等到所有功能都实现以后,再开始测试,否则,自己花在调试和测试的时间会远远超过自己的预期。(之所以有人会实现了所有功能以后,再开始测试和调试,就是“太相信”自己了,^_^);
- 不要太相信软件会按照自己的预期工作,因此,在设计和编码阶段,在代码中插入调试信息,是个非常棒的想法。如果有调试信息,那么在调试时,就能很方便地看出程序是否按照自己的设计预期在工作,如果没有,也能通过调试信息,发现问题的端倪,至少能缩小问题的原因范围。当然,增加调试信息,会增加系统的开销,降低系统的性能,而且真正的产品中可能不允许太多的调试信息或者日志,那么可以利用编译开关的方法,将调试信息分级,譬如分为正常流程信息、错误流程信息、严重错误信息等等,自己调试或者测试时,打开全部的信息显示或记录;产品发布前,只打开错误或者严重错误信息,这样做,如果产品在客户使用中出现了错误,工程师也能根据这些信息记录,缩小问题原因的范围,为后续的问题原因分析,提供非常宝贵和重要的依据。
原则二:尽早集成调试
原则一是不要相信自己,那么原则二就是不要太相信队友,嘻嘻
每个人理解能力和技术能力不同,那么针对某个功能需求,相关人员的理解就可能出现偏差,不同人员关于接口部分的实现细节可能出现不一致。那么当产品集成调试时,也就是问题的高发时期,而且这些问题通常也很难找到原因和解决。可以具体采取一些措施,减少产品集成过程中产生的问题,譬如:
- 尽早进行产品集成。当相关人员实现了产品的某个细节后,就应该立即进行集成测试,这个时候,产品功能很少,即使集成测试出现了问题,问题也比较容易定位和解决,而且在这个集成过程中,增强了相关人员彼此关于需求的理解;当一个细节测试没有问题后,再进行下一个细节的开发和测试,这样步步为营的做法,不但减少了产品最终集成可能出现的问题,更重要的是增强了开发人员的安全感,让开发人员感觉到身边的那个队友还是很可靠的嘛,呵呵。
- 尽早让用户使用产品。其实,用户有的时候根本就不清楚自己想要的是什么样的产品,只有当你把产品摆到用户的面前,他们才告诉你,他们想要的是那个样子,他们需要的是那个功能(相信此时很多开发人员都想骂人)。因此,当产品可使用的时候,就要拿到用户面前,让用户使用,让用户提意见,然后再继续开发。一句话,就是不要太相信用户当初说了什么。
- 将产品需求以文档或邮件的方式确定下来。当相关人员通过会议确定了一个功能实现细节之后,就埋头去码代码去了,但是集成时,发现对方根本没有按照当初会议确定的要求去做,对方还辩自己就是按照当初会议确定的方式实现的,没有证据的工程师真的是百口莫辩,只有泪奔了。但是,如果此时,你能拿出根据当初会议讨论的会议纪要文档,孰对孰错,就一目了然了!有了文档,即使用户抱怨产品有些功能不是自己想要的,你也有证据表示,这些功能当初是经过他们确认过的。
原则三:做总比不做要强
编码过程中,每当进行异常保护或者异常处理时,是不是都感觉比较痛苦。这些异常保护或者异常处理,现在根本看不到有任何好处,或许以后也永远用不到,但是却要增加自己要码的代码行数,有点吃力不讨好。但是,这些东西如果不做,一旦出现问题,可能是很大的问题,但是如果做了,可能就没有问题了,从投入产出比来看,只是多敲一些代码,多花费一些时间而已,换来的却是,产品更高的可靠性和稳定性。
简单总结一下:
- 防御式编程,避免“笔误”;
- 迭代式地编译、测试和调试;
- 增加调试信息,并对调试信息分等级,通过编译开关选择打开或关闭它们;
- 产品中一定要有日志记录,尤其是操作日志、错误日志记录,这样,当出现问题时,可以尽可能地缩小问题原因范围;
- 尽早集成产品,将功能需求以文档或邮件的方式确定下来;
以上讨论的都是如何避免产生bug,下面说说,如何分析bug。
我将bug依据复现的难易程度分为:必现的bug,比较容易复现的bug,很难复现的bug。
对于必现的bug,我通常淡定地称为其不是bug,因为,通过不断地复现,不断地调试,这些bug通常都能被解决,被解决了,还是bug么?
对于比较容易复现的bug,所谓比较容易复现,就是通过不太复杂操作,尝试几次、十几次,现象就可出现的bug,因为复现操作变得复杂,所以,为了每次复现能够获得更多的信息,尽量多地增加调试信息,以期望问题复现后,极大地缩小问题原因的范围。毕竟复现问题是一件颇为繁琐、枯燥的事情。
对于很难复现的bug,所谓很难复现,就是尝试了各种复现方法,复现了几十次,甚至上百次都无法复现的bug。首先,分析造成那个bug的所有可能的原因,然后尽量针对每个可能的原因,增加对应的调试信息记录,以期望在复现过程中,一旦出现,就能定位问题,不过做到这一点其实是很难的,但是做总比不做强。在第一家公司的时候,我们曾经在交付客户使用的产品中,增加了bug追踪信息记录,以期望找到在测试过程中发现的一个仅出现过一次的bug。
在产品设计和开发过程中,对于异常的处理和保护是保证产品可靠性和可用性的关键,下面说说自己关于这些的一些想法:
-
对异常进行危险等级分类,对于不同的等级采取不同的办法;所采取的办法通常包括:
- 给用户以提示,但是不执行用户的错误操作,譬如用户输入的参数超出了允许范围等,参数的格式不满足要求等;
- 软件的一部分功能崩溃,要尽量不影响其他部分,譬如上位机软件崩溃,不能影响下位机设备的正常运行;前端界面的崩溃,不能污染数据库;
- 自动应急处理,譬如提供冗余设备,当一套设备故障,备用设备立即启用;提供看门狗功能,设备故障后,尽快重新启动,并且从硬存储中恢复距离事故最近的状态;
- 给用户提供应急措施,譬如对于可能造成人体伤害的设备,都需要提供一个紧急停止按钮,该按钮的标示还要非常醒目;
设计良好产品的日志记录和错误追踪功能,这样做的好处是:
- 通过用户的操作日志,确认用户是否存在非法操作,以确认事故的责任;
- 错误追踪信息,一方面可以帮助工程师分析产品发生异常的原因,一方面可以帮助产品开发者分析产品使用过程中可能存在的问题,以帮助进一步改进产品;
无论是产品开发过程中,还是产品使用过程中日志记录和错误追踪功能都非常重要,关于这两个功能,简单总结一下自己的经验:
- 如果是桌面程序,可以将日志记录和错误追踪作为文本文件保存在硬盘上;
- 如果是嵌入式程序,则可以将日志以编码的方式记录在非易失的存储介质中(非易失的要求是保证当设备掉电或者重启,以保证日志不会丢失;另外,对该介质的访问速度有一定要求,不能太慢了,否则会影响设备的性能),然后通过网络或者其他方式将这些编码读出、解析;
- 在保证系统性能的基础上,日志信息能加多少,就加多少;(当遇到问题时,你会非常庆幸自己有一份非常详细的日志在手,呵呵)
- 日志需要必须包含两个要素:时间、做了什么;尽量包含以下要素:操作是否成功、失败的原因;
- 尽量避免日志的循环往复记录,譬如系统循环往复地发生一种错误,如果每发生一次错误,就记录一条,那么有限的日志记录空间就只有这一条日志了,留给工程师分析问题的信息就太少了,可以采取这样一种方式:错误发生时记录一条,错误消失时再记录一条,以此,就可以确认错误持续的时间,而且也不会覆盖掉其他的日志信息;
以上只是自己三年工作以来对于一些问题的思考,囿于自己所从事的行业和经验,有些想法还不太完善,也不具有普适性,仅算是一种思路的梳理吧,只是希望通过这种梳理能够帮助自己更深入地认识这些问题,想出更好的解决的办法,呵呵。