什么是schrödinbug?


52

这个维基页面告诉:

schrödinbug是一个错误,只有在有人阅读源代码或以异常方式使用该程序后,该错误才显现出来,而该错误最初并没有起作用,这时该程序立即停止为每个人工作,直到修复。术语表补充说:“尽管……这听起来不可能,但它确实发生了;有些程序已经隐匿了schrödinbugs多年了。”

谈论的内容非常模糊。

有人可以提供一个例子说明schrödinbug的情况吗(例如在虚构的/真实的情况下)?


15
请注意,报价是开玩笑地讲的。

11
如果您了解Shrodinger的猫,我认为您最好了解shrodinbug:en.wikipedia.org/wiki/Shrodingers_cat
Eimantas 2011年

1
@Eimantas我实际上现在更加困惑,但这是一篇有趣的文章:)

Answers:


82

以我的经验,模式是这样的:

  • 系统工作多年
  • 报告一个错误
  • 开发人员调查了该错误,并发现了一些似乎完全有缺陷的代码,并声明该代码“永远无法工作”
  • 错误已修复,本来可以工作(但已使用多年)的代码图例不断增长

让我们在这里合乎逻辑。永远都行不通的代码... 永远行不通的代码。如果确实有效,则该语句为假。

因此,我要说的是,完全按照所描述的错误(即观察有缺陷的代码使它停止工作)显然是胡说八道。

实际上,发生的事情是两件事之一:

1)开发人员尚未完全理解代码。在这种情况下,代码通常是一团糟,并且其中的某个地方对某些外部条件具有主要但非显而易见的敏感性(例如,特定的OS版本或配置以某种较小但重要的方式控制某些功能的工作方式)。更改了这种外部条件(例如,通过认为服务器升级或更改不相关),从而导致代码中断。

然后,开发人员查看代码,而不是了解历史背景或没有时间来追踪每种可能的依赖关系和方案,而是声明它永远无法工作并重写。

在这种情况下,这里要理解的是,“它永远不会起作用”的想法被证明是错误的(因为它确实起作用了)。

并不是说重写是一件坏事-常常不是,尽管很高兴确切地知道哪里出了错,这很耗时,并且重写代码段通常更快,并且可以确保已解决问题。

2)实际上它从来没有奏效,只是没有人注意到。这是令人惊讶的普遍现象,尤其是在大型系统中。在这种情况下,某人以一种前所未有的方式开始和开始看待事物,或者业务流程发生了变化,从而使以前的次要案例进入了主流程,而某些事情却没有真正起作用(或起作用了,但并非全部起作用)时间)找到并报告。

开发人员查看它并声明“它永远不可能成功”,但用户说“胡说八道,我们已经使用了很多年”,他们是对的,但是他们认为无关紧要(而且通常直到开发人员找到了他们到达的确切条件“哦,是的,我们现在就这样做了以前没有做过”)。

在这里,开发人员是对的-它永远不会起作用,也永远不会起作用。

但是,无论哪种情况,以下两项都是正确的:

  • 声称“它永远不可能成功”的说法是正确的,也从未成功过-人们只是认为它确实有效
  • 它确实有效,并且“不可能成功”的陈述是错误的,并且导致(通常是合理的)缺乏对代码及其依赖关系的理解

1
如此频繁地发生在我身上
发生

2
洞察现实主义这些情况
StuperUser

1
我想这通常是“ WTF”时刻的结果。我曾经有一次。我重新阅读了一些编写的代码,并意识到最近发现的一个错误应该会使整个应用程序崩溃。实际上,在进一步检查之后,我编写的另一个组件是如此出色,足以弥补错误。
Thaddee Tyl 2011年

1
@Thaddee-我之前已经看过,但是我也看到代码模块中的两个错误,它们互相调用以互相抵消,因此它确实起作用。看看其中一个,它们都坏了,但在一起很好。
乔恩·霍普金斯

7
@乔恩·霍普金斯(Jon Hopkins):我还遇到了两个互相抵消的bug,这真是令人惊讶。我发现了一个错误,,口臭名昭著的声明“它永远不可能成功”,更深入地寻找了它仍然起作用的原因,并且发现了至少在大多数情况下可以纠正第一个错误的另一个错误。这个发现真的让我感到震惊,而且发现只有一个错误,后果将是灾难性的!
Alexis Dufrenoy

54

由于每个人都提到了本不应该使用的代码,因此,大约在8年前,一个濒临死亡的VB3项目正在转换为.net,我将举一个例子。不幸的是,该项目必须保持最新状态,直到.net版本完成-我是唯一一个甚至可以远程理解VB3的人。

有一个非常重要的功能,每次计算都会调用数百次-它计算了长期养老金计划的每月利息。我将重现有趣的部分。

Function CalculateMonthlyInterest([...], IsYearlyInterestMode As Boolean, [...]) As Double
    [about 30 lines of code]
    If IsYearlyInterestMode Then
        [about 30 lines of code]
        If Not IsYearlyInterestMode Then
            [about 30 lines of code (*)]
        End If
    End If
End Function

标有星号的部分具有最重要的代码;这是进行实际计算的唯一部分。显然,这永远都不应该起作用,对吗?

进行了大量的调试,但最终我找到了原因:IsYearlyInterestModeTrueNot IsYearlyInterestMode这也是事实。这是因为有人沿线将其转换为整数,然后在应该将其设置为true的函数中将其递增(如果False将其设置为0,则将其设置为1,即VB True,所以我可以看到逻辑在那里),然后将其转换回布尔值。我的处境永远都不可能发生,却一直发生。


7
结语:我从未修复过该功能。我刚刚修补了失败的呼叫站点,以便像其他所有站点一样发送2个。
配置器

所以您的意思是当人们误解代码时使用它吗?
Pacerier,2011年

1
@Pacerier:更多情况是当代码混乱时,它只能偶然地正常工作。在我的示例中,没有开发人员打算IsYearlyInterestMode将评估结果视为“是”和“不是”。最初的开发人员添加了几行(包括其中之一)if实际上并不了解其工作原理-它恰好可以正常工作,因此足够好
configurator

16

不知道真实的示例,而是使用示例情况进行简化:

  • 暂时没有发现错误,因为该应用程序不会在导致其失败的条件下运行代码。
  • 有人通过做一些超出正常使用范围(或检查源代码)的操作来注意到它。
  • 既然已经注意到了该错误,那么该应用程序也将在正常情况下失败,直到该错误得到修复。

之所以会发生这种情况,是因为该错误会破坏应用程序的某些状态,从而导致以前正常情况下的故障。


4
一种解释是该软件存在随机故障,没有人能够在精神上联系在一起。因此,这些错误被认为是自然原因(例如随机硬件故障)。一旦读取了源代码,人们现在就可以将所有先前的随机错误与该原因相关联,并且将意识到,它本来就不应该起作用。
rwong 2011年

4
第二个解释是该软件中有一部分是通过责任链模式实现的。尽管一个处理程序存在严重的错误,但每个处理程序都以健壮的方式编写。现在,第一个处理程序将始终失败,但是由于第二个处理程序(职责重叠)试图​​完成相同的任务,因此总体操作似乎已成功。如果第二个模块有任何更改(例如责任区更改),则将导致整体故障,尽管实际的错误位于其他位置。
rwong 2011年

13

一个真实的例子。我无法显示代码,但是大多数人都会与此相关。

我们有一个很大的内部实用程序函数内部库。有一天,我正在寻找一种功能来完成某件事,然后Frobnicate()尝试使用它。恩:结果Frobnicate()总是返回错误代码。

深入研究实现,我发现一些基本的逻辑错误Frobnicate()使它总是失败。在源代码管理中,我可以看到该函数自编写以来就没有被修改过,这意味着该函数从未按预期工作。为什么没有人注意到这一点?我搜索了源清单的其余部分,发现所有现有的调用者Frobnicate()都忽略了返回值(因此包含了自己的细微错误)。如果我更改这些函数以像应该检查的那样检查返回值,那么它们也将开始失败。

这是乔恩·霍普金斯(Jon Hopkins)在回答中提到的条件2的一种常见情况,在大型内部库中,这种情况令人沮丧。


...这是避免在任何可用的外部库中编写内部库的充分理由。它将经过更多的测试,因此没有太多令人讨厌的惊喜(首选开放源代码库,因为无论如何也可以修复它们)。
2012年

是的,但是如果程序员忽略返回代码,那不是库的错。(顺便说一句,您最后一次检查的printf()
重新编码

这就是发明检查异常的原因。
凯文·克鲁姆维德

10

这是我在某些系统代码中看到的真正的Schrödinbug。根守护程序需要与内核模块进行通信。因此,内核代码创建了一些文件描述符:

int pipeFDs[1];

然后通过将连接到命名管道的管道建立通信:

int pipeResult = pipe(pipeFDs);

不行pipe()将两个文件描述符写入数组,但是其中只有一个空间。但大约七年它的工作; 该数组恰好位于内存中一些未使用的空间之前,被选作文件描述符。

然后,有一天,我不得不将代码移植到新的体系结构上。它停止工作,并且发现了本不应该工作的错误。


5

的必然结果Schrödinbug是Heisenbug -它描述了消失(或偶尔显示)试图调查和/或修复时的错误。

Heisenbugs是神话般的聪明小动物,当加载调试器时会运行并隐藏,但是一旦您停止观看,就会从木制品中消失。

实际上,这些通常似乎是由以下一项或多项引起的:

  • 优化所带来的影响,即使用其编译的代码-DDEBUG被优化到与发行版本不同的级别
  • 由于现实世界中的通信总线或中断与模拟的“完美”虚拟负载有细微的差异,因此存在细微的时序差异

两者都强调了在发布设备上测试发布代码以及使用仿真器进行单元/模块/系统测试的重要性。


为什么我在发布此消息之前没有注意到S.Lote的回答和delnan的评论?
Andrew

我经验不足,但是发现了其中的一些。我在Android NDK环境中工作。当调试器找到断点时,它仅暂停Java线程,而不暂停C ++线程,从而使某些调用成为可能,因为元素是在C ++上初始化的。如果不使用调试器,则Java代码的运行速度将比C ++快,并尝试使用尚未初始化的值。
MLProgrammer-CiM 2013年

几个月前,我在Django数据库API的用法中发现了一个Heisenbug :当时DEBUG = True,原始SQL查询的“参数” arg的名称发生了变化。由于查询的时间过长,我们一直将其用作关键字arg,以使其更加清晰,当需要将其推送到测试版网站时,该查询完全中断了,其中DEBUG = False
Izkata 2014年

2

我见过一些Schödinbugs,并且总是出于相同的原因:

公司政策要求每个人都应该使用程序。
没有人真正使用过它(主要是因为没有对其进行培训。)
但是他们无法告诉管理层。因此,每个人都必须说“我已经使用该程序两年了,直到今天才遇到此错误”。
该程序从未真正起作用,除了少数用户(包括编写该程序的开发人员)。

在一种情况下,该程序已经过大量测试,但未在真实数据库中进行测试(这被认为过于敏感,因此使用了伪造的版本。)


1

我有一个自己的历史例子,大约是25年前。我还是一个在Turbo Pascal中进行基本图形编程的孩子。TP有一个称为BGI的库,其中包含一些函数,这些函数使您可以将屏幕的某个区域复制到基于指针的内存块中,然后在其他位置将其着色。结合在黑白屏幕上进行异或异化处理,可以用来制作简单的动画。

我想更进一步,制作精灵。我编写了一个程序,该程序绘制了大块和控件以对其进行着色,就像您这样做的那样,它将它们复制为像素,从而生成了一个简单的绘制程序来创建精灵,然后可以将其复制到内存中。只有一个问题,要使用这些带斑点的精灵,必须将它们保存到文件中,以便其他程序可以读取它们。但是TP无法序列化基于指针的内存分配。手册完全声明它们无法写入文件。

我想出了一段成功写到文件的代码。并开始编写一个测试程序,该程序在我创建游戏的过程中在背景上从我的绘图程序中清除了一个精灵。而且效果很好。第二天,它停止工作。它只剩下乱七八糟的东西了。它再也无法工作了。我创建了一个新的精灵,它可以完美地工作-直到它失效为止,这又是一个乱码。

花费了很长时间,但最终我弄清楚了发生了什么。就像我想的那样,绘图程序没有将复制的像素数据保存到文件中,而是保存了指针本身。当下一个程序读取文件时,它以指向同一内存块的指针结尾-该内存块仍包含上一个程序已在此处写入的内容(在MS-DOS上,不存在内存管理)。但是它一直有效……直到您重新启动,或者运行了可以重用相同内存区域的任何东西,然后您才出现乱码,因为您正在将一堆完全无关的数据写入视频存储块。

它永远不应该起作用,甚至不应该看起来可以工作(并且在任何实际的操作系统上都不可能),但是它仍然可以工作,一旦崩溃,它就一直崩溃。


0

当人们使用调试器时,就会一直发生这种情况。

调试环境不同于实际的(无调试器)生产环境。

使用调试器运行可能会掩盖堆栈溢出之类的内容,因为调试器的堆栈框架掩盖了该错误。


我不认为这是指调试器中运行的代码与编译时的代码之间的区别。
乔恩·霍普金斯,

26
那不是schrödinbug,而是heisenbug

@delnan:处于边缘,IMO。我发现这是不确定的事情,因为存在着不可知的自由度。我喜欢将heisenbug保留给测量一件事情实际上会干扰另一件事的事情(例如,竞争条件,优化器设置,网络带宽限制等)
S.Lott

@ S.Lott:您描述的情况确实涉及观察到通过弄乱堆叠框架等来改变事物。(我见过的最糟糕的例子是调试器将在单步模式下和平且“正确”地执行无效段寄存器值的加载。结果是,尽管在保护模式下加载了实模式指针,RTL仍保留了一些例程(因为它只是被复制而不是被取消引用,所以它的表现很完美。)
Loren Pechtel

0

我从未见过真正的schrodinbug,我不认为它们可以存在-发现它不会破坏事物。

相反,发生了一些变化,暴露了一个潜伏了很久的错误。所做的任何更改仍会更改,因此该漏洞不断出现,而同时又有人发现了该漏洞。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.