我们的应用程序中有一个不是每次都会发生的 bug,因此我们不知道它的“逻辑”。我今天甚至不能把它复制100次。
免责声明: 这个错误是存在的,我已经看到了。它不是一个 Pebkac或类似的东西。
复制这类错误的常见提示是什么?
添加某种类型的日志记录或跟踪。例如,记录用户在导致 bug 之前提交的最后一个 X 操作(只有当您可以设置一个条件来匹配 bug 时)。
尝试在你的应用程序中添加代码,以便在发生错误时自动跟踪(甚至通过邮件/短信提醒你)
记录任何你能记录的东西,这样当它发生时,你可以捕获正确的系统状态。
另一件事-尝试应用自动化测试,以一种形式化的方式覆盖比基于人的测试更多的领域。.虽然希望渺茫,但总的来说是个不错的练习。
仔细阅读堆栈跟踪,并尝试猜测可能发生的情况; 然后尝试跟踪记录每一行可能导致麻烦的代码。
把你的注意力集中在资源的处理上; 我发现的许多隐秘的零星错误都与近距离的处理有关:)。
什么发展环境? 对于 C + + ,你最好的选择可能是 VMware Workstation 记录/重播,参见: Http://stackframe.blogspot.com/2007/04/workstation-60-and-death-of.html
其他建议包括检查堆栈跟踪和仔细的代码概述... ... 实际上没有灵丹妙药:)
“ 海森堡”需要高超的诊断技巧,如果你想从这里的人那里得到帮助,你必须更详细地描述它,并且耐心地听各种测试和检查,在这里报告结果,并且迭代它直到你解决它(或者认为它在资源方面太昂贵)。
你可能必须告诉我们你的实际情况,语言,数据库,操作系统,工作量估计,过去发生的时间,以及其他无数的事情,列出你已经做过的测试,它们是如何进行的,并准备好做更多并分享结果。
这也不能保证我们能一起找到它。
添加详细日志记录。要添加足够的日志记录来理解场景,需要进行多次——有时是十几次——的迭代。 现在的问题是,如果问题是一个竞态条件,这很可能是如果它不可靠地重现,所以日志可以改变时间和问题将停止发生。在这种情况下,不要将日志记录到文件中,而是在内存中保留一个日志的旋转缓冲区,只有在检测到问题已经发生时才将其转储到磁盘上。
编辑: 多一点想法: 如果这是一个 gui 应用程序,使用 qa 自动化工具运行测试,该工具允许您重播宏。如果这是一个服务类型的应用程序,请尝试至少猜测一下正在发生什么,然后通过编程创建“反常”的使用模式,这些模式将执行您所怀疑的代码。创建比平常更高的负载等。
所有这些,再加上一些蛮力软机器人,它是半随机的,并散布了大量的断言/验证(c/c + + ,可能类似于其他语言)通过代码
我建议写下用户一直在做的所有事情。如果你有10个这样的错误报告,你可以尝试找到一些东西,把它们联系起来。
对问题进行分析,并对代码进行对读。把你知道是真实的问题记录下来,并试着断言哪些逻辑前提 必须的是真实的。像 CSI 一样跟着证据走。
大多数人本能地说“添加更多的日志记录”,这可能是一个解决方案。但是对于很多问题来说,这只会使事情变得更糟,因为日志记录可以改变时间依赖关系,从而使问题更频繁或更少地出现。将频率从千分之一改为百万分之一不会让你更接近问题的真正根源。
因此,如果你的逻辑推理没有解决问题,它可能会给你一些细节,你可以研究与日志或断言在你的代码。
大量的日志记录和仔细的代码检查是你唯一的选择。
如果应用程序被部署,而你又不能调整日志记录,那么这些问题会尤其令人痛苦。在这一点上,您唯一的选择就是仔细检查代码,并试图推断程序如何进入糟糕的状态(用科学的方法来拯救!)
通常这类 bug 与损坏的内存有关,因此它们可能不会经常出现。您应该尝试使用某种内存分析器来运行您的软件,例如,valground,以查看是否出现了错误。
除了大量的耐心,你还需要一个安静的祈祷和诅咒:
高温。
假设我从一个生产应用程序开始。
我通常会在我认为 bug 正在发生的地方添加调试日志。我设置了日志语句,以便深入了解应用程序的状态。然后打开调试日志级别,并请求用户/操作员通知我下一个 bug 发生的时间。然后,我分析日志,看看它提供了哪些关于应用程序状态的提示,以及这是否能够更好地理解可能出现的错误。
我重复步骤1,直到我有一个很好的想法,我可以开始在调试器中调试代码
有时候代码运行的迭代次数是关键,但是有时候它可能是组件与外部系统(数据库、特定用户机器、操作系统等)的交互。花一些时间设置与生产环境尽可能匹配的调试环境。虚拟机技术是解决这一问题的有力工具。
接下来我通过调试器继续。这可能包括创建某种测试工具,使代码/组件处于我从日志中观察到的状态。了解如何设置条件断点可以节省大量时间,所以要熟悉调试器中的条件断点和其他特性。
调试,调试,调试。如果几个小时后你哪儿也去不了,那就休息一下,做一些无关紧要的事情。带着新的想法和观点回来。
如果现在还没有任何进展,那么回到步骤1并进行另一次迭代。
对于真正困难的问题,您可能不得不求助于在发生 bug 的系统上安装调试器。与步骤4中的测试工具相结合,通常可以解决真正令人困惑的问题。
对于程序员来说,仅仅因为开发了某种工作流程和使用应用程序的习惯,就不能重复用户经历过的崩溃,这是很常见的。
在这个频率为1/100的情况下,我想说的是,首先要做的是处理异常并在任何地方记录任何内容,否则您可能要花费一周时间来寻找这个 bug。 还要列出项目中可能敏感的关节和特性的优先级列表。例如: 1-多线程 2-野生指针/松散数组 3-依赖输入设备 等等。 这将有助于你分割的领域,你可以蛮力,直到打破,再次由其他海报建议。
为了。NET 项目你可以使用 Elmah (错误日志记录模块和处理程序)来监视你的应用程序中未捕获的异常,它的安装非常简单,并提供了一个非常好的界面来浏览未知的错误
Http://code.google.com/p/elmah/
这使我今天能够捕捉到在注册过程中发生的一个非常随机的错误
除此之外,我只能建议尽可能多地从用户那里获取信息,并对项目工作流程有一个全面的了解
他们大多在晚上出来。 差不多吧
您的应用程序很有可能是 MTWIDNTBMT (不需要多线程时的多线程) ,或者可能只是多线程(出于礼貌)。在多线程应用程序中重现零星错误的一个好方法是将下面这样的代码散布开来(C #) :
Random rnd = new Random(); System.Threading.Thread.Sleep(rnd.Next(2000));
及/或这样:
for (int i = 0; i < 4000000000; i++) { // tight loop }
模拟线程在不同的时间完成任务,或者长时间占用处理器。
这些年来,我继承了许多漏洞百出、多线程的应用程序,而像上面这样的代码通常会使零星的错误发生得更加频繁。
这个问题没有一个普遍的好答案,但以下是我的发现:
做这种事需要天赋。并非所有的开发人员都最适合它,即使他们是其他领域的超级明星。所以了解你的团队,他们有这方面的天赋,希望你能给他们足够的糖果,让他们对帮助你感到兴奋,即使这不是他们的领域。
回溯过去,把它当作一项科学调查。从窃听器开始,你看到的是错误的。对于什么可能导致这种情况提出假设(这是创造性/想象力的部分,不是每个人都有天赋的艺术)——了解代码是如何工作的会有很大帮助。对于这些假设中的每一个(最好是按照你认为最有可能发生的事情来排序——这里还是纯粹的直觉感觉) ,开发一个试图排除它作为原因的测试,并检验这个假设。任何不符合预测的假设并不意味着假设是错误的。检验这个假设,直到它被证实是错误的(虽然你不太可能想先进行另一个假设,只是不要折扣这个假设,直到你有一个明确的失败)。
在这个过程中收集尽可能多的数据。广泛的日志记录和其他任何适用的东西。不要因为缺乏数据而对假设不屑一顾,而是要弥补数据的缺乏。通常,正确假设的灵感来自于对数据的研究。在堆栈跟踪中注意到一些异常,在日志中出现奇怪的问题,在数据库中缺少一些应该存在的东西,等等。
仔细检查每一个假设。很多时候,我看到一个问题没有很快得到解决,因为一些通用的方法调用没有得到进一步的研究,所以这个问题被认为是不适用的。“哦,那个,那应该很简单。”(见第一点)。
如果你用完了假设,这通常是由于对系统的知识不足造成的(即使你自己写了每一行代码,这也是真的) ,你需要通过运行和检查代码,并获得对系统的额外洞察力来提出一个新的想法。
当然,以上这些都不能保证什么,但我发现这种方法能够始终如一地取得成果。
由于这是语言无关的,我将提到一些调试公理。
计算机做的任何事都不是随机的。“随机事件”表示尚未发现的模式。调试从隔离模式开始。改变单个元素,并评估是什么改变了 bug 的行为。
不同的用户,同一台电脑? 同一个用户,不同的电脑? 发生强烈的周期性吗? 重新启动是否改变周期性?
仅供参考,我曾经见过一个只有一个人经历过的病毒。我指的是人,不是用户账号。用户 A 将永远不会看到他们的系统上的问题,用户 B 将坐在该工作站,以用户 A 的身份登记和可以立即重现的错误。应用程序不可能知道坐在椅子上的身体之间的区别。然而-
用户以不同的方式使用这个应用程序。用户 A 习惯性地使用热键来调用一个动作,而用户 B 则使用屏幕上的控制。用户行为的差异将在几次操作之后级联成一个可见的错误。
任何影响 bug 行为的差异都应该被调查,即使它毫无意义。
单元测试。在应用程序中测试一个 bug 通常是可怕的,因为有太多的噪音,太多的可变因素。一般来说,干草堆越大,就越难找出问题所在。创造性地扩展您的单元测试框架以包含边缘案例可以节省数小时甚至数天的筛选时间
说了没有什么灵丹妙药,我能感觉到你的痛苦。
添加与此 bug 相关的方法中的前置和后置条件检查。
你可以看看 合同设计
我工作的团队已经征集用户记录他们的时间,他们花在我们的应用程序与 CamStudio 当我们有一个讨厌的错误追踪。它易于安装和使用,并且使得复制那些唠叨的错误变得更加容易,因为您可以观察用户正在做什么。它与你使用的语言也没有任何关系,因为它只是记录 Windows 桌面。
然而,这条路似乎只有在你正在开发企业应用程序并与用户保持良好关系的情况下才是可行的。
这有所不同(正如您所说的) ,但是有些东西可以很方便地使用它
假设你在 Windows 上,你的“ bug”是一个崩溃或者非托管代码(C/C + +)中的某种损坏,然后看看来自微软的 申请验证机构。该工具有许多停止,可以启用这些停止在运行时验证事情。如果您对发生 bug 的场景有一个概念,那么尝试在运行 AppVerifer 的情况下运行该场景(或该场景的压力版本)。确保在 AppVerifier 中打开页面堆,或者考虑使用/RTCcsu 开关编译代码(更多信息参见 http://msdn.microsoft.com/en-us/library/8wtf2dfz.aspx)。
@ p. marino-没有足够的名声来评论 =/
由于一天中的时间而导致的 dr-build 失败
你提到了一天中的某个时间,这引起了我的注意。曾经有一个 bug,有人在晚上加班,试图在离开前构建并提交,但总是失败。他们最终放弃了,回家了。当他们在第二天早上发现它构建良好时,他们承诺(也许应该更多地怀疑 = ]) ,这个构建对每个人都有效。一两个星期后,有人加班到很晚,出现了意想不到的构建失败。结果发现代码中有一个 bug,在晚上7点休息 > 之后进行了任何构建。 >
今年1月,我们还在项目中一个很少使用的角落发现了一个 bug,它导致了不同模式之间的编组问题,因为我们没有考虑到基于0和1个月的不同日历。因此,如果没有人在项目的这一部分捣乱,我们直到2011年1月才有可能发现这个 bug
这些问题比线程问题更容易解决,但我认为仍然很有趣。
雇几个测试员!
这招对怪异的海森堡很管用。 (我还建议你去买一本戴夫 · 阿根斯的《调试》 ,这些想法有一部分是从他的想法中衍生出来的!)
(0)使用 Memtest86检查系统的内存!
整个系统都显示出问题,所以制作一个测试夹具来测试整个系统。 假设它是一个带有 GUI 的服务器端的东西,您使用一个 GUI 测试框架运行整个东西,并执行必要的输入以引发问题。
它不会100% 失败,所以你必须让它更经常地失败。
首先把系统切成两半(二进制切) 更糟糕的情况是,您必须一次删除一个子系统。 如果他们不能被评论出来,就把他们灭掉。
看看还会不会失败,失败的次数更多吗?
保持正确的测试记录,一次只更改一个变量!
最坏的情况下,你使用夹具,你测试了几个星期,以获得有意义的统计数据。这是困难的; 但是记住,夹具正在做这项工作。
我没有线程,只有一个进程,而且我不与硬件对话
如果系统没有线程,没有通信进程,没有硬件联系; 这很棘手; Heisenbug 通常是同步的,但是在没有线程的情况下,它更可能是未初始化的数据,或者在发布后使用的数据,无论是在堆上还是在堆栈上。试着用一个像瓦尔格林那样的检查员。
对于线程/多进程问题:
试着在不同数量的 CPU 上运行它。如果它在1楼运行,试试4楼!尝试强制一个4台计算机的系统到1。 主要是确保事情一件一件发生。
如果存在线程或通信进程,这可以消除 bug。
如果这没有帮助,但你怀疑它是同步或线程,尝试改变操作系统的时间片大小。 使它作为您的操作系统供应商允许的罚款! 有时候,这会导致几乎每次都出现竞态条件!
当然,试着在时间片上慢一点。
然后,将测试夹具设置为在所有位置都附加了调试器的情况下运行,并等待测试夹具在出现故障时停止运行。
如果其他方法都失败了,就把硬件放到冰箱里运行。一切的时机都会改变。
使用强化的坠机记录器。在 Delphi 环境中,我们有 EurekaLog 和 MadExcel。其他工具存在于其他环境中。或者你可以诊断核心转储。您正在寻找堆栈跟踪,它会告诉您它在哪里爆炸,它是如何到达那里的,在内存中有什么,等等。如果是一个用户交互的东西,有一个应用程序的截图也是很有用的。以及它在哪台机器上崩溃的信息(操作系统版本和补丁,当时还在运行什么等等)我提到的两个工具都可以做到这一点。
如果是一些只有少数用户才会发生的事情,但是你不能复制它,而且他们可以,那么你就坐在他们旁边观看。如果看不出来,换个座位——你“开车”,他们会告诉你该怎么做。通过这种方式,您将发现微妙的可用性问题。双击单击按钮,例如,在 OnClick 事件中启动重入。诸如此类。如果用户是远程的,使用 WebEx,Wink 等,记录他们的崩溃,所以你可以分析回放。
调试是困难和耗时的,特别是如果您不能确定地重现问题。我给你的建议是找出确定性地重现它的步骤(不仅仅是有时)。
近年来,在失效再现领域已经进行了大量的研究,并且仍然十分活跃。记录与重放技术一直是大多数研究者的研究方向。这是你需要做的:
1)分析源代码,确定应用程序中不确定性的来源,也就是说,哪些方面可以让你的应用程序通过不同的执行路径(如用户输入,操作系统信号)
2)在下次执行应用程序时记录它们
3)当您的应用程序再次失败,您有步骤,以重现失败在您的日志。
如果您的日志仍然没有重现失败,那么您正在处理一个并发错误。在这种情况下,应该查看应用程序如何访问共享变量。不要试图记录对共享变量的访问,因为这样会记录太多的数据,从而导致严重的速度减慢和大量的日志。不幸的是,我没有多少可以帮助您重现并发 bug 的东西,因为在这个主题上的研究还有很长的路要走。我所能做的最好的事情就是提供一个关于并发 bug 的确定性重播主题的最新进展(到目前为止)的参考:
Http://www.gsd.inesc-id.pt/~nmachado/software/symbiosis_tutorial.html
最好的问候