第二十六章 调 试 414
第二十六章
第二十六章第二十六章
第二十六章 调
调调
调 试
试试
试
目录
目录目录
目录
26.1 概述
26.2 找错
26.3 修改错误
26.4 调试心理因素
26.5 调试工具
26.6 小结
相关章节
相关章节相关章节
相关章节
软件质量概括:见第 23 章
单元测试:见第 25 章
软件升级:见第 30 章
调试用于发现错误的根源并改正它,而测试恰好相反,它是用来发现错误的。对有些项目,
调试可占到整个开发时间的 50%。对许多程序员来说,调试是编程最为困难的部分。
调试其实并不是很困难的。如能遵循以下建议,调试是很容易的,你也只有少数错误要用
到调试。大多数错误是由于疏忽所致,通过检查源代码或通过调试程序进行单步调试是很容易
发现这种错误的。
26.1
26.126.1
26.1
概
概概
概
述
述述
述
最近,COBOL 语言的设计者 Rear Admiral Grace Hopper 常提到“bug”。这个词的历史,要
回溯到第一台大型数字计算机 Mark I 时代(IEEE 1992)。程序员发现一次电路故障是由一只进
入计算机内部的飞蛾所致,从这以后,计算机故障都被称为“bug(臭虫)”。
“臭虫”是一个生动的词,它是由以下图示小虫派生出来的。
第二十六章 调 试 415
将其用于软件缺陷中,就是当你忘了喷洒杀虫剂的时候,臭虫作为不速之客潜入了你的代
码中。它们通常可视为错误。软件中的一只臭虫意味着一个错误。错误后果却并不是上图所示
那样生动。它更可能同以下便条一样:
在本文中,代码中错误的技术名称是“错误”或“缺陷”。
26.1.1 调试在软件质量中的作用
调试在软件质量中的作用调试在软件质量中的作用
调试在软件质量中的作用
同测试一样,调试并不是提高软件质量的一种方法。它只用于改正错误。软件质量从项目
的开始便应确保。提高软件质量的最佳方法是遵循详细需求分析、有一个出色的设计、高质量
编码方法。调试为最终的一个不得已之举。
26.1.2 调试差别
调试差别调试差别
调试差别
为何要提到调试?是否人人都懂调试?事实并不是这样。对有经验的程序员的研究发现,
相同错误所需时间比从 20 到 1 不等。而 且,有些程序员不仅能迅速发现错误还能准确地改正错
误。以下是对至少四年工作经验的专业程序员调试一个含有 12 个错误的程序的效率的研究结
果:
最快的 3 个程序员 最慢的 3 个程序员
平均调试时间(分钟) 5.0 14.1
平均未发现错误数 0.7 1.7
平均产生新错误数 3.0 7.7
三个最好的程序员在调试中发现错误所用时间为三个最差程序员的三分之一,而所产生错
误数仅为最差程序员的五分之二。最好的程序员可发现所有的错误,并改正它们而不再产生新
的错误。最差的程序员漏掉了 12 个错误中的 4 个,而且在改正了 8 个错误后却引入了另 11 个错
误。许多其它研究也已经证实了这种大的差别。
以上例子除了使我们对调试有一个深入的了解,也可证明软件质量的基本原则:提高开发
质量可降低开发消耗。最好的程序能发现最多的错误、最快地发现错误,也能最经常地进行错
误改正。你不必在质量、代价和时间上作出选择——它们是相互影响的。
第二十六章 调 试 416
26.1.3 使你有所收获的错误
使你有所收获的错误使你有所收获的错误
使你有所收获的错误
错误意味着什么?如果你并不完全明白你的程序将作何用时在程序中会出现错误。如果你
并不十分清楚你将告诉计算机去做些什么,你就不过是在尝试不同的事物。如果你是靠尝试来
编程,产生错误是必然的。你不必清楚怎样去改错,你应学会怎样一开始就避免它们。
大部分人是易犯错误的,然而,你可能是一个有着深刻洞察力的优秀程序员。如果是这样,
你程序中的错误对你是一个很好的机会:
从中对程序加深了解
从中对程序加深了解从中对程序加深了解
从中对程序加深了解。如果你已经改正了错误,你能从程序中学到一些东西。
了解错误类型
了解错误类型了解错误类型
了解错误类型。你编写程序并在其中引入了错误。但不是每天每个错误都是清楚可见的,
终有一天你有机会发现某个错误。一旦你发现了某个错误,你应问问自己为什么会产生此错误?
你怎样更迅速地发现它?怎样预防错误的发生?是否还有类似错误?你是否能在其引起麻烦之
前改正它?
从别人的角度了解代码质量
从别人的角度了解代码质量从别人的角度了解代码质量
从别人的角度了解代码质量。为了发现错误你不得不阅读代码。这就为评估你的代码性能
提供机会。是否代码易于阅读?能否提高代码质量?使用你的发现提高以后所编代码的质量。
了解解决问题的方法
了解解决问题的方法了解解决问题的方法
了解解决问题的方法。解决问题的方法是否能使你有信心?是否寻找工作的方法?是否能
迅速发现错误?是否能迅速调试并发现错误?是否有痛苦和受挫折感?是否常胡乱猜测?是否
需提高解决问题的能力。考虑到许多项目花费在调试上的时间量,如你能仔细观察调试的进行,
你将不会浪费时间。花时间分析和改变调试方法,可能是减少开发一个程序所需时间量的
最佳途径。
了解如何改正
了解如何改正了解如何改正
了解如何改正。除了会发现错误之外,你应懂得如何改正错误。使用 goto 手段只能补偿症
状而不是修改问题本身。你是否能容易地改正错误?或通过对问题的精确诊断和对症下药,你
是否对问题有了系统的修正?
考虑了各种可能后,调试是培植你自身进步的异常肥沃的土壤。它是所有创建道路的交叉
路口(可读性、代码质量)。这也是对创建好的代码的报答——尤其是代码质量好到使你无需经
常调试。
26.1.4 一个有效的方法
一个有效的方法一个有效的方法
一个有效的方法
不幸的是,院校里的编程人员很少提供调试程序的结构框架。如果你在学习编程,你可能
已遇到过对调试的讨论了。虽然作者受到了很好的计算机教育,作者所接受的调试建议是“将
程序中插入输出语句以检查错误”。这并不是完美的。如果别的程序员所受教育和作者本人类似
的话,许多程序员将不得不发明自己的调试方法。这是一种浪费!
26.1.5 调试的误区
调试的误区调试的误区
调试的误区
在 Dante 看来,地狱底层是为魔鬼准备的。在现代社会,过时的方法有可能让不懂如何进
行有效调试的程序员如同进入了地狱最底层一般。使用以下几种调试方法可能使程序员备受痛
苦:
靠猜测发现错误
靠猜测发现错误靠猜测发现错误
靠猜测发现错误。为了发现错误,在程序中胡乱插入输出语句,以便检查错误之所在。如
果用输出语句仍不能发现错误,再尝试程序中的其它地方直到有了一点眉目。甚至不回到程序
的最初版本上,也不用各种修改记录。如果你并不清楚程序在干些什么,编程是令人痛苦的。你
第二十六章 调 试 417
应存一些可乐饮料之类的东西,因为你在到达终点之间将是漫漫长夜。
不花费时间理解问题
不花费时间理解问题不花费时间理解问题
不花费时间理解问题。由于问题是试验性的,认为无需完全理解就能改正它,甚至简单地
认为发现它就足够了。
用最为明显的方式改正错误
用最为明显的方式改正错误用最为明显的方式改正错误
用最为明显的方式改正错误。你应改正你所发现的特定问题,而不是去作一些大的模糊不
清的修改,否则可能会影响整个程序,以下是一个较好的例子:
X = Compute( Y )
if( Y = 17 )
X = $25.15 { Compute()doesn't work for y = 17,so fix it}
你只需在明显的地方加入一个特殊用例,不要 为 了 17 这个值的问题而进入 Compute()函
数的内部。
对调试的迷信
对调试的迷信对调试的迷信
对调试的迷信。魔鬼已为迷信调试者在地狱留出了部分地方。每组中都有一个程序员可能
为无尽头的问题所困扰:可恶的机器、神秘的编译错误、当月圆时才出现的语言错误、坏数据、
丢失重要的修改、编辑程序不正确地保存程序。这就是“编程迷信”。
如果你所编程序出现了问题,这是你自己的过错。这不是计算机也不是编译程序的过失。
程序本身不会作某些事情。它不会自己编写自己,而是你编写了它,所以你应对它负责。
即使一个错误刚开始似乎不是你的过失,但是你应仍有兴趣弄清楚是否真是这样。这有助
于调试,你想找到代码中的错误是困难的,而当你认为你的代码无错时则更是困难。当你宣称
某人的代码中存在错误,其它程序员会相信你已对问题进行了仔细检查,这样可能增大你言行
不一致的缺点。假设错误是自己的,可使你免受宣称某个错误是别人,而最后发现是你的而不
得不改口的窘迫处境。
26
2626
26.
..
.2
22
2
找
找找
找
错
错错
错
调试包括发现和改正错误。发现错误(并理解错误)将化费 90%的调试时间。
幸运的是,为了找到一种比胡乱猜测更好的调试方法,你无需跟魔鬼有任何关系。和恶魔
期望的恰恰相反,边思考边调试要比随意调试更有效和更令人感兴趣。
假定你被要求调查一件凶杀案。哪一种方法更使人感兴趣呢?:挨家挨户检查并询问每个
人在 10 月 17 日晚上都干了些什么?或从所发现线索推断凶手的特征?大多数大都倾向于推断
凶手的特征,而大多数程序员则发现合理的调试方法更令人满意。甚至,低效程序员中二十分
之一的程序员对如何改错也不是任意猜测的。他们使用的是科学调试方法。
26.2.1 科学调试方法
科学调试方法科学调试方法
科学调试方法
以下是你使用科学调试方法的步骤:
1.通过重复实验收集数据
2.建立假设以解释尽可能多的相关数据
3.设计实验以便证实或否定假设
4.证实或否定假设
5.按要求重复以上步骤
以上过程和调试有着对应的关系。以下是发现错误的有效方法:
第二十六章 调 试 418
1.固定错误
2.确定错误源
3.改正错误
4.测试修改
5.寻找类似错误
以上第一步和科学方法的第一步相似之处在于它依赖于重复性。如果你能使某一错误可靠
地发生的话,你也就能方便地确诊它。以上第二步则利用了科学方法的所有各步。你收集导致
错误的数据,分析所产生的结果,然后形成对错误源的假设。你设计测试用例并检查以便能评
估假说,然后你就能恰如其分地宣告你的成功或重复进行你的工作。
让我们通过一个具体例子看一看以上各步。
假定你有一个不时出错的雇员数据库程序。这个程序按升序打印出雇员表及其收入:
Formating,Fred Freeform $5,877
Goto,Gray $1,666
Modula,Mildred $10,788
Many-Loop,Mavis $8,889
Statement,sue switch $4,000
Whileloop,Wendy $7,860
所出现错误是 Many-Loop,Mavis 和 Modula,Mildred 的结果不对。
固定错误
固定错误固定错误
固定错误
如果某一错误不是可靠地发生,对其进行诊断是不可能的。使一个间歇性错误能定期发生
是调试程序最富有挑战性的任务之一。
不定期发生错误通常是由于未对变量进行初始化或使用了悬挂指针。如果某一数有时错有
时对,这可能是计算中所涉及到的某个变量没有被正确地初始化——大多数情况下此变量初始
值被置为 0。如果你使用了指针,并且所发生的现象是奇怪的和不可预测的,你可能使用了一
个未初始化指针,或对已分配的存储器单元使用了指针。
固定一个错误通常需要一个以上的测试用例来产生错误。它包括将测试用例减至最少而仍
能产生错误的情况。如果你所在组织有专门的测试组,有时使测试用例更为简单是测试组的工
作。但在大多数情况下,这是你自己的工作。
为了简化测试用例,你引入了科学的方法。假定你有 10 种可用于组合和产生错误的因素。
对产生错误的非相关因素建立假说,改变假想非相关因素,然后返回测试用例。如果你仍然发
现错误,便可排除这些因素简化测试。这样你可试着进一步简化测试。如果你未曾发现错误,
你可否定这些特定假说,这样你便能明白更多的东西。也可能是一些微妙的变化仍将产生错误,
但是你最少能明白一个不产生错误的特定修改。
在雇员收入程序中,当最初运行程序时,Many-Loop,Mavis 是列在 Modula,Mildred 之后。当
程序第二次运行时,列表是正确的:
Formating,Fred Freeform $5,877
Goto,Gary $1,666
Many-Loop,Mavis $8,889
Modula,Mildred $10,788