调试是一门有待进一步深入研究的“艺术”,近半个世纪以来,人们在调试领域取得的成就微乎其微,结果因软件程序故障而陷入困境的项目比比皆是。本文提出的新型软件调试系统“断言调试系统(ADS)”可以将调试提升为现代工业流程,ADS将断言转化为足以引领程序开发革命的技术。
断言调试(Assertive debugging)是利用自带代码对程序进行监控并能确保嵌入式系统性能的新型调试方法。
调试是一门有待进一步深入研究的“艺术”……最有效的调试技术是那些在程序本身基础上设计并构建的技术。现在,许多最优秀程序员都利用近一半的程序对另一半程序进行调试;而用于调试的这一半程序最终将完全被摒弃。出人意料的是,这最终竟也能提高生产效率。 —节选自Donald Knuth的《计算机编程艺术(The Art of Computer Programming)》。
正如Don Knuth所述,调试经常被我们严重忽视,而我们也因此付出了惨重的代价。近半个世纪以来,我们在调试领域取得的成就微乎其微,结果因软件程序故障而陷入困境的项目比比皆是。因调试而浪费的时间和资源,在商业项目中,成本可能高达数十亿美元;在军事项目中,损失的不仅是金钱,甚至包括生命。这种现状简直让人无法忍受:我们必须探寻新的方法和途径。本文就提出了这样一种新方法。
本文提出的新型软件调试系统“断言调试系统(ADS)”可以将调试由次要的“艺术形式”提升为现代工业流程。ADS虽然利用了既有的思想,即John von Neumann于1947年首先提出的“断言”理论,但据我所知,ADS处理断言的方式却是Neumann或任何其他人从未提出的新方式:ADS更系统也更彻底地利用断言,而不像其他工具只在程序员想到的时候才使用。为此,ADS将断言由半个世纪以来一直漂浮不定且鲜有建树的理论转化为足以引领程序开发革命的技术。与Knuth的论述不同,ADS并不摒弃那部分用于调试的程序,而是将其作为程序主体补充的文档进行保存,这样,当程序需要修改时完全可以加以复用。
程序故障是主要的瓶颈
现在几乎不可能找到完全不需要编程运算的科学或工程项目,同样地,也很难找到不因程序故障原因而无法预期交付的软件。调试问题对几乎所有的项目都至关重要,而因软件程序故障带来教训也足够深刻:当客户对产品不满意时,我们会丧失业务;当产品迟迟无法推向市场时,我们的销售额会下降。随着我们在关键应用中越来越频繁地使用计算机,我们的教训也越来越惨重,这不仅关乎任务完成,甚至性命攸关。
在这些关键应用中,慎重地选择调试方法并加以证明变得越来越重要,甚至成为法律需要。对于那些高度依赖调试的应用而言,一半程序完全用于调试已日益无法忍受。ADS方法在编程的同时就能直接触及这些问题:这有助于开发人员缩短调试进程并支持软件对象的系统级和存档级调试。本文极力主张采用该方法,这或许有助于防止陷入困境。
调试的现状
调试发展历程中最值得关注的是,现代调试技术居然与半个世纪前刚刚进入现代计算时代时没有太大区别。我们仍然让故障程序运行至预测的关键点,然后停止运行并查看关键变量的状态。只要其中一个关键变量的值与预期的不同,我们就会努力分析为什么会造成这种结果。如果不知道什么地方出错,我们会重复执行这个过程,在程序更早的地方停下来。经过若干次反复,我们就能在充分接近程序故障的地方停下来,结果发现,故障的原因是:我们忘了重置某个计数器或清空某段内存、从而使某些数据结构产生溢出或犯了其他6种经典编程错误中的任意一种。
这就是上个世纪50年代中叶的软件调试方法,至今仍在沿用。如果时间允许且客户足够耐心,那么该方法仍将继续沿用下去,直至最终找到困扰的程序故障。但这种方法的缺点也很明显:通常只适用于一些特定情形,调试需要的时间也无法预知,而且并不能帮助程序员更好地理解程序调试或找到类似的程序故障。
什么是程序故障?
为了说明哪些程序故障才是真正麻烦的程序故障,即ADS专注解决的问题,这里简要地对软件故障进行了分类并说明了各类故障的严重性。这种分类本身并无任何新意,只是收集并组织了一些通用常识,然后以便于理解ADS的形式组织起来。这里,我们考虑的只是程序员产生的错误,而由硬件故障、操作员错误操作或其他不受程序员控制的条件引发的故障本身并不复杂,所以不在ADS处理的故障之列。程序员错误包括:
1. 算法设计错误。程序员或其客户错误理解了问题,因此解决问题的方法(即算法)即便完美无缺,也无法发挥效用。例如,程序员假定地球是个完美的球体,然后基于此计算人造地球卫星的轨道。他的错误本身与算法无关,但与他或客户对问题的理解有关。
2. 程序设计错误。尽管程序员对问题的理解及解决问题的方法都正确,但是在为解决方案设计程序时犯了错误。例如,他没有意识到计算机执行程序的时间比根据常规预测的时间更长。该问题与计算机相关:反映了程序员的理解能力,不与任何特定的计算机或编程语言相关,而与对通用计算的理解有关。
3. 程序实现错误。程序员在生成计算机执行的指令时出现错误。这类错误中,有以下两种变形:
a. 体系或语法错误。程序违背了程序开发工具规定的准则,但这种错误仅被这些工具捕获。 b. 独立或逻辑错误。程序本身没有错误,但无法运行结束或产生错误输出。程序员要么犯了机械错误(如拼写错误),要么使用了不能被开发系统捕获的类格式错误,要么更严重地在详细程序设计中犯下了逻辑错误,如疏忽了缓存刷新或在数据区外进行写操作(这时,显然程序开发工具出现了故障。这虽然不是这位程序员的错,却是另一位程序员的失误)。
类型1与算法无关:这些故障只是因为疏忽大意或愚钝而产生,因此也无须采取任何特殊补救措施。类型2与计算机相关,但也不是太棘手:这些故障显而易见,一般程序设计早期阶段就能发现,因此问题也不是太突出。类型3a现在已经得到了很好的解决,大多数现代程序开发系统能检测出所有的通用语法错误并精确地定位。有时,这些软件甚至还能纠错,例如一些专用于文字处理的程序就能自动地将“hte”纠正为“the”。 |