这个问题说明了一切。如果你有多个用户报告的bug,但是日志中没有出现bug的记录,也不能重复出现bug,无论你怎么努力,你如何修复它?或者你可以吗?
我相信这在你们中的许多人身上都发生过。在这种情况下你做了什么,最后的结果是什么?
编辑:我更感兴趣的是对无法找到的错误所做的事情,而不是无法解决的错误。无法解决的错误是这样的,您至少知道存在问题并且在大多数情况下有一个起点来搜索它。万一找不着了,怎么办?你甚至可以做任何事情吗?
不同的编程语言会有自己的 bug 风格。
添加调试语句可以使问题无法复制,因为调试语句本身将指针移动得足够远以避免 SEGFAULT ---也称为Heisenbugs。指针问题很难跟踪和复制,但调试器可以提供帮助(例如GDB和DDD)。
具有多个线程的应用程序可能仅通过非常特定的时间或事件序列来显示其错误。在难以复制的情况下,不正确的并发实现可能会导致死锁。
一些 Web 浏览器因内存泄漏而臭名昭著。在一个浏览器中运行良好的 JavaScript 代码可能会导致在另一个浏览器中出现错误行为。使用经过数千名用户严格测试的第三方库有助于避免某些晦涩的错误。
根据应用程序(有错误)运行的环境的复杂性,唯一的办法可能是简化环境。应用程序是否运行:
应用程序在什么环境中产生问题?
退出无关的应用程序、终止后台任务、停止所有计划的事件(cron 作业)、消除插件和卸载浏览器加载项。
由于网络对于许多应用程序至关重要:
消除尽可能多的未知数:
消除生产、测试和开发之间的所有差异。使用相同的硬件。按照完全相同的步骤完美地设置计算机。一致性是关键。
使用大量的日志记录来关联事件发生的时间。检查日志是否有任何明显的错误、时间问题等。
如果软件看起来没问题,请考虑硬件故障:
主要用于嵌入式:
当您在本地运行应用程序(即,不通过网络)时会发生什么?其他服务器是否也遇到同样的问题?数据库是远程的吗?可以使用本地数据库吗?
介于硬件和软件之间的是固件。
时序问题很难跟踪:
收集有关该问题的硬数字数据。一个起初可能看起来是随机的问题,实际上可能有一个模式。
有时系统升级后会出现问题。
不同的操作系统有不同的方式来分发冲突的库:
执行操作系统的全新安装,并仅包括您的应用程序所需的支持软件。
确保每个库只使用一次。有时应用程序容器的库版本与应用程序本身不同。这可能无法在开发环境中复制。
编写当错误发生时触发通知(例如,日志、电子邮件、弹出窗口、寻呼机哔声)的检测方法。使用自动化测试将数据提交到应用程序中。使用随机数据。使用涵盖已知和可能的极端情况的数据。最终,该错误应该再次出现。
值得重申的是其他人提到的:睡吧。花时间远离问题,完成其他任务(如文档)。远离电脑并进行一些锻炼。
逐行浏览代码,并描述每一行对你自己、同事或橡皮鸭的作用。这可能会导致有关如何重现错误的见解。
宇宙射线可以翻转比特。由于现代的内存错误检查,这不像过去的问题那么大。离开地球保护的硬件软件会遇到由于宇宙辐射的随机性而根本无法复制的问题。
有时,尽管很少,编译器会引入错误,特别是对于小众工具(例如,遭受符号表溢出的 C 微控制器编译器)。是否可以使用不同的编译器?工具链中的任何其他工具是否会引入问题?
如果它是一个 GUI 应用程序,那么观察客户生成错误(或尝试生成错误)是非常宝贵的。毫无疑问,他们正在做一些你从未猜到他们正在做的事情(没有错,只是不同)。
否则,请将您的日志集中在该区域。记录大部分内容(您可以稍后将其取出)并让您的应用程序也转储其环境。例如机器类型、VM 类型、使用的编码。
您的应用程序是否报告版本号、内部版本号等?您需要它来准确确定您正在调试的版本(或不调试!)。
如果您可以检测您的应用程序(例如,如果您在 Java 世界中,则使用 JMX)然后检测相关区域。存储统计信息,例如请求+参数、时间等。利用缓冲区存储最近的“n”个请求/响应/对象版本/任何内容,并在用户报告问题时将它们转储出来。
如果你不能复制它,你可以修复它,但不知道你已经修复了它。
我已经对错误是如何触发的(即使我不知道这种情况是如何发生的)做出了最好的解释,修复了它,并确保如果错误再次出现,我们的通知机制会让未来的开发人员知道我希望我知道的事情。在实践中,这意味着在跨越可能触发错误的路径时添加日志事件,并记录相关资源的指标。当然,还要确保测试总体上很好地执行了代码。
决定添加哪些通知是一个可行性和分类问题。因此,首先要决定开发人员在错误上花费多少时间。如果不知道错误的重要性,就无法回答。
我有好的结果(没有再次出现,代码更适合它),也有不好的结果(花了太多时间没有解决问题,无论错误最终修复与否)。这就是估计和问题优先级的目的。
有时我只需要坐下来研究代码,直到找到错误。尝试证明该错误是不可能的,并且在此过程中您可能会找出可能出错的地方。如果你真的成功地说服自己这是不可能的,假设你在某个地方搞砸了。
添加一堆错误检查和断言来确认或否认您的信念/假设可能会有所帮助。某些事情可能会失败,这是您从未预料到的。
这可能很困难,有时几乎是不可能的。但我的经验是,如果你花足够的时间在它上面,你迟早能够重现和修复错误(如果花的时间值得,那是另一回事)。
在这种情况下可能会有所帮助的一般建议。
进行随机更改,直到某些东西起作用:-)
假设您已经添加了所有您认为会有所帮助的日志记录,但它并没有……想到两件事:
从报告的症状向后工作。想想自己......“我想产生报告的症状,我需要执行哪些代码,我将如何获得它,我将如何获得它?” D 导致 C 导致 B 导致 A。接受如果错误不可重现,那么常规方法将无济于事。我不得不盯着代码看了好几个小时,这些思维过程一直在寻找一些错误。通常事实证明这是非常愚蠢的事情。
记住 Bob 的第一条调试定律:如果你找不到东西,那是因为你找错了地方 :-)
思考。难的。把自己锁起来,不许打扰。
我曾经有一个错误,证据是损坏数据库的十六进制转储。指针链被系统地搞砸了。所有用户的程序,以及我们的数据库软件,都在测试中完美运行。我盯着它看了一个星期(它是一个重要的客户),在排除了几十个可能的想法之后,我意识到数据分布在两个物理文件中,并且损坏发生在链跨越文件边界的地方。我意识到如果备份/恢复操作在关键点失败,这两个文件最终可能会“不同步”,恢复到不同的时间点。如果您随后在已经损坏的数据上运行客户的程序之一,它将准确地产生我所看到的打结的指针链。然后,我演示了一系列准确再现腐败的事件。
修改您认为发生问题的代码,以便在某处记录额外的调试信息。下次发生时,您将拥有解决问题所需的一切。
有两种类型的错误是您无法复制的。你发现的那种,和别人发现的那种。
如果你发现了这个错误,你应该能够复制它。如果您无法复制它,那么您根本没有考虑导致该错误的所有促成因素。这就是为什么每当你有一个错误,你应该记录它。保存日志,获取屏幕截图等。如果你不这样做,那么你怎么能证明这个 bug 真的存在呢?也许这只是一个错误的记忆?
如果其他人发现了一个错误,而您无法复制它,显然请他们复制它。如果他们无法复制它,那么您尝试复制它。如果您不能快速复制它,请忽略它。
我知道这听起来很糟糕,但我认为这是合理的。您复制其他人发现的错误所花费的时间非常长。如果错误是真实的,它自然会再次发生。有人,也许甚至你,会再次偶然发现它。如果难以复制,那也是比较少见的,多出现几次估计也不会造成太大的伤害。
如果您将时间花在实际工作、修复其他错误和编写新代码上,您的工作效率会比您尝试复制一个您甚至无法保证确实存在的神秘错误要高得多。等它自然地再次出现,你就可以把所有的时间都花在修复上,而不是浪费时间试图揭示它。
讨论问题,阅读代码,通常是相当多的。我们通常成对进行,因为您通常可以很快地通过分析消除可能性。
首先查看您可以使用哪些工具。例如,Windows 平台上的崩溃转到 WinQual,因此如果您遇到这种情况,您现在可以获得崩溃转储信息。您是否可以发现潜在错误的静态分析工具、运行时分析工具、分析工具?
然后看输入和输出。在用户报告错误的情况下,输入有什么相似之处,或者输出中有什么不合适的地方吗?编制一份报告清单并寻找模式。
最后,正如大卫所说,盯着代码。
要求用户为您提供远程访问他的计算机并自己查看所有内容。要求用户制作一个关于他如何重现此错误的小视频并将其发送给您。
当然两者并不总是可能的,但如果它们是可能的,它可能会澄清一些事情。查找错误的常用方法仍然相同:分离可能导致错误的部分,尝试了解正在发生的事情,缩小可能导致错误的代码空间。
有诸如 gotomeeting.com 之类的工具,您可以使用这些工具与用户共享屏幕并观察行为。可能存在许多潜在问题,例如他们机器上安装的软件数量,某些工具实用程序与您的程序冲突。我相信 gotomeeting 不是唯一的解决方案,但可能存在超时问题、互联网速度慢的问题。
大多数时候我会说软件不会报告您正确的错误消息,例如,在 java 和 c# 跟踪每个异常的情况下.. 不要捕获所有但保留一个可以捕获和记录的点。除非您使用远程桌面工具,否则 UI 错误很难解决。大多数情况下,它甚至可能是第三方软件中的错误。
如果您在一个真正的大型应用程序上工作,您可能会有 1,000 个错误的队列,其中大多数肯定是可重现的。
因此,恐怕我可能会以 WORKSFORME (Bugzilla) 的形式关闭该错误,然后继续修复一些更具体的错误。或者做项目经理决定做的任何事情。
当然,进行随机更改是一个坏主意,即使它们是本地化的,因为您可能会引入新的错误。