不,bug在你的代码里(还有我的)
完全有可能我之前已经在这个话题上发表过一些内容。我知道我在社交媒体上发布过相关内容。 时不时地——幸好不是太频繁——我在 Stack Overflow 上看到类似这样的帖子:
“这看起来像是 VS.NET 的一个 bug” “我 100% 确定我的代码是正确的” “这似乎是一个明显的 bug” “这是编译器的 bug 吗?”
最后一个至少是以问题的形式提出的,但通常周围的文字清楚地表明发帖人期望答案是“是的,这是编译器的 bug”。 有时候,bug 确实在你使用的库中,或者在 JIT 编译器、C# 或 Java 编译器等等中。我自己也报告过很多 bug,包括一些我之前写过的有趣的 bug,比如相机固件的问题,或者一个只在 Linux 上失败的单元测试。但我尽量保持以下心态:
当我的代码行为不符合我的预期时,我的第一个假设是我在某个地方出错了。
通常,这个假设是正确的。 因此,我在诊断问题时的第一步总是尝试确保能够可靠地复现问题,然后轻松地复现(例如,无需启动移动应用或在 CI 上运行单元测试),再然后简洁地复现(用尽可能少的代码)。如果问题在我的代码中,这些步骤有助于我找到它。如果问题确实在编译器/库/框架中,那么在我完成所有这些步骤后,我处于一个更好的位置来报告它。 但是等等:仅仅因为我成功创建了一个最小化复现问题的方式,并不意味着我肯定找到了一个 bug。问题仍然可能出在我身上。此时,bug 不太可能是我代码中最常见的意义上的问题(至少对我来说):“我本意是做 X,但我的代码明显做了 Y。”相反,更可能的情况是我使用的库在设计上与我的预期不同,或者我使用的语言工作方式与我的预期不同,即使编译器的行为符合规范¹。 因此,我接下来要做的是查阅文档:如果我成功将问题隔离到某个方法的行为不符合预期,我会多次阅读该方法的整个文档,确保没有一些额外的说明或免责声明来解释我所看到的现象。我会寻找我做出假设的模糊点。如果是编译器的行为不符合预期,我会尝试隔离出让我困惑的特定行或表达式,找出规范并仔细查看每一个细节。如果内容太多,我可能会在这个阶段做笔记,以便更好地理解。 此时,如果我仍然不理解我所看到的行为,那可能确实是别人代码中的 bug。但此时,我不仅有一个最小化的示例可以发布,而且还有一个理由说明为什么我认为代码应该有不同的行为。然后,也只有到那时,我才觉得准备好报告一个 bug——并且我可以以一种让维护者的工作尽可能轻松的方式来做。 但大多数时候,事情并不会这样结束——因为大多数时候,bug 在我的代码中,或者至少在我的理解中。预期 bug 在我的代码中的心态通常能帮助我比预期是编译器 bug 时更快地找到那个 bug。 还有一个遗留问题:如何传达这个信息而不显得居高临下。如果我告诉某人 bug 可能在他的代码中,我知道这听起来像是因为我认为我写代码比他更好。完全不是这样——如果我看到意外的行为,那可能是我代码中的 bug。这也是我写这篇文章的原因之一:我希望通过在 Stack Overflow 的评论中链接到这篇文章,能够更积极地传达这个信息。
¹ 这在 C# 中仍然绝对会发生——而且我已经不再为此感到难过。我主持 ECMA 标准化 C# 的任务组。这包括一些对 C# 的了解远比我深入的人,包括 Mads Torgersen、Eric Lippert、Neal Gafter 和 Bill Wagner。即便如此,在我们每月的许多会议中,我们仍然会发现一些让我们中的一个或所有人都感到惊讶的行为。或者我们只是无法就标准中编译器应该做什么达成一致,即使标准就在我们面前。这既令人谦卑,又令人兴奋和好笑。