1. 调试
调试是执行一次成功的测试之后所要进行的工作。
调试包含了两个步骤:
-
确定程序中可疑错误的准确性质和位置。
-
修改错误。
2. 暴力法调试
暴力调试方法可至少被划分为三种类型:
-
利用内存信息输出来调试。
-
根据一般的”在程序中插入打印语句”建议来调试。
-
使用自动化的调试工具进行调试。
2.1. 内存信息输出
使用内存信息输出是最缺乏效率的暴力调试方法。原因如下:
-
难以在内存区域与源程序中的变量之间建立对应关系。
-
即使对于复杂程度较低的程序,内存信息输出也会产生数量非常庞大的数据,其中的大多数都是与调试无关的。
-
内存信息输出显示的是程序的静态快照,仅能显示出在某一个时刻程序的状态,为了发现错误,还需要研究程序的动态状态(随时间的状态变化)。
-
内存信息输出很少可以精确地在错误发生的地方产生,因此无法显示在错误发生时程序的状态。错误发生到输出内存信息这段时间之内程序执行的活动,可能会掩盖掉发现错误所需的线索。
-
通过分析输出的内存信息来发现问题的方法并不太多(因此很多程序员都是密切注视,急切地渴望着错误能神奇地从内存信息输出中自行暴露出来)。
2.2. 程序中插入打印语句
在失效的程序中插入输出变量值的语句,这种做法也不具有很强的优势。它有如下缺点:
-
它不是鼓励我们去思考程序中的问题,而主要是一种碰运气的方法。
-
它所产生的需要分析的数据量非常庞大。
-
它要求我们修改程序,这些修改可能会掩盖掉错误、改变关键的时序关系,或者会引人新的错误。
-
它可能对小型程序有效,但如果应用到大型程序,成本就相当高。况且对于某些类型的程序,如操作系统或过程控制软件,这种办法甚至无法使用。
2.3. 自动化工具调试
这个方法比上述的两个测试方法要好些,但是仍处于碰运气的测试状态。所以暴力测试法我们推荐的优先级是比较低的。
3. 归纳法调试
归纳法调试可以从细节转到全局,也就是从线索出发,寻找线索之间的联系。
归纳法调试的步骤如下:
-
确定相关数据,最好将所有可用的数据或症状都考虑进去。
-
组织数据。
-
作出假设。
-
证明假设。
-
解决问题。
4. 演绎法调试
演绎的过程是从一些普遍理论或前提出发,使用排除法和精炼的过程。
演绎法调试过程如下:
-
列举出所有可能的原因或假设。
-
利用数据排除可能的原因。
-
提炼剩下的假设。
-
证明剩下的假设。
-
修复问题。
5. 回溯法调试
从程序产生不正确的结果的地方开始,从该处观察到的结果推断出程序变量应该是些什么值。使用回溯法调试,可以确定程序中从状态符合预期值的位置点,到第一个状态不符合预期值的位置点之间的范围。
6. 测试法调试
当发现了某个被怀疑的错误的症状之后,我们需要编写与原先有所变化的测试用例,尽量确定错误的位置。
7. 调试的原则
调试原则包括定位错误的原则和修改错误的原则
7.1 定位错误的原则
-
动脑筋。
-
如果遇到了僵局,就留到稍后解决。
-
如果遇到了困境,就把问题描述给其他人听。
-
仅将调试工具作为第二种手段。
-
避免使用试验法——仅将其作为最后的手段。
7.2 修改错误的技术
-
存在一个缺陷的地方,很有可能还存在其他缺陷。
-
应纠正错误本身,而不仅是其症状。
-
正确纠正错误的可能性并非100%。
-
随着程序规模的增加,正确修改错误的可能性反而会降低。
-
应以是改正错误会引入新错误的可能性。
-
修改错误的过程也是临时回到设计阶段的过程。
-
应修改源代码,而不是目标代码。
8. 错误分析
详细的错误分析包括如下内容:
-
错误出现在什么地方?
-
谁制造了这个错误?
-
哪些做得不正确?
-
如何避免该错误的出现?
-
为什么错误没有早些发现?
-
该如何更早的发现错误?