你如何修复一个无法复制的错误?

82

问题已经说得很清楚了。如果你有一个多个用户报告的错误,但在日志中没有发现该错误的记录,而且无论你如何努力都无法重复出这个错误,你该怎么修复它呢?甚至,你是否能够修复它?

我相信许多人都遇到过这种情况。在这种情况下,你会怎么做,最终结果是什么?


编辑: 我更关心的是对于一个找不到的错误,应该采取什么措施,而不是一个无法解决的错误。无法解决的错误至少表明存在问题,并且在大多数情况下有一个起点来搜索问题。而对于一个找不到的错误,你该怎么办?你还能做些什么吗?


1
https://dev59.com/GXVC5IYBdhLWcg3w4Vb6 使用了“解决(resolve)”一词,但问题显然是指无法重现的问题。 - John Saunders
1
普遍认为,无漏洞的软件实际上是难以实现的(至少在可能小型、可数学证明的算法之外),并不是说有人希望软件中存在漏洞。其余的观点是,对于来自项目经理的不切实际的期望,我们没有太多可以做的。 - MatthewMartin
15个回答

90

语言

不同的编程语言会有它们自己的bug。

C

添加调试语句可能会使问题变得不可能复制,因为调试语句本身会将指针移动足够远以避免SEGFAULT,也称为Heisenbugs。指针问题很难跟踪和复制,但调试器可以帮助(例如GDBDDD)。

Java

具有多个线程的应用程序可能只在非常特定的时间或事件序列中显示其错误。不正确的并发实现可能会导致死锁,在难以复制的情况下出现。

JavaScript

某些Web浏览器以内存泄漏而闻名。在一个浏览器中运行良好的JavaScript代码可能会导致在另一个浏览器中产生错误行为。使用经过数千个用户严格测试的第三方库可以避免某些晦涩的错误。

环境

根据应用程序(存在错误的应用程序)运行的环境的复杂性,唯一的解决方法可能是简化环境。应用程序是在以下环境中运行的:

  • 服务器上?
  • 台式机上?
  • 在Web浏览器中?

应用程序在哪个环境中产生问题?

  • 开发?
  • 测试?
  • 生产?

退出多余的应用程序,关闭后台任务,停止所有计划事件(cron作业),删除插件并卸载浏览器附加组件。

网络

由于网络对许多应用程序至关重要:

  • 确保稳定的网络连接,包括无线信号。
  • 软件是否能够在网络故障后重新连接?
  • 是否正确关闭所有连接以释放文件描述符?
  • 是否有不该使用机器的人?
  • 是否有恶意设备与机器的网络交互?
  • 是否有附近的工厂或无线电塔会导致干扰?
  • 数据包大小和频率是否在正常范围内?
  • 数据包是否被监视以检测丢失
  • 所有网络设备是否足够支持高带宽使用?

一致性

尽可能消除未知因素:

  • 隔离架构组件。
  • 删除非必要或可能存在冲突的元素。
  • 停用不同的应用程序模块。

消除生产、测试和开发之间的所有差异。使用相同的硬件。完全按照相同的步骤设置计算机。一致性至关重要。

日志

大量使用日志记录以关联事件发生的时间。检查日志是否存在任何明显的错误、时序问题等。

硬件

如果软件看起来没问题,请考虑硬件故障:

  • 物理网络连接是否牢固?
  • 有没有松动的电缆?
  • 芯片是否正确安装?
  • 所有电缆是否连接干净?
  • 工作环境是否清洁且无尘?
  • 任何隐藏的设备或电缆是否被啮齿动物或昆虫损坏?
  • 驱动器上是否存在坏块?
  • CPU风扇是否正常工作?
  • 主板是否能够为所有组件(CPU、网络卡、视频卡、驱动器等)供电?
  • 电磁干扰是否可能是问题的根源?

而对于嵌入式系统:

  • 供应不足绕过?
  • 板污染?
  • 坏的焊点/热流焊不良?
  • 当电源电压超出公差时,CPU未复位?
  • 由于供电电路从I/O端口反向供电并且不能完全放电,导致重置错误?
  • 抓扭现象?
  • 浮动输入引脚?
  • 逻辑电平上不足(有时为负)的噪声裕度?
  • 时间裕度不足(有时为负)?
  • 锡须
  • ESD损坏?
  • ESD干扰?
  • 芯片勘误?
  • 接口误用(例如I2C离板或在高功率信号存在的情况下)?
  • 竞争条件?
  • 假冒零件?

网络与本地

在本地运行应用程序(即非跨网络)会发生什么?其他服务器是否遇到相同的问题?数据库是否远程?可以使用本地数据库吗?

固件

硬件和软件之间是固件。

  • 计算机BIOS是否更新?
  • BIOS电池是否工作?
  • BIOS时钟和系统时钟是否同步?

时间和统计数据

时间问题很难跟踪:

  • 问题发生的时间是什么时候?
  • 频率如何?
  • 在此期间运行了哪些其他系统?
  • 应用程序是否具有时间敏感性(例如,闰日或闰秒会导致问题)?

收集有关问题的硬数据。一开始可能看起来是随机的问题,实际上可能有一个模式。

变更管理

有时,在系统升级后出现问题。

  • 问题最初是什么时候开始的?
  • 环境中有什么变化(硬件和软件)?
  • 回滚到以前版本后会发生什么情况?
  • 有哪些问题版本与好版本之间存在差异?

库管理

不同的操作系统有不同的解决冲突库的方法:

  • Windows 有 DLL Hell
  • Unix 可能有许多损坏的符号链接。
  • Java 库文件也可能很难解决。

执行操作系统的全新安装,并只包括应用程序所需的支持软件。

Java

确保每个库仅使用一次。有时应用程序容器与应用程序本身的库版本不同。这可能无法在开发环境中复制。

使用诸如 MavenIvy 的库管理工具。

调试

编写一个检测方法,当出现错误时触发通知(例如日志、电子邮件、弹出窗口、寻呼机哔声)。使用自动化测试向应用程序提交数据。使用随机数据。使用涵盖已知和可能的边缘情况的数据。最终,错误应该再次出现。

睡眠

值得重申的是,像其他人提到的那样:好好睡一觉吧。远离问题,完成其他任务(如文档编写)。远离电脑,进行一些锻炼。

代码审查

逐行遍历代码,并描述每行代码的作用,可以对自己、同事或橡皮鸭进行说明。这可能会带来如何复现错误的见解。

宇宙辐射

宇宙射线可以翻转位。由于现代内存错误检查的存在,这不再是过去的大问题。离开地球保护的硬件软件存在无法复制的问题,因为宇宙辐射具有随机性。

工具

有时,尤其是对于小众工具(例如C微控制器编译器遭受符号表溢出),编译器会引入错误。是否可以使用不同的编译器?工具链中的其他任何工具是否会引入问题?


15

如果它是一个GUI应用程序,观察客户生成错误的过程(或者尝试生成错误)会非常宝贵。他们肯定会做一些你从未想过的事情(不是错误地做,而是以不同的方式)。

否则,将日志集中在那个区域。记录大部分内容(稍后可以提取),并让您的应用程序转储其环境,例如机器类型、VM类型和使用的编码。

您的应用程序是否报告版本号、构建号等?这样可以确定您正在调试的确切版本(或没有!)。

如果可以为您的应用程序添加工具(例如在Java世界中使用JMX),则应该在相关区域中添加探针。存储统计信息,例如请求+参数、时间戳等。使用缓冲区存储最后的“ n”个请求/响应/对象版本/任何内容,当用户报告问题时转储它们。


12

如果你无法复制它,那么你可能可以修复它,但是无法知道你是否已经解决了问题。

我已经尽力解释了出现错误的原因(即使我不知道那种情况怎么会发生),修复了错误,并确保如果该错误再次出现,我们的通知机制将让未来的开发人员了解我希望知道的信息。实际上,这意味着在跨越可能引发错误的路径时添加日志事件,并记录相关资源的度量。当然,还要确保测试代码能够全面运行。

决定添加哪些通知是可行性和分类问题。决定在首次遇到该错误时要花费多少开发时间也是如此。这需要知道该错误有多重要才能回答。

我有过好的结果(错误没有再次出现,代码也变得更好了),也有过坏的结果(花费太多时间而没有解决问题,无论错误是否最终得到解决)。这就是估算和问题优先级的用途所在。


3
我曾经有过这样的经历。我成功地定位到了代码中错误的具体位置并进行修复,但事实证明修复并不彻底,因为在代码另一个距离它500行的位置还存在一个同样的错误。 - Joshua
1
这是确切的事实,这意味着您需要知道如何复制它。 - paul

10
有时候我只能坐下来研究代码,直到找到错误。尝试证明这个错误是不可能的,在这个过程中,你可能会发现自己哪里出了错。如果你真的成功地让自己相信它是不可能的,那么就假设你在某个地方搞砸了。
添加一堆错误检查和断言可能有所帮助,以确认或否认你的信念/假设。有些东西可能会失败,而你却从未想到会失败。

7

有时候,找到并修复bug可能会很困难,甚至几乎不可能。但我的经验是,只要你花足够的时间(是否值得花这么多时间则是另一回事),迟早能够找到并解决它。

以下是一些通用建议,希望能对您解决问题有所帮助:

  • 如果可能的话,请添加更多日志记录,以便下次出现bug时您可以获得更多数据。
  • 向用户询问是否能够重现该bug。如果可以,您可以让他们在您的监督下重现它,并希望找出触发该bug的原因。

是的 - 就在10分钟前,我接到了一个电话,看起来有人终于成功复现了一个我多年来一直怀疑但不确定的错误。这是一个每年可能出现两次的问题,从未能够被复现,观察到的行为总是可能是由于过去静默加载的价格变化所致。 - Loren Pechtel
增加更多日志记录。这是正确的答案。如果您有权修复错误,那么您可能也有权使代码自行报告其错误。我见过开发人员变得害怕进行重大代码更改,好像他们永远被继承的无法诊断的代码所困扰。如果代码默默失败,则该沉默是您需要解决的第一个错误。作为开发人员,您是相关利益相关者。 - durette

6

思考。深入思考。关起门来,不要接受打扰。

我曾经遇到一个错误,证据是一个损坏数据库的十六进制转储。指针链被系统地搞乱了。用户的所有程序和我们的数据库软件在测试中都能正常工作。我凝视着它一个星期(那是一个重要的客户),排除了数十种可能的想法后,我意识到数据分布在两个物理文件中,而损坏发生在链穿越文件边界的位置。我意识到如果备份/还原操作在关键点失败,那么两个文件可能会“失去同步”,恢复到不同的时间点。如果你在已经损坏的数据上运行客户的程序之一,它会产生我看到的纠缠的指针链。然后,我演示了一系列可以精确重现损坏的事件。


6

尝试随机更改直到有所作为 :-)


这是正确的做法:D - Christian X

6
假设您已经添加了所有您认为有助于解决问题的日志,但仍然无法解决问题...两件事情需要考虑:
  1. 从报告的症状开始逆向思考。想想自己...“如果我想产生报告的症状,我需要执行哪些代码,我该如何执行并到达那里?” D导致C,C导致B,B导致A。接受一个事实:如果一个错误无法重现,那么通常的方法是无法帮助的。我曾经花费很多时间盯着代码进行这种思考过程来查找一些错误。通常情况下,结果证明是某些非常愚蠢的东西。

  2. 记住Bob的第一条调试定律:如果您找不到某个东西,那是因为您正在错误的地方寻找:-)


3

讨论问题,阅读代码,通常是大量的代码。通常我们会成对进行,因为你通常可以通过分析快速消除可能性。


3

在您认为问题出现的代码处进行修改,以便在某个地方记录额外的调试信息。下次出现问题时,您将拥有解决问题所需的一切。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接