这个维基页面告诉:
schrödinbug是一个错误,只有在有人阅读源代码或以异常方式使用该程序后,该错误才显现出来,而该错误最初并没有起作用,这时该程序立即停止为每个人工作,直到修复。术语表补充说:“尽管……这听起来不可能,但它确实发生了;有些程序已经隐匿了schrödinbugs多年了。”
谈论的内容非常模糊。
有人可以提供一个例子说明schrödinbug的情况吗(例如在虚构的/真实的情况下)?
这个维基页面告诉:
schrödinbug是一个错误,只有在有人阅读源代码或以异常方式使用该程序后,该错误才显现出来,而该错误最初并没有起作用,这时该程序立即停止为每个人工作,直到修复。术语表补充说:“尽管……这听起来不可能,但它确实发生了;有些程序已经隐匿了schrödinbugs多年了。”
谈论的内容非常模糊。
有人可以提供一个例子说明schrödinbug的情况吗(例如在虚构的/真实的情况下)?
Answers:
以我的经验,模式是这样的:
让我们在这里合乎逻辑。永远都行不通的代码... 永远行不通的代码。如果确实有效,则该语句为假。
因此,我要说的是,完全按照所描述的错误(即观察有缺陷的代码使它停止工作)显然是胡说八道。
实际上,发生的事情是两件事之一:
1)开发人员尚未完全理解代码。在这种情况下,代码通常是一团糟,并且其中的某个地方对某些外部条件具有主要但非显而易见的敏感性(例如,特定的OS版本或配置以某种较小但重要的方式控制某些功能的工作方式)。更改了这种外部条件(例如,通过认为服务器升级或更改不相关),从而导致代码中断。
然后,开发人员查看代码,而不是了解历史背景或没有时间来追踪每种可能的依赖关系和方案,而是声明它永远无法工作并重写。
在这种情况下,这里要理解的是,“它永远不会起作用”的想法被证明是错误的(因为它确实起作用了)。
并不是说重写是一件坏事-常常不是,尽管很高兴确切地知道哪里出了错,这很耗时,并且重写代码段通常更快,并且可以确保已解决问题。
2)实际上它从来没有奏效,只是没有人注意到。这是令人惊讶的普遍现象,尤其是在大型系统中。在这种情况下,某人以一种前所未有的方式开始和开始看待事物,或者业务流程发生了变化,从而使以前的次要案例进入了主流程,而某些事情却没有真正起作用(或起作用了,但并非全部起作用)时间)找到并报告。
开发人员查看它并声明“它永远不可能成功”,但用户说“胡说八道,我们已经使用了很多年”,他们是对的,但是他们认为无关紧要(而且通常直到开发人员找到了他们到达的确切条件“哦,是的,我们现在就这样做了,以前没有做过”)。
在这里,开发人员是对的-它永远不会起作用,也永远不会起作用。
但是,无论哪种情况,以下两项都是正确的:
由于每个人都提到了本不应该使用的代码,因此,大约在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
标有星号的部分具有最重要的代码;这是进行实际计算的唯一部分。显然,这永远都不应该起作用,对吗?
进行了大量的调试,但最终我找到了原因:IsYearlyInterestMode
是True
,Not IsYearlyInterestMode
这也是事实。这是因为有人沿线将其转换为整数,然后在应该将其设置为true的函数中将其递增(如果False
将其设置为0,则将其设置为1,即VB True
,所以我可以看到逻辑在那里),然后将其转换回布尔值。我的处境永远都不可能发生,却一直发生。
IsYearlyInterestMode
将评估结果视为“是”和“不是”。最初的开发人员添加了几行(包括其中之一)if
实际上并不了解其工作原理-它恰好可以正常工作,因此足够好
不知道真实的示例,而是使用示例情况进行简化:
之所以会发生这种情况,是因为该错误会破坏应用程序的某些状态,从而导致以前正常情况下的故障。
一个真实的例子。我无法显示代码,但是大多数人都会与此相关。
我们有一个很大的内部实用程序函数内部库。有一天,我正在寻找一种功能来完成某件事,然后Frobnicate()
尝试使用它。恩:结果Frobnicate()
总是返回错误代码。
深入研究实现,我发现一些基本的逻辑错误Frobnicate()
使它总是失败。在源代码管理中,我可以看到该函数自编写以来就没有被修改过,这意味着该函数从未按预期工作。为什么没有人注意到这一点?我搜索了源清单的其余部分,发现所有现有的调用者Frobnicate()
都忽略了返回值(因此包含了自己的细微错误)。如果我更改这些函数以像应该检查的那样检查返回值,那么它们也将开始失败。
这是乔恩·霍普金斯(Jon Hopkins)在回答中提到的条件2的一种常见情况,在大型内部库中,这种情况令人沮丧。
printf()
这是我在某些系统代码中看到的真正的Schrödinbug。根守护程序需要与内核模块进行通信。因此,内核代码创建了一些文件描述符:
int pipeFDs[1];
然后通过将连接到命名管道的管道建立通信:
int pipeResult = pipe(pipeFDs);
这不行。pipe()
将两个文件描述符写入数组,但是其中只有一个空间。但大约七年它做的工作; 该数组恰好位于内存中一些未使用的空间之前,被选作文件描述符。
然后,有一天,我不得不将代码移植到新的体系结构上。它停止工作,并且发现了本不应该工作的错误。
的必然结果Schrödinbug是Heisenbug -它描述了消失(或偶尔显示)试图调查和/或修复时的错误。
Heisenbugs是神话般的聪明小动物,当加载调试器时会运行并隐藏,但是一旦您停止观看,就会从木制品中消失。
实际上,这些通常似乎是由以下一项或多项引起的:
-DDEBUG
被优化到与发行版本不同的级别两者都强调了在发布设备上测试发布代码以及使用仿真器进行单元/模块/系统测试的重要性。
DEBUG = True
,原始SQL查询的“参数” arg的名称发生了变化。由于查询的时间过长,我们一直将其用作关键字arg,以使其更加清晰,当需要将其推送到测试版网站时,该查询完全中断了,其中DEBUG = False
我有一个自己的历史例子,大约是25年前。我还是一个在Turbo Pascal中进行基本图形编程的孩子。TP有一个称为BGI的库,其中包含一些函数,这些函数使您可以将屏幕的某个区域复制到基于指针的内存块中,然后在其他位置将其着色。结合在黑白屏幕上进行异或异化处理,可以用来制作简单的动画。
我想更进一步,制作精灵。我编写了一个程序,该程序绘制了大块和控件以对其进行着色,就像您这样做的那样,它将它们复制为像素,从而生成了一个简单的绘制程序来创建精灵,然后可以将其复制到内存中。只有一个问题,要使用这些带斑点的精灵,必须将它们保存到文件中,以便其他程序可以读取它们。但是TP无法序列化基于指针的内存分配。手册完全声明它们无法写入文件。
我想出了一段成功写到文件的代码。并开始编写一个测试程序,该程序在我创建游戏的过程中在背景上从我的绘图程序中清除了一个精灵。而且效果很好。第二天,它停止工作。它只剩下乱七八糟的东西了。它再也无法工作了。我创建了一个新的精灵,它可以完美地工作-直到它失效为止,这又是一个乱码。
花费了很长时间,但最终我弄清楚了发生了什么。就像我想的那样,绘图程序没有将复制的像素数据保存到文件中,而是保存了指针本身。当下一个程序读取文件时,它以指向同一内存块的指针结尾-该内存块仍包含上一个程序已在此处写入的内容(在MS-DOS上,不存在内存管理)。但是它一直有效……直到您重新启动,或者运行了可以重用相同内存区域的任何东西,然后您才出现乱码,因为您正在将一堆完全无关的数据写入视频存储块。
它永远不应该起作用,甚至不应该看起来可以工作(并且在任何实际的操作系统上都不可能),但是它仍然可以工作,一旦崩溃,它就一直崩溃。
当人们使用调试器时,就会一直发生这种情况。
调试环境不同于实际的(无调试器)生产环境。
使用调试器运行可能会掩盖堆栈溢出之类的内容,因为调试器的堆栈框架掩盖了该错误。
我从未见过真正的schrodinbug,我不认为它们可以存在-发现它不会破坏事物。
相反,发生了一些变化,暴露了一个潜伏了很久的错误。所做的任何更改仍会更改,因此该漏洞不断出现,而同时又有人发现了该漏洞。