您还未登录! 登录 | 注册 | 帮助  

您的位置: 首页 > 软件测试管理 > 缺陷管理 > 正文

认识软件中的Bug

发表于:2018-06-20 作者:生活如同马拉松 来源:简书 点击数:
  1、bug的认识
  Software changes the world, but bug will destroy it!
  软件重要性和影响毋庸置疑, 但是其质量却关乎软件的生死。软件开发中修bug是必不可少的一个环节,而且往往风险是不好评估的。作为一个专业程序员,修复bug也是日常工作的一部分,同样也是一个专业程序员的基本素养。 谈谈自己对于软件bug的理解。
  2、bug的分类
  bug,其实就是软件期望的行为与实际行为的差异。从程序的角度来看,在软件整个生命周期中都会有bug的出现。需求分析过程中,需求理解的不足,导致的理解错位 ,遗漏甚至变化都可能导致bug;设计本身有好坏之分,但是bug本身还是比较隐晦,不是那么明显。 编码阶段,也会有理解错误,语言特性,第三方库框架,等等导致的bug. 后期打包,部署,运维也会产生 bug,打包的错误,配置错误,以及环境的错位。 自己主要谈谈编码引入的和环境的导致bug。这是最最经常碰到的bug。
  另外也可以从其他方面开分类,比如稳定可重现的bug与不可稳定重现的bug。 比如多线程导致的竞争。数据敏感的导致的不能稳定重现的bug。 从是否与环境关联,业务逻辑的bug,环境相关的的bug等。
  3、bug的修复
  3.1、追本溯源白盒法
  好消息是绝大部分都是容易修复的。可以按照这个套路去修复的,可以事半功倍:
  1. 重现bug。
  如果不能重现bug,一切的修复都是盲人摸象瞎说。 如果能重现的bug,基本一半的问题都解决掉。  如果还可以简化重现的步骤,那么恭喜你那么80%的问题都解决掉。 如何简化这个需要根据上下文大胆假设,小心摸索去除无关的步骤,凸显主要步骤。
  2. 定位bug
  传统ide调试bug,在出现bug的地方设置断点,追本溯源,就可以找到。 往往根据数据流,关键点确认或者折半查找基本就可以找到原因。  如果assert被触碰到,恭喜大大加快定位速度。如果数据是事件,估计需要查找源头,但是往往问题都不大。
  3. 修复bug
  这个往往是逻辑最简单的一步,但是具体情具体分析。每个程序员都是自己模块的专家。
  4. 验证bug
  验证修复的代码的版本,bug的确被修掉而且无副作用。第一确认修复后软件行为的确与预期一致。尽量去验证,即使是验证比较耗时的情况,比如传统软件的安装。如果实在不行,那么麻烦加一个test case。 千万不要相信自己眼睛,有时候会手误眼误,将真写成假,忘记取反而自己检查不出来。专业程序员的声誉就没有了。
  同样需要验证没有regression,因为regression bug是你想不到的bug。尤其是业务复杂,代码不熟悉的时候,一不小心就会有惊喜! 但是如果没有测试套件,自己需要更加谨慎,找同事的眼睛帮你审查审查。 如果regression test靠人,那基本是行不通的,只能祷告吧。
  再分享一个有趣的,有时候两个bug还会相互抵消,所谓负负得正。这时候修掉一个才会发现另外一个。 所以验证一定不要忘记!
  常规修复,就是从现象bug找发生的原因。是有代码的情况下,追本溯源,通过工具,按照log,assert,数据流,一步一步找到原因的白盒定位法。 另外一种是通过黑盒用工程方法去查找原因。
  3.2、工程修复。
  有下面两种bug很是挑战的bug都是不能稳定重现的bug。 一种是特定机器上重现。一种是在特定版本上才重现。
  对于第一种是貌似是环境问题(然而不一定)。 这个时候你需要比较两个环境的不同,如果是两个操作系统,恭喜你摊上大事了。比较两个系统的差异,这个往往耗时而且看运气,而且无源码,无法debug。有时候有log,错误在google或stackoverflow 一搜,问题解决了。但是运气不好呢?分享一个case,之前开发Excel一个插件,在特定window2008 server 上跑不起来。原因是签名验证失败,但是为什么签名验证失败? 排查一周(耗时吧),把各种因素(以来的版本,时间,证书,patch)都去假设,无果。
  有什么办法? 简化还原!
  将应用程序简化,去掉不用模块同时使它同时还能够重现问题。简化,尽可能的去简化,甚至将业务代码全删掉,留一个壳为止,一个hello world为止。在这个过程中,指导你简化到某一步为止,就会发现问题。 对于上面的那个问题,我们删完所有代码,依然问题可以重现。应该是环境问题可信没有源代码,给MS报一个bug,很快出一个patch修掉。这一点MS还是很给力!
  另外,简化还原其实生物上用的更多,比如研究大脑神经元如何通讯? 大脑神神经元那么复杂,到底信息是如何传递的呢? 也是用的简化还原?研究最简单呢的生物几十个细胞的那种,是如何传递I的;发现规律再在稍微复杂的动物身体验证;最后才在人的身体验证。如果直接去人身体研究,那么会疯掉的。
  另外一个某个特定版本有上可以重现。上面常规白盒追本溯源方法,对于这个问题也可用用工程法法黑盒查找原因。将之前版本来验证,如果知道是哪一次提交引入的,那么这个问题也很容易定位到了。黑盒定位到,再去看代码去找原因。分享一个故事, 曾经上线的时候发现一个优先级很高的bug,测试人员对几十次提交对应的几十build,进行验证,折半验证,定位到出错是由具体的某次提交引进的。居然定位比开发还快,真是服了:).
  其实工程黑盒定位于白盒追本溯源是相辅相成。黑盒有时候帮你定位,指导白盒去哪里找原因这是一个相符印证的过程,相互引导的过程。 分享一个看到的案例(JVM加载jar顺序机制导致的bug)。 (一次上线,部分机器服务不能 正常工作,貌似是环境问题。回滚,问题消失,那就是代码引入问题。但是别的机器怎么不重现? 大胆假设怀疑是依赖库加载的顺序不一样,小心验证果然是。进一步研究jvm加载的顺序过程?层层剥洋葱,最后找到原因, 加载顺序与存储的在linux机器上的i-node的序号有关。以后知道这是一个坑,要避免。 )这是一个白盒与黑盒相互印证求解的典型过程。
  还有一个是build脚本案例,下一次在分享。
  4、 预防减少bug,提高软件质量和开发效率
  bug能修复固然是好事,但是修复bug耗时耗力,而且风险大时间不可预估。更好的做法应该是减少bug或者预防bug,让把bug杀死再萌芽之中,或者第一时间发现它去修复掉。这才是软件开发中的正道。下面来谈一谈常规软件开发如何减少bug。
  4.1、单元测试以及TDD
  单元测试可以定位准,稳定重现,发现早(所谓稳准狠)。对于引入的bug第一时间就发现,扼杀在萌芽之中。同时一定覆盖率的单元测试可以帮助你发现regression bug。  TDD是把这种优势发挥到极致,需求确认,接口可用性,代码验证,回归测试,辅助后期代码重构和演化,同时帮助后期维护了解代码等等。如果单元测试这的是程序员必备的法宝,如果不识货给丢掉,真是暴殄天物。
  这个时候如果用TDD开发,还是经常用IDE调试拍错,那么你需要反省,自己的测试类写的多么差。用IDE去调试bug,那么代码已经有点失控。因为单元测试已经不能告诉你哪里出错,这个时候不是测试例子不好,就是粒度太大。
  另外,之前代码里面写的assert,也是一个好手段可以辅助发现定位错误。但是用一个测试用例去显示测试更好一些。
  4.2、Code review
  More eyes , the bug will no place to stay
  Code review 或code Inspection, 多一双眼睛会将bug无处藏身。帮助你检查需求,库的用法,语言写法。负责的code review可以尽早的发现bug 。同时还可以带来,知识传播,相互备份,相互切磋学习。不可多的的好办法。 但是如何做好一个好的code review 需要从流程文化上下功夫。 想想曾经的领导是多么明智,多大阻力也将这个的习惯坚持下来。team中让大家轮流值日review code,强制的,开始不情愿到最后的大家都尝到甜处。
  4.2、流程的CI,CD pipeline
  对于每一次提交,放到CI/CD 的服务器上面跑一遍,经过单元测试,模块测试,集成测试,端到端测试。基本上bug会第一时间发现。CD上面如果部署,尽可能和生产环境一致,对于环境问题的bug也很大程度上暴露出来。
  现在软件项目开发必备。如果一个软件开发公司里面没有一个完整的CI/CD pipeline ,那就可以 不用考虑这个公司,或者去了第一件事就帮建立一个吧。 这个是目前看到性价比最高的投资。
  4.3、环境隔离
  软件以及其依赖最终会运行在其host的环境里面。这种环境问题其实往往最难调试的,最耗时间的。 软件的依赖库(dll hell),版本关系,甚至驱动,时钟,操作系统,甚至是配置都可能引入错误。 甚至专门有一个术语 “只在你机器上有的bug“。
  对于应用程序的隔离,和运行环境隔离,行业一直在努力。从装操作系统的物理机,到虚拟机, JVM,直到docker基本上告一段落。应用程序和其依赖配置完全打包成一个标准的image去发布,运行在一个的标准的容器里面。应用程序与外界隔离,同时运行环境也与运行环境解耦了。 更近一步 infrastructure as a code 理念,环境可以由代码生成,稳定快速生成。终于运行环境由小心呵护的pets 到随便屠杀的 cattle的转变。 进一步,强调运行环境不可变性immutability OS,immutability infrastructure。 这一切让运行环境可控起来。
  Docker作为一个事实的应用程序打包标准,用来发布软件。这样大大减少打包、配置、第三方依赖以及版本管理导致的各种问题;immutability infrastructure  技术理念使得运行环境可以预测,进一步去除硬件操作系统差异环境导致问题。
  4.4、log
  这个放到最好,不是因为不重要,而是因为其越来越总要。云上的应用不能本地调试,也不能让服务宕机调试。这个时候只能看log。写log需要技巧,能让帮助你回放现场,快速排查定位。 更进一步,微服务一个请求,调用多个服务,而且是调用多个服务多次。如果同时有多个请求,那么怎么去在这个多log去寻找某次请求的原因。这个时候一定需要借助工具,elk,zipkin。 当然各种的monitor、matrix也是你的好伴侣让你提早发现问题辅助定位问题。这个还在探索中。
  方法知识工具
  勿忘初心,提高自身的素养还需要从基本功抓起。比如常见的web开发的一个bug,可能需要涉及方方面面。从浏览器URL开始,DNS,CDN,路由,代理,网管,认证,webserbver,servlet,可能会用web框架。比如spring 以及 Spring MVC框架,数据库,缓存; 同样还有留意java语言,jvm本身,以及第三方库 以及profile; 同时可能需http协议,流量转发,要抓包wireshock,fiddle,等等才能定位你到一个问题。基本知识具备,运用一定的工具,就可以让让bug抽丝剥茧,一层一层找到下去。 遇到各种深深浅浅奇奇怪怪的妖魔鬼怪,才能遇事不惊,游刃有余。
  总结
  bug软件开发中必然要面对的,程序员工作中的一部分。 本文将bug的分类,简单介绍bug修复思路,以及回归到主道以预防为主,修复为辅来提高软件开发的效率。最后回归初心,真正修复bug的,需要程序员有好的知识储备,掌握工具和方法,才能见到做到心中有数,游刃有余!