无法理解的程序Bug分类大全 - jvns


以下是无法理解Bug分类:

  1. 很难复制
  2. 你不太了解整个系统
  3. 很难获得有关Bug的数据
  4. 你的假设之一是错误的
  5. 这个bug真的很复杂

 
1.本地难以重现的bug
那些让我考虑转行的bug通常只发生在少数用户身上,无法由通常用户或内部完全重现,并且每个bug报告中的描述略有不同(有点像大脚怪目击事件) )。
以下是一些难以重现bug的具体方式:
  • bug是不确定的

你用完全相同的输入运行你的程序 1000 次,它只失败一次。这种情况在多线程程序中的竞争条件下经常发生。
该错误仅发生在生产中
许多bug很难在您的开发环境中重现,要么是因为很难确定究竟是哪些输入触发了错误,要么是因为它们仅在某些难以重现的条件下(如大量流量)发生。
  • 您无权访问那些发生过bug的机器

这方面的三个例子:
  1. Bug在客户计算机上运行的软件(二进制文件或网站),他们遇到了问题,而您无法直接访问他们的计算机以查看发生了什么。
  2. bug问题涉及您没有很多访问权限的托管云服务。
  3. bug问题仅发生在您无权访问的数据输入上(可能是因为数据已分类/私有)

  • 您无权访问重现bug所需的数据

bug很容易重现,但他们重现它所需的数据是机密的,所以他们不被允许访问。
  • 重现很慢

有些bug您确切地知道如何重现它,但是重现bug需要很长时间(例如 20 分钟或更长时间)。这很难,因为很难保持专注:也许每天只能尝试 1 个实验!
 
2.你对整个系统不太了解
即使您可以重现该bug,但如果您不了解程序中存在该bug的部分是如何工作的,您最终可能会陷入困境。
  • 未知的未知数:bug涉及您不知道的系统或概念

有时bug是由您甚至不知道存在的系统部分引起的。例如,当我调试这个 TCP 问题时,我从未听说过 Nagle 的算法或延迟 ACK。所以很难认识到是他们造成了问题!
我能够诊断出这个错误的唯一原因是工作中的某个人偶然发布了一篇关于它的博客文章,我记得症状是相似的。
接下来的几节是关于程序工作的混淆可能导致错误难以解决的更具体的方式。
  • bug位于您不了解的外部库中

有时,该错误位于您完全不熟悉的库或开源程序中,但无论如何您都必须修复它。这使得调试难是因为:
  1. 你需要了解库是如何运作的
  2. 修改库并让您的程序使用库的修改版本并不总是那么容易,因此很难进行试验和更改或向库添加额外的检测

  • 你根本不明白bug信息

一些错误消息最初似乎完全无法理解。这方面的几个例子:
  1. “β 的值可能会产生 dom!”,来自Mark Allen 关于该错误消息的演讲
  2. “大小必须介于 16793600(16MB) 之间”第一个元素:点数来自谈话Kiran Bhattaram的被诅咒的操作系统教科书的故事
  3. 如果您不知道某些编译器错误消息的含义,它们可能会非常令人困惑

这些很棘手,因为不清楚从哪里开始——什么是 β?这个元素点在这里做什么?
另一个变体是调试以令人困惑的方式格式化的输出。
  • 您不知道要搜索哪些关键字以获取更多信息

很多人提到的一种情况是:您搜索一个您认为与您的错误相关的关键字,您得到了 1000 万条结果,但没有一个是有用的。
  • 该错误位于专有系统中

搞清楚一个不熟悉的系统已经很难了,更糟糕的是你连源码都看不懂!
  • 该系统记录不全

一些变体:
  1. 没有文档,或者文档非常稀疏
  2. 关于系统的唯一信息是从别人无法联系-的人谁不知道它已经离开了公司,或者你不知道他们是谁,或者他们在一家公司工作,你无法找到任何联系信息
  3. 您需要的信息在 2000 页的 PDF 中,您不知道从哪里开始查找

  
3. 很难获得程序内部状态的信息
即使您大致了解正在使用的系统并且可以重现该bug错误,但如果您无法在bug错误发生时获得有关程序内部状态的足够信息,则调试几乎是不可能的。
以下是一些难以获取有关程序内部状态的数据的具体原因。
  • 根本没有输出

您的程序失败了,但根本没有任何输出可以告诉您失败的原因。甚至没有错误信息!它只是没有用。
这在我身上发生过,因为操作系统错误——我的玩具操作系统没有启动,因为它在我有任何打印输出的方式之前就失败了,我不知道是错的——它只是不起作用!
  • 输出太多了

也很容易淹没在过多的输出中——我打开了调试输出,然后完全被有多少信息淹没了。很难说在一百万条日志行中什么是相关的,什么是不相关的!
  • 有关该错误的信息分散在许多地方

在调查分布式系统错误时,与错误相关的日志行通常分布在一堆不同的服务中。有时,您无法使用请求 ID 轻松找出服务 A 中的哪些日志行对应于您在服务 B 中看到的异常。
因此,您最终会花费很长时间手动盯着日志并试图将它们关联起来。我在这方面花费的时间比我希望的要多:)
  • 无法使用调试器/添加打印语句

例如,如果您想了解数据库的状态(如 Postgres),您绝对不会将调试器附加到您的生产数据库,并且您可能不想重新编译它以添加额外的日志信息. (虽然我确实重新编译了程序只是为了添加我需要的额外日志信息!)
因此,您需要依赖程序现有的日志记录机制,并希望它们拥有您需要的信息。
  • 当您使用调试器时,错误就会消失

调试器可以导致错误消失的另一个原因是它是否是竞争条件——调试器通常会使程序运行得慢一点,这可能导致竞争不会发生。
 
4. 你的假设之一是错误的
例如,在几乎所有情况下,都可以假设编译器没有错误并且错误在您的代码中。但正如 Twitter 上的某人指出的那样,很少是编译器错误!(这是他们遇到的编译器错误
其他(更平凡的)假设可能是错误的例子:
  1. 假设您的新代码正在运行,而实际上正在缓存某些内容
  2. 假设设置了一些环境变量而不是
  3. 假设错误在软件中,而它在硬件中(就像一根坏电缆!)
  4. 假设文档是正确的

让我们回顾一下“你的一个假设是错误的”的几个变体。
  • 红鲱鱼

有时,您在调试的早期看到一些看起来非常可疑的东西,并花了很长时间调查它,但后来证明它与错误完全无关。这是很正常的,通常并不意味着你做错了什么(你不能每次都采取最有效的方式来理解错误!)。但这真的很令人沮丧。
  • 有效的情况和无效的情况看起来完全一样

发生这种情况时非常令人沮丧 - 您 100% 确定没有任何更改,但不知何故代码不再工作!(当然,答案是有些东​​西确实发生了变化,只是你看不到)
这方面的几个例子。
  1. 一个输入导致您的代码中断,但它在一堆其他输入上成功,您无法弄清楚使代码中断的输入有何不同
  2. 有一个错字,你的大脑只是拒绝注意到
  3. 一个非常小的代码更改导致了一个错误,你真的认为它不应该有任何区别
  4. 完全相同的代码在相同的输入上运行,但是有一些外部因素导致了您没有考虑过的错误(例如磁盘上的文件或环境变量)

我们将讨论的最后一种类型是非常复杂的错误!
 
5.bug真的很复杂
我想把这个分开,因为很多很难理解的错误最终其实很简单!由于上述一些原因,它们很难理解(错误的假设!你不了解系统!很难观察程序的状态!)。
但是有些错误确实非常复杂。这个的几个变种:
  • 代码很复杂

推特的一个例子
对系统行为的太多、广泛和未知的影响。例如,多继承在库中横行
  • 当你谷歌时,错误信息有 0 个结果

这并不总是意味着错误很复杂,但是当有 0 个结果时令人震惊,或者有 1 个结果并且它是......库的源代码,或者论坛上的 1 个悲伤的人发布了关于您的确切错误但没有回复的人。(“哦,不,以前没有人遇到过这个错误吗?!?!”)
  • 一个bug实际上是 3 个bug错误

对于大多数错误,只有一件事出了问题——系统中的一切都在正常工作,除了 1 件事,您只需要确定导致问题的 1 件事。
当多个事情同时被破坏时,这会困难得多——也许你的程序中有一个错误,你正在使用的库中也有一个错误,以及你的负载平衡器的一些意外行为。
一个常见的例子是安全漏洞——它们通常涉及非常复杂的错误,即使您弄清楚到底发生了什么,也需要很长时间来解释和理解。
 
其中许多可以同时发生
我正在和我的搭档聊天,讨论工作中的一个绩效问题,他们花了几个月的时间才诊断出来。这很有挑战性,因为:
  • 它是间歇性的(仅在有大量交通时发生)
  • 它只发生在生产中
  • 他们无法直接访问发生这种情况的系统(由供应商管理)
  • 它涉及一个他们以前不知道存在的 Linux 内核系统

他们想通了,但是因为困难的事情太多,所以花了很多时间!