Bug 是的学习机会。所以我们怎样尽可能多地从修复的 bug 中吸取经验?我至今已经用了13 年的一个方法是,写下这个bug的简单描述、修复方法以及吸取的经验。
  遥想 2002 年,我偶然发现一篇描述如何使用的文章。我从那时起使用这个方法了,并且相信它能帮助我成为一个更出色的程序员。不过后来我忘记在哪里呢,索性我写一篇。
  每当我修复一个特别棘手或有趣的 bug,我总会花几分钟写下一些这个 bug 的信息。这有一个典型记录的例子:
  例子
  【日期】:2015-08-17
  【问题】:当解码 Q.931 信令时无限循环
  【原因】:当在Q.931信令中发现一个未知的元素id时,我们试图通过读取它的长度来跳过它,并且将位置指针迁移几个字节。但是,在这个例子中的长度是零,导致我们反复跳过相同的元素id。
  【怎么发现的】:在解码一个 Ethereal 从 Nortel 追踪到的安装信息时发现了这个问题。他们的信息是 1016 字节长度(包含大量快速启动元素),但我们的 MSG_MAX_LEN 是 1000。通常我们会收到一条来自 common/Communication.cxx 的信息,但现在,当直接输入需要解析的数据时,数组末端内存访问越界,其恰好是 0,暴露了这个问题。
  为了找到它,我仅仅在 9931 解码中添加一些打印输出。但很幸运数据恰好是零。
  【修复】:如果长度是零,设置为 1。这方式总是行得通。
  【在哪些文件修改了】:
  callh/q931_msg.cxx
  callh/q931_msg.cxx
  【我导致的】:是的
  【解决Bug的时间】:1小时
  【教训】:信任收到信息中获得的数据。不仅仅是产生大量可能导致问题的数据。显示长度为 0 也同样不好。
  实施方式
  我有一个命名为 bugs.txt 的纯文本文件。在文件的顶部是一个具备所有标题的模板,但是没有包含信息。当我添加一个新的记录,我复制模板部分,粘贴在模板下面。然后完成记录并且填满信息。大多数展示在上面例子中的 fields 应该是不需要声明的。
  有必要指出这并不是 bug 追踪器。我并没有添加所有我修复的 bug 。例如,如果我只是忘记在代码中添加声明,一旦发生这种情况,我意识到问题出在哪里了,我并不会添加这条记录。仅仅当 bug 本身,或是修复,或是调试过程特别有趣,我才会添加一条新的记录。通常,是我导致了这个 bug。但是偶尔,特别是花几天时间才追踪到的困难 bug,尽管不是我造成的我也会添加一条记录。
  一旦修复了一个 bug,我的第一反应是松了口气然后继续前进。我试着一更正后写下这个记录。这时我的脑海中依然清晰保留所有的细节。拖延会让精确回忆发生的事情变得很困难(或者我压根忘记写下这条记录)。
  至今,我已经有 194 条记录,平均每个月有一条新的记录。重要的是教训部分。这里需要自我反省。是什么导致这个 bug 的特殊性?我发现经验常常来自于三个不同的方面:
  编码。我在代码中犯了什么错误?我是否忘记了 else 部分代码?是否系统调用失败,但没有检查 response?我在未来应该怎么调整代码来防止这些问题?
  测试。一般不包括本可以在测试中捕获的 bug 。要是这样的话,应该在哪个阶段测试——单元、功能、系统?丢失了哪个测试用例?
  调试。我本可以怎样快速定位 bug?我是否使用了正确的方法?我是否假定了太多?我是否需要在代码中使用更好的日志记录?
  益处
  Nassim Nicholas Taleb 在《Antifragile》中写到:“错误包含丰富的信息”。我完全同意这个观点。Bug 帮助我们更好地理解系统,告诉我们怎样提高编码、测试和调试技巧。所以我认为尽可能从 bug 中学习经验,是再正常不过的事了。
  我发现为每个有趣的 bug 记录下来,让我轻易学习到很多。在记录的行为中我会对发生的事情思考得更深刻。同样,一旦记录下来,我可以在之后检查发生的事情。偶尔,我也会浏览文件,只阅读教训部分,对我认为是从 bug 中学到的有价值的经验加强记忆。
  我记录 bug 文件至今已经有 8 年了。这是一段漫长的时间,但是我坚持下来了,因为作为一名合格的程序员,它帮助我进步。尝试一下吧,看看它是否也对你有益!