调试(也称为纠错)作为成功测试的后果出现,也就是说,调试是在测试发现错误之后排除错误的过程。虽然调试应该而且可以是一个有序过程,但是,目前它在很大程度上仍然是一项技巧。软件工程师在评估测试结果时,往往仅面对着软件错误的症状,也就是说软件错误的外部表现和它的内在原因之间可能并没有明显的联系。调试就是把症状和原因联系起来的尚未被人深入认识的智力过程。
调试不是测试,但是它总是发生在测试之后。调试过程从执行一个测试用例开始,评估测试结果,如果发现实际结果与预期结果不一致,则这种不一致就是一个症状,它表明在软件中存在着隐藏的问题。调试过程试图找出产生症状的原因,以便改正错误。
调试过程总会有以下两种结果之一:①找到了问题的原因并把问题改正和排除掉了;②没找出问题的原因。在后一种情况下,调试人员可以猜想一个原因,并设计测试用例来验证这个假设,重复此过程直至找到原因并改正了错误。
调试工作如此困难,可能心理方面的原因多于技术方面的原因,但是,软件错误的下述特征也是相当重要的原因。
(1)症状和产生症状的原因可能在程序中相距甚远,也就是说,症状可能出现在程序的一个部分,而实际的原因可能在与之相距很远的另一部分。紧耦合的程序结构更加剧了这种情况。
(2)当改正了另一个错误之后,症状可能暂时消失了。
(3)症状可能实际上并不是由错误引起的(例如,介入误差)。
(4)症状可能是由不易跟踪的人为错误引起的。
(5)症状可能是由定时问题而不是由处理问题引起的。
(6)可能很难重新产生完全一样的输入条件(例如,输入顺序不确定的实时应用系统)。
(7)症状可能时有时无,这种情况在硬件和软件紧密地耦合在一起的嵌入式系统中特别常见。
(8)症状可能是由分布在许多任务中的原因引起的,这些任务运行在不同的处理机上。
在调试过程中会遇到从恼人的小错误(例如,不正确的输出格式)到灾难性的大错误(例如,系统失效导致严重的经济损失)等各种不同的错误。错误的后果越严重,查找错误原因的压力也越大。通常,这种压力会导致软件开发人员在改正一个错误的同时引入两个甚至更多个错误。
无论采用什么方法,调试的目标都是寻找软件错误的原因并改正错误。通常需要把系统地分析、直觉和运气组合起来,才能实现上述目标。一般说来,有下列3种调试途径可以采用。
1.蛮干法
蛮干法可能是寻找软件错误原因的最低效的方法。仅当所有其他方法都失败了的情况下,才应该使用这种方法。按照“让计算机自己寻找错误”的策略,这种方法印出内存的内容,激活对运行过程的跟踪,并在程序中到处都写上WRITE(输出)语句,希望在这样生成的信息海洋的某个地方发现错误原因的线索。虽然所生成的大量信息也可能最终导致调试成功,但是,在更多情况下这样做只会浪费时间和精力。在使用任何一种调试方法之前,必须首先进行周密的思考,必须有明确的目的,应该尽量减少无关信息的数量。
2.回溯法
回溯是一种相当常用的调试方法,当调试小程序时这种方法是有效的。具体做法是,从发现症状的地方开始,人工沿程序的控制流往回追踪分析源程序代码,直到找出错误原因为止。但是,随着程序规模扩大,应该回溯的路径数目也变得越来越大,以至彻底回溯变成完全不可能了。
3.原因排除法
对分查找法、归纳法和演绎法都属于原因排除法。
对分查找法的基本思路是,如果已经知道每个变量在程序内若干个关键点的正确值,则可以用赋值语句或输入语句在程序中点附近“注入”这些变量的正确值,然后运行程序并检查所得到的输出。如果输出结果是正确的,则错误原因在程序的前半部分;反之,错误原因在程序的后半部分。对错误原因所在的那部分再重复使用这个方法,直到把出错范围缩小到容易诊断的程度为止。
归纳法是从个别现象推断出一般性结论的思维方法。使用这种方法调试程序时,首先把和错误有关的数据组织起来进行分析,以便发现可能的错误原因。然后导出对错误原因的一个或多个假设,并利用已有的数据来证明或排除这些假设。当然,如果已有的数据尚不足以证明或排除这些假设,则需设计并执行一些新的测试用例,以获得更多的数据。
演绎法从一般原理或前提出发,经过排除和精化的过程推导出结论。采用这种方法调试程序时,首先设想出所有可能的出错原因,然后试图用测试来排除每一个假设的原因。如果测试表明某个假设的原因可能是真的原因,则对数据进行细化以准确定位错误。
上述3种调试途径都可以使用调试工具辅助完成,但是工具并不能代替对全部设计文档和源程序的仔细分析与评估。
如果用遍了各种调试方法和调试工具却仍然找不出错误原因,则应该向同行求助。把遇到的问题向同行陈述并一起分析讨论,往往能开阔思路,较快找出错误原因。
一旦找到错误就必须改正它,但是,改正一个错误可能引入更多的其他错误,以至“得不偿失”。因此,在动手改正错误之前,软件工程师应该仔细考虑下述3个问题。
(1)是否同样的错误也在程序其他地方存在?在许多情况下,一个程序错误是由错误的逻辑思维模式造成的,而这种逻辑思维模式也可能用在别的地方。仔细分析这种逻辑模式,有可能发现其他错误。
(2)将要进行的修改可能会引入的“下一个错误”是什么?在改正错误之前应该仔细研究源程序(最好也研究设计文档),以评估逻辑和数据结构的耦合程度。如果所要做的修改位于程序的高耦合段中,则修改时必须特别小心谨慎。
(3)为防止今后出现类似的错误,应该做什么?如果不仅修改了软件产品还改进了开发软件产品的软件过程,则不仅排除了现有程序中的错误,还避免了今后在程序中可能出现的错误。