你如何重现零星发生的错误?

我们的应用程序中有一个错误,每次都不会发生,因此我们不知道它的“逻辑”。 我今天甚至没有得到它的转载。

免责声明:这个错误存在,我已经看到了。 这不是一个pebkac或类似的东西。

什么是常见的提示来重现这种错误?

分析一对中的问题并配对阅读代码。 记下你所知道的问题是正确的,试图断言哪些逻辑先决条件必须适用于这种情况。 像CSI一样跟踪证据。

大多数人本能地说“增加日志”,这可能是一个解决scheme。 但是对于很多问题,这只会让事情变得更糟,因为日志logging可以充分改变时序依赖性,使问题更频繁或更less。 将频率从千分之一改变为百分之一,不会使您更接近问题的真正根源。

所以,如果你的逻辑推理不能解决问题,它可能会给你一些你可以用代码中的日志logging或断言进行调查的细节。

添加某种日志或跟踪。 例如,logging导致错误之前用户提交的最后一个X操作(只有当您可以设置条件来匹配错误时)。

这个问题没有一个好的答案,但是我发现:

  1. 这种事情需要人才。 不是所有的开发者都适合,即使他们是其他领域的超级巨星。 所以,了解你的团队,有一个天赋,希望你能给他们足够的糖果,让他们兴奋的帮助你,即使这不是他们的领域。

  2. 倒退,把它当作科学调查。 从错误开始,你看到的是错误的。 对可能导致的原因进行假设(这是富有创造性/富有想象力的部分,并非每个人都有才能的艺术),而且知道代码的工作原理也很有帮助。 对于每个假设(最好按照你认为最有可能的方式sorting – 这里又是纯粹的直觉),开发一个试图消除它作为原因的testing,并检验假设。 任何未能达到预测的情况并不意味着这个假设是错误的。 testing这个假设,直到它被证实是错误的(虽然因为它不太可能,你可能想先移到另一个假设,只是不打折这个,直到你有明确的失败)。

  3. 在此过程中收集尽可能多的数据。 广泛的日志logging和其他任何适用的。 不要因为缺乏数据而忽略一个假设,而是弥补缺乏数据。 对于正确的假设来说,灵感常常来自检查数据。 注意堆栈跟踪中的某些内容,日志中的奇怪问题,缺less应该在数据库中的东西等等。

  4. 仔细检查每个假设。 很多时候我看到一个问题不能很快得到解决,因为一些普通的方法调用没有进一步调查,所以这个问题只是被认为是不适用的。 “哦,那应该很简单。” (见第1点)。

如果您没有使用假设,那通常是由于系统知识不足造成的(即使您自己编写每行代码也是如此),您需要仔细检查代码并获得对系统的更多了解提出一个新的想法。

当然,以上都没有保证什么,但是我发现的方法是一致的。

程序员通常不能重复用户经历的崩溃,仅仅是因为你已经开发了一定的工作stream程和使用应用程序的习惯。

在这个1/100的频率下,我要说的第一件事就是处理exception并在任何地方logging任何东西,否则你可能会花一周的时间来search这个bug。 还要在项目中列出潜在的敏感expression和特征。 例如:1 – multithreading2 – 通用指针/松散数组3 – 依赖于input设备等。这将帮助您按照其他海报的build议,对可以蛮力或再次破解的区域进行分割。

既然这是语言不可知的,我会提到几个debugging的公理。

没有一台电脑是随机的。 “随机发生”表示尚未发现的模式。 debugging从隔离模式开始。 改变个别元素并评估是什么改变了错误的行为。

不同的用户,同一台电脑? 同一个用户,不同的电脑? 这种情况是否强烈周期? 重新启动会改变周期吗?

仅供参考 – 我曾经看到过一个人遇到的错误。 我的意思是人,而不是一个用户帐户。 用户A永远不会在自己的系统上看到问题,用户B将坐在该工作站上,以用户A身份login,并可以立即重现该错误。 应用程序不应该知道椅子上的身体差异。 然而-

用户以不同的方式使用应用程序。 用户A习惯性地使用热键来调用动作,而用户B使用屏幕上的控制。 用户行为的差异会在稍后的几个动作中级联成可见的错误。

即使没有任何意义,也应该调查影响错误行为的任何差异。

有一个很好的机会,你的应用程序是MTWIDNTBMT(multithreading,当它不需要multithreading),或者也许只是multithreading(有礼貌)。 在multithreading应用程序中重现零星错误的一个好方法就是像(C#)那样散布这样的代码:

Random rnd = new Random(); System.Threading.Thread.Sleep(rnd.Next(2000)); 

和/或这个:

 for (int i = 0; i < 4000000000; i++) { // tight loop } 

模拟线程完成他们的任务在不同的时间比平常或捆绑处理器长期延伸。

多年来,我inheritance了许多bug,multithreading的应用程序,像上面的代码一样,通常会使零星错误发生得更频繁。

添加详细的日志logging。 这将需要多次(有时甚至是十几次)迭代来添加足够的日志logging来理解场景。 现在的问题是,如果问题是一个竞争条件,如果它不能可靠地重现,那么logging就会改变计时,问题就会停止。 在这种情况下,请不要login到文件,而是将日志的旋转缓冲区保留在内存中,并且只有在检测到发生问题时才将其转储到磁盘上。

编辑:多一点想法:如果这是一个GUI应用程序运行testing与QA自动化工具,它允许您重播macros。 如果这是一个服务types的应用程序,尝试至less猜测发生了什么,然后以编程方式创build“怪胎”使用模式,这将运行您怀疑的代码。 创build高于平常的负载等

什么发展环境? 对于C ++,您最好的select可能是VMWare Workstationlogging/重播,请参阅: http : //stackframe.blogspot.com/2007/04/workstation-60-and-death-of.html

其他build议包括检查堆栈跟踪,仔细的代码概述…真的没有银子弹:)

尝试在您的应用程序中添加代码,以便在发生错误时自动追踪错误(甚至通过邮件/短信提醒您)

logging任何你能做到的事情,当它发生时,你可以捕捉到正确的系统状态。

另一件事 – 尝试应用自动化testing,可以覆盖更多的领土比以人为本的testing形成的方式..这是一个远射,但一般的良好做法。

所有这些,再加上一些蛮力的软机器人,它是半随机的,并通过代码大量的断言/validation(c / c ++,可能类似于其他的langs)

logging和仔细的代码审查吨是唯一的select。

如果应用程序已部署,而且无法调整日志logging,则这可能会特别痛苦。 在那个时候,你唯一的select就是用细齿梳子通过代码,并试图推断程序如何进入坏的状态(科学的方法来拯救!)

通常这些错误与损坏的内存有关,因此它们可能不会经常出现。 你应该尝试使用某种内存分析器(例如valgrind)来运行你的软件,以查看是否出现问题。

比方说,我从一个生产应用程序开始。

  1. 我通常在我认为发生错误的地方添加debugging日志logging。 我设置了日志语句,让我深入了解应用程序的状态。 然后打开debugging日志级别,并要求用户/操作员通知下一次错误发生的时间。 然后我分析日志,看看它给出的应用程序状态的提示,以及是否能够更好地理解可能出现的问题。

  2. 我重复第1步,直到我可以开始在debugging器中debugging代码的位置

  3. 有时运行代码的迭代次数是关键,但有时候也可能是组件与外部系统(数据库,特定用户机器,操作系统等)的交互。 花一些时间设置一个尽可能与生产环境相匹配的debugging环境。 VM技术是解决这个问题的好工具。

  4. 接下来,我通过debugging器进行。 这可能包括创build某种testing工具,将代码/组件置于我从日志中观察到的状态。 知道如何设置条件断点可以节省很多时间,所以熟悉debugging器中的这些function和其他function。

  5. debugging,debugging,debugging。 如果几个小时之后你还没有去,那就rest一下,去做一些与时间无关的事情。 回来一个新的思想和观点。

  6. 如果你现在已经无处可去了,回到步骤1再做一次迭代。

  7. 对于真正困难的问题,您可能不得不求助于在发生错误的系统上安装debugging器。 这与步骤4中的testing工具相结合通常可以解决真正令人困惑的问题。

unit testing。 testing应用程序中的错误往往是可怕的,因为有这么多的噪音,如此多的可变因素。 一般来说,(干草)堆栈越大,查明问题越困难。 创造性地扩展您的unit testing框架以包含边缘案例可以节省数小时甚至数天的筛选时间

说了没有银弹。 我感到你的痛苦。

在与此错误相关的方法中添加前后条件检查。

你可以看看合同devise

随着很多的耐心,一个安静的祈祷和诅咒,你将需要:

  • 一个logging用户操作的好机制
  • 在用户执行某些操作(应用程序状态,数据库等)时收集数据状态的良好机制,
  • 检查服务器环境(例如,在特定时间运行的防病毒软件等)并logging错误的时间并查看是否可以find任何趋势
  • 一些更多的祈祷和诅咒…

HTH。

假设你在Windows上,并且你的“bug”是非托pipe代码(C / C ++)中的崩溃或某种损坏,那么请看看Microsoft的Application Verifier 。 该工具有多个停止,可以启用在运行时validation的东西。 如果您对发生错误的场景有所了解,那么在运行AppVerifer的过程中尝试运行该场景(或场景的压力版本)。 请确保打开AppVerifier中的pageheap,或者考虑使用/ RTCcsu开关编译代码(有关更多信息,请参阅http://msdn.microsoft.com/zh-cn/library/8wtf2dfz.aspx )。

“ Heisenbugs ”需要很高的技能来诊断,如果你想得到这里的人的帮助,你必须更详细地描述这个问题,耐心地听取各种testing和检查,在这里报告结果,并重复这个直到你解决它(或决定它在资源方面太昂贵了)。

您可能不得不告诉我们您的实际情况,语言,数据库,操作系统,工作量估计,过去发生的时间以及其他事情,列出您已经做过的testing,testing结果准备做更多并分享结果。

这不能保证我们能够共同find它,要么…

我build议写下用户一直在做的所有事情。 如果你可以说10个这样的错误报告你可以尝试find连接它们的东西。

仔细阅读堆栈跟踪,并猜测可能发生的事情; 然后尝试跟踪\logging可能会造成麻烦的每行代码。

把重点放在处理资源; 我发现许多鬼鬼祟祟的零星错误都与closures\处置东西有关:)。

对于.NET项目您可以使用Elmah(错误logging模块和处理程序)监视您的应用程序是否发现了exception,安装非常简单,并提供了一个非常好的界面来浏览未知错误

http://code.google.com/p/elmah/

这在今天救了我,以捕捉注册过程中发生的一个非常随机的错误

除此之外,我只能build议尽可能从用户那里获得尽可能多的信息,并对项目工作stream程有透彻的了解

他们大多在晚上出来….大多数

与我合作的团队邀请用户logging他们在CamStudio应用程序中花费的时间,当我们有一个讨厌的bug追踪时。 安装起来非常简单,而且使用起来也非常简单,因为你可以看到用户在做什么。 它与您所使用的语言没有任何关系,因为它只是loggingWindows桌面。

但是,这条路线似乎是可行的,只有当你正在开发企业应用程序,并与用户有良好的关系。

这可能会有所不同(正如你所说),但是一些方便的东西可以

  • 当问题发生时立即进入debugging器并转储所有线程(或等价物,例如立即倾倒内核等)。
  • 在打开日志的情况下运行,否则完全在发布/生产模式下运行。 (这在一些随机的环境中是可能的,比如c和rails,但不是很多。
  • 做的东西,使机器上的边缘条件更差…强制低内存/高负载/更多的线程/服务更多的请求
  • 确保你真的在听用户遇到问题的实际情况。 确保他们真正解释了相关的细节。 这似乎是一个打破了很多领域的人。 试图重现错误的问题是无聊的。
  • 习惯阅读优化编译器生成的程序集。 这似乎阻止了人们,它不适用于所有的语言/平台,但它可以帮助
  • 准备好接受这是你的(开发者)的错。 不要陷入坚持代码完美的陷阱。
  • 有时你需要真正地跟踪发生在机器上的问题。

@ p.marino – 没有足够的代表评论= /

tl; dr – 由于一天中的时间而造成故障

你提到了一天的时间,那引起了我的注意。 曾经有一个错误曾经是某个晚上在晚上工作的人,在他们离开之前试图build立和承诺并一直失败。 他们最终放弃了回家。 当他们第二天早上抓到的时候,它们build立了良好的状态,他们承诺(可能应该是更多的suspiscious =),并且这个构build对每个人都有效。 一个星期或两个星期后,有人迟到,并有一个意想不到的build设失败。 结果发现有一个代码在7PM之后发生了一个bug break。>

我们还发现一个很less使用的项目angular落这个一月的错误,导致不同模式之间的编组问题,因为我们没有考虑到不同的日历是0和1个月的基础。 所以如果没有人干扰项目的这个部分,我们就不可能在1月之前find这个bug。 2011

这些比线程问题更容易解决,但我认为仍然很有趣。

雇用一些testing人员!

这已经为真正怪异的heisenbugs工作。 (我也build议Dave Argans获得“debugging”的副本,这些想法部分来源于使用他的想法!)

(0)使用类似Memtest86的东西检查系统的内存!

整个系统出现问题,所以做一个testing治具来练习整个事情。 用GUI来说服务器端的东西,你用GUItesting框架来运行整个事情,做出必要的input来挑起问题。

它不会100%的时间失败,所以你不得不经常失败。

从一开始就把系统切割成二分之一(二元印章)的情况下,你必须一次去掉一个子系统。 如果他们不能被注释掉,将它们存根出来。

看看它是否仍然失败。 它经常失败吗?

保持正确的testinglogging,一次只更改一个variables!

最坏的情况下,你使用夹具,你testing了几个星期,以获得有意义的统计数据。 这很难; 但请记住,跳汰机正在做这项工作。

我没有线程,只有一个进程,我不会和硬件通话

如果系统没有线程,没有通信进程和联系人没有硬件; 这很棘手; heisenbugs通常是同步的,但是在no-thread no processes的情况下,它更可能是未初始化的数据,或者被释放后使用的数据,无论是在堆上还是在堆栈上。 尝试使用像valgrind这样的检查器。

对于线程/多进程问题:

尝试在不同数量的CPU上运行它。 如果它在1上运行,请尝试4! 尝试强制一个4电脑系统。这将主要确保事情发生一次。

如果有线程或通信进程,这可以摆脱错误。

如果这没有帮助,但是您怀疑它是同步或线程,请尝试更改OS时间片大小。 尽可能的让你的操作系统供应商允许! 有时这几乎每次都会造成竞赛状况!

反过来说,尝试减慢时间片。

然后,您将testing夹具设置为随附的debugging器,然后等待testing夹具停止发生故障。

如果一切都失败了,把硬件放在冰箱里运行。 一切的时间将会转移。

如果您无法确定性地重现问题,则debugging非常困难且耗时。 我对你的build议是找出确定性地重现它的步骤(不仅仅是有时)。

过去几年中在失败再生领域进行了大量的研究,并且仍然非常活跃。 logging和重放技术是迄今为止大多数研究者的研究方向。 这是你需要做的:

1)分析源代码,确定应用程序中的非确定性的来源,即通过不同的执行path(如用户input,操作系统信号)

2)在下一次执行应用程序时logging它们

3)当您的应用程序再次失败时,您有步骤来重现日志中的失败。

如果您的日志仍然不能重现失败,那么您正在处理并发错误。 在这种情况下,你应该看看你的应用程序如何访问共享variables。 不要尝试logging对共享variables的访问,因为您将logging太多的数据,从而导致严重的减速和大型日志。 不幸的是,我可以说没有太多可以帮助你重现并发性的错误,因为研究在这个主题方面还有很长的路要走。 我能做的最好的事情就是提供迄今为止关于并发错误的确定性重放主题的最新进展:

http://www.gsd.inesc-id.pt/~nmachado/software/Symbiosis_Tutorial.html

最好的祝福

使用增强型碰撞记者。 在Delphi环境中,我们有EurekaLog和MadExcept。 其他工具存在于其他环境中。 或者你可以诊断核心转储。 你正在寻找堆栈跟踪,它会告诉你它在哪里爆炸,如何到达那里,内存是什么等。如果是一个用户交互的东西,那么有一个应用程序截图也是有用的。 关于它崩溃的机器的信息(操作系统版本和补丁,当时还在运行什么等)。我提到的两个工具都可以做到这一点。

如果这是一些用户发生的事情,但你不能复制它们,他们可以坐下来观看。 如果不明显,换座椅 – 你“开车”,他们告诉你该怎么做。 您将以这种方式发现微妙的可用性问题。 双击一个单击button,例如,在OnClick事件中启动重新进入。 之类的东西。 如果用户是远程用户,请使用WebEx,Wink等进行录制,以便分析播放。