• 如果您想对本站表示支持,请随手点击一下广告即可~
  • 本站致力于提供原创、优秀的技术文章~
  • 有任何疑问或建议 均可以在站点右侧栏处 通过各种方式联系站长哦~
  • 找BUG记

    心路 EXP 127阅读 0评论

    —— By EXP 2014-03-22

    ■ 令人头痛的陈年老BUG(序章)

    前几天,码农朋友甲(下文简称“甲”)拿着我5年前发表在某博文的代码问我:“这段代码是有bug吧?”下面就是他给我指出来的一段C++代码,大家可以先尝试能不能找到甲看到的bug:



      诚然,突然要我查一段几年前写下的代码是否有bug,我内心是比较抗拒的——尤其是我自己写的代码(我对自己还是有相当自信的)——毕竟人的弱点就是不善于揭发自己的短处。不过这都只是次要的心理因素。
      归根结底,所谓打铁趁热,bug也是越早发现越好,新代码的bug总是要比历史代码的bug更容易处理。而面对这个陈年老bug,我已经完全忘记了我在5年前写这段代码的思绪,所以要我马上就应付甲的质疑是不可能的。与其再花费一番周折琢磨我自己的代码,我干脆直接就举手投问:“所有测试用例运行可以通过,是哪里有bug呢?”

    ■ 因注释而蔓延

    甲告诉我,是memset函数使用错误:在C++中,函数memset的作用是对一段连续的内存块赋值,即赋值的单位是字节,换而言之memset只能用于字节数组,但int数组不是字节数组。



      老实说,我很高兴甲会如此仔细的看我5年前的代码。而且毫无疑问,他的观点是正确的。但是也不见得我就是错的。因为早在那时我就已经知道memset函数的局限所在,但我坚持要用这个函数做数组的初始化,是因为我看中了它的效率——
      相对于逐个赋值的方法初始化数组元素、memset的效率要高得多,因为从寻址次数来看,前者的时间复杂度是O(n)、后者是O(1),更何况当时所解决问题的n是上千万级别的。虽然我把memset用在非字节数组,只要我保证初始化的值只为0就不会有任何问题。事实上也是如此。
      于是我自信满满地告诉甲,单纯断章取义地看我这个方法,确实是一个bug。但如果整体地去看我的代码就恰恰相反,我只是利用了bug,并得到了更高效的处理
      但是甲之后的一席话确实值得我深思:
      “或许对目前的这份代码而言,这个bug是被你巧妙地利用了,但是我觉得真正的bug或许不是你的代码,而是你没有文字注释去说明你的想法。不要忘记你已经共享了你的代码,当更多人看到这段程序时,如果他们不了解menset的原理就照样搬用,那么你就无异于在别人的代码中散播了bug,因为你不能把他们代码中的val限制为0。”

    ■ 最危险的组合

    不得不承认,甲是对的。即使我有足够的自信在5年后仍然记得利用这个bug的前因后果,但在这5年间早已误了不少别人的子弟……
      不过话说回来,先不论这个bug的蔓延性,甲能够如此深入琢磨我的历史遗留物、并发现这个bug实属难得——在软件中有一种bug是最难被发现的:组合式的bug。组合式的bug有两种类型:相辅相成型、相互弥补型——甲在我代码中发现的bug就属于后者。
      相辅相成型:举例而言,一个bug是楼梯很滑,另一个bug扶手坏了,但除非这两个bug同时存在,否则只有其中一个bug是不足以让人摔下楼梯的。
      相互弥补型:它与相辅相成型刚好相反,只有两个bug同时存在(或不存在)程序才会正常运行。若只修正了其中一个bug,另一个bug就会曝露出来,而且会让人有误以为自己改错了的假象,因为修改之前程序是可以正常运行的。
      之所以说它难以发现,因为组合bug几乎无迹可寻,尤其是相互弥补型。除非是编译原理的狂热爱好者、抑或出现了非常极端的运行环境。存在组合bug的程序,其通常状态无异于正常程序,而且可能正常运行了很长时间都没有曝露出来
      回到我的代码,它已经正常运行5年了。如果甲没有向我质问他心中的疑惑,而是擅自修改了他所发现的bug,那么我的程序就无法正常运行了——而甲就很可能会因此陷入怀疑自己的正确性的境地。

    ■ 令人头痛的陈年老BUG(终章)

    事实上,不是所有bug都需要解决掉的。很多时候我们明明知道正在为代码引入一个bug,但是我们却依然保留它。因为回避它的代价太大了,我们宁愿限制它的前提条件不让它轻易发生、或者将其“圈养”起来(如try-catch)不让它暴走——如何容忍bug也是一门学问
      不过也总有一些技术葩喜欢另辟蹊径,誓言要代表月亮消灭所有bug维护代码界安全——先不说甲就是这种人,反正我是不会去消灭一个几年前就已经知道的bug的。如果要消灭它,我当时就做了,何必等到现在。
      这前面提到的“新bug更易于旧bug被解决”是一个原因,但我真正担忧的是我或许会引入更多不可控的bug——代码的历史太久远了,我已经近乎忘记了它的逻辑,我一旦盲目修改,完全有可能采用了更危险的方法去解决那个稳定了5年的bug。
      很多时候,我们写完一段代码,只要程序能够编译运行、完成需求功能就算完成了,鲜有考究bug的可能性,大部分的bug都是通过日后使用时再去发现和解决的。其实解决bug的黄金时间在于代码刚被编写的时候,这时候我们往往只需看到异常提示,就可以马上定位异常原因,因为潜意识中我们已经隐约觉得哪个位置会报什么异常了。
      所以当我们在面对一些陈年老bug的时候,其实早就已经错过了解决它的最好时机。这时候不妨将其圈养起来,可能相比于消灭它,会令代码更安全。

    转载请注明:EXP 技术分享博客 » 找BUG记

    喜欢 (2) 分享 (0)
    发表我的评论
    取消评论

    表情

    Hi,您需要填写昵称和邮箱!

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址