这句话 - 它在我的机器上起作用可能是一种娱乐的根源,但它也代表了发展世界中一种普遍的态度 - 这种态度经常迫使用户在我们愿意之前证明错误调查他们。但实际上,我们需要承担责任并追逐这个问题,无论它带我们去哪里。
解决错误解决的两种方法
求解错误需要一种两管齐下的方法。最初,我们想复制问题发生的环境;它可能是用户机器的特定物品。另外,我们可能需要诉诸远程调试或使用用户机器中的日志,要求他们代表我们执行某些操作。
几年前,我试图复制用户报告的错误。尽管匹配JVM版本,OS,网络连接等等,但该错误根本不会出现。最终,用户发送了一个显示错误的视频,我注意到它们在UI中的单击不同。这突出了一个事实,通常,错误复制过程不仅在机器中,而且在用户行为中。
用户行为和通信在错误解决中的作用
在这种情况下,尽可能隔离用户行为至关重要。使用视频来验证行为可能会有所帮助。了解复制环境中的细微差异是其中的关键部分,并且与可以重现问题的人开放,清晰的沟通是必须的。
但是,可能会有障碍。有时,报告该问题的人来自支持部门,而我们可能会在研发部门。有时,客户可能会感到沮丧,从而导致沟通破裂。这就是为什么我认为将研发部门与支持部门集成以确保问题更平稳的解决方案至关重要。
用于解决错误的工具和技术
strace
,dtrace
等几种工具可以为运行应用程序提供深入的见解。此信息可以帮助我们查明应用程序中的差异和不当行为。像Docker这样的容器技术的出现大大简化了统一环境的创造,消除了许多微妙的差异。
我正在调试一个仅在客户位置失败的系统。事实证明,他们的网络连接非常快,在我们的本地设置代码完成执行之前,往返管理服务器的往返。我通过远程记录到他们的现场机器并在那里重现该问题来追踪它。有些问题只能在特定的地理位置中表现出来。
诸如网络差异,数据源差异和规模之类的因素可能会对环境产生重大影响。您如何重现只有在一个大集群中每秒有1,000个请求时出现的问题?可观察性工具在管理这些情况下可能非常有帮助。在这种情况下,调试过程发生了变化,这不再是关于复制的,而是关于在我讨论here时了解环境的可观察信息。
理想情况下,我们不应该达到这些情况,因为测试应具有正确的覆盖范围。但是,实际上,事实并非如此。许多公司都有长期测试,旨在整夜运行并最大程度地强调系统。他们有助于在野外发生之前发现并发问题。失败通常是由于缺乏存储(填充了所有日志),但是通常在失败时,很难再现。使用循环重新运行多次失败的代码通常是一个完美的解决方案。另一个有价值的工具是“力量投掷” i discussed previously。从长远来看,这使我们得以优雅地失败并经过绊脚石。
记录
记录是大多数应用程序的重要特征;这是我们需要调试这些边缘案例的确切工具。我以前及其价值。
是的,记录需要很像可观察性。如果不记录“已经到位”,我们无法调试现有的错误。像许多事情一样,开始正确记录并拿起最佳实践永远不会太晚。
并发
如果错误难以捉摸,那么与并发问题的几率很高。如果问题不一致,那么这是一个起点,验证所涉及的线程并确保正确的线程正在执行您的期望。
使用单线断点仅暂停一个特定的线程,并检查是否在特定方法的情况下存在种族条件。在可能的情况下,在可能的情况下使用跟踪点,而不是断点 - 在调试屏蔽皮肤或更改并发相关的错误时,这通常是造成不一致的原因。
查看所有线程,并尝试通过使其他线程入睡来给每个线程一个边缘。仅当满足某些条件时,可能会出现并发问题。我们可以使用这种技术偶然发现独特的条件。
尝试自动化该过程以获得复制。当遇到这样的问题时,我们通常会创建一个循环,该循环运行了数百甚至数千次测试案例。我们通过记录并试图在日志中找到问题来做到这一点。
请注意,如果问题确实是并发代码中的问题,则额外的记录可能会对结果产生重大影响。在一种情况下,我将字符串列表存储在内存中,而不是将它们写入日志。然后,执行完成后,我将完整的列表丢弃。使用内存记录进行调试是不理想的,但是它可以避免避免记录器甚至直接控制台输出的开销(FYI控制台输出通常比记录器要慢,因为缺乏过滤和没有管道)。
)。什么时候“放弃”
虽然从来没有真正建议“放弃”,但可能会有一段时间您必须接受一致在计算机上持续重现问题的时间是不可行的。在这种情况下,我们应该继续进行调试过程的下一步。这涉及对潜在原因做出假设并创建测试案例以复制它们。
在无法解决错误的情况下,在代码中添加日志记录和断言很重要。这样,如果错误浮出水面,我们将有更多信息可以使用。
调试的现实:案例研究
在Codename One上,我们每天的计费突然从几美元飙升至数百。潜在的成本如此之高,它威胁要在一个月内破产我们。尽管我们尽了最大的努力,包括受过基础的猜测和解决我们能做的一切,但我们永远无法查明特定的错误。相反,我们必须通过蛮力解决问题。
最后,解决错误是关于持久性和不断学习的。这不仅是要接受该错误作为开发过程的一部分,而且还了解我们如何从每种调试经验中改善和成长。
tl; dr
格言“它在我的机器上起作用”通常在软件开发的世界中缺乏。我们必须拥有错误的所有权,试图尽可能地复制用户的环境和行为。清晰的交流是关键,研发和支持部门之间的集成是无价的。
现代工具可以为运行应用程序提供深入的见解,从而帮助我们查明问题。虽然像Docker这样的容器技术简化了统一环境的创建,网络,数据源和规模的差异仍然会影响调试。
有时,尽管我们尽了最大的努力,但在我们的机器上不能始终如一地复制错误。在这种情况下,我们需要对潜在原因做出有根据的假设,创建重现这些假设的测试案例,并将记录和断言添加到《代码》中,以进行未来的调试协助。
最后,调试是一种学习经验,需要持久性和适应性,对于任何开发人员的增长和改善至关重要。