调试策略
本节阅读量:在调试程序时,在大多数情况下,绝大多数时间都将花费在试图查找错误的实际位置上。一旦发现问题,与之相比,其余的步骤(修复问题并验证问题是否已修复)通常是微不足道的。
在本课中,我们将开始探索如何查找错误。
通过阅读代码发现问题
假设您注意到一个问题,并且希望跟踪该特定问题的原因。在许多情况下(特别是在较小的程序中),通过阅读代码,我们可以快速了解问题所在的位置。
考虑以下程序片段:
|
|
如果您希望该程序按字母顺序打印名称,但它却按相反的顺序打印,那么问题可能出在sortNames函数中。在可以将问题缩小到特定函数的情况下,可以通过查看代码来发现问题。
然而,随着程序变得越来越复杂,通过代码检查来发现问题也变得越来越困难。
首先,有更多的代码需要查看。查看程序中数千行长的每一行代码可能需要很长时间(更不用说它非常无聊)。其次,代码本身往往更复杂,有更多可能出错的地方。第三,代码的行为可能不会为您提供关于哪里出错的许多线索。如果您编写了一个程序来输出股票推荐,如果它没有任何输出,那么您可能对从哪里开始查找问题没有太多的头绪。
最后,错误可能是由错误的假设引起的。几乎不可能直观地发现由错误假设导致的错误,因为在检查代码时,您可能会做出相同的错误假设,而不会注意到错误。
如果我们有一个通过代码检查无法找到的问题,我们如何找到它?
通过运行程序查找问题
如果我们不能通过代码检查找到问题,我们可以采取另一种方法:我们可以观察程序运行时的行为,并尝试从中诊断问题。这种方法可以概括为:
- 尝试研究,如何复现问题
- 运行程序,收集信息,缩小查找范围
- 重复前面的1,2步,直到解决问题
在本章的其余部分,我们将讨论种方法和技术。
复现问题
发现问题的第一步也是最重要的一步是能够重现问题。再现问题意味着以一致的方式出现问题。原因很简单:除非你能观察到问题的发生,否则很难找到它。
如果软件问题很明显(例如,每次运行程序时,程序都会在同一位置崩溃),那么重现问题可能是微不足道的。然而,有时复制问题可能会困难得多。问题可能仅发生在某些计算机上,或在特定情况下(例如,当用户输入某些输入时)。在这种情况下,生成一组再现步骤是很有帮助的。复制步骤是一个明确的步骤列表,可以遵循这些步骤以使问题复发,并具有高度的可预测性。目标是能够使问题尽可能地再次发生,因此我们可以反复运行程序,并寻找线索来确定是什么导致了问题。如果问题可以100%地再现,这是理想的,即使再现性低于100%是可以的。仅在50%的时间内发生的问题可能意味着诊断该问题所需的时间是可以100%复现问题的两倍,因为一半的时间程序不会显示该问题,不能提供任何有用的诊断信息。
专注于问题
一旦我们能够合理地重现问题,下一步就是找出问题在代码中的位置。根据问题的性质,这可能是容易的,也可能是困难的。举个例子,假设我们不太清楚错误到底发生在哪里,我们如何找到它?
在这里,让我们玩一个猜数字游戏。请你猜一个1到10之间的数字。对于您的每个猜测,我将告诉您每个猜测是太高、太低还是正确。此游戏的一个实例可能如下所示:
|
|
在上面的游戏中,你不必猜所有的数字就能找到正确的数字。通过猜测,考虑从每个猜测中学习到的信息的过程,您可以仅通过几次猜测就“找到”正确的数字(如果您使用最优策略,您总是可以在4次或更少的猜测中找到正确的数字)。
我们可以使用类似的过程来调试程序。在最坏的情况下,我们可能不知道错误在哪里。然而,我们确实知道问题一定在程序开始和结束中,我们可以观察到的错误症状发生在执行的代码中的某个地方。这至少排除了之后执行的程序部分。但这仍然可能留下许多代码需要覆盖。为了诊断问题,我们将对问题在哪里进行一些有根据的猜测,目标是快速地找到问题所在。
通常,无论是什么原因导致我们注意到的问题,都会给我们一个接近实际问题所在位置的初步猜测。例如,如果程序没有在应该向文件写入数据的时候将数据写入文件,那么问题可能在处理向文件写入的代码中的某个地方。然后,我们可以使用类似二分查找的策略来尝试并隔离问题的实际位置。
例如:
- 如果在我们的程序中的某个点上,我们可以证明问题尚未发生,这类似于收到“太低”的猜测结果——我们知道问题一定在程序的稍后位置。例如,如果我们的程序每次都在相同的位置崩溃,并且我们可以证明程序执行的特定点没有崩溃,那么崩溃必须在代码的后面。
- 如果在我们的程序中的某个点上,我们可以观察到与问题相关的不正确行为,那么这类似于接收到“过高”的猜测结果,并且我们知道问题一定在程序中的较早位置。例如,假设一个程序打印某个变量x的值。您希望它打印值2,但它打印的是8。变量x肯定具有错误的值。如果在程序执行过程中的某个时刻,我们可以看到变量x已经具有值8,那么我们知道问题一定发生在该点之前。
猜数字类比并不完美——我们有时也可能将代码的某个部分排除考虑范围,仍然不会获得实际问题是在该点之前还是之后的信息。
在下一课中,我们将展示所有这三种情况的示例。
最终,通过足够的猜测和一些好的技术,我们可以找到导致问题的确切路径!当你排除了所有其他的事情,剩下的唯一的事情一定是导致问题。
您想使用什么样的猜测策略取决于您——什么是最好的策略取决于错误类型,因此您可能希望尝试许多不同的方法来缩小排查问题的范围。当您获得调试问题的经验时,您的直觉将帮助并指导您。
那么我们如何“猜测”呢?有许多方法可以做到这一点。我们将在下一节中从一些简单的方法开始,然后我们将在这些方法的基础上进行扩展,并在后续小节中探索其他方法。
