Why Programs Fail-系统化调试指南 (德)_读书笔记

来源:互联网 发布:男性疾病网络咨询 编辑:程序博客网 时间:2024/06/05 19:27

1 调试

调试过程的七个步骤:

1.在数据库中跟踪问题。

2.重现故障。

3.自动化和简化测试用例。

4.寻找可能的感染源。

5.专注于最可能的来源:已知的错误状态;代码和输入中的来源;反常;代码的坏味道。

6.分离感染链。

7.修正缺陷。

软件问题的生命周期——从问题的首次出现到它被修正

1.用户把问题提交给软件发布者。

2.发布者重现问题。

3.发布者分离问题环境。

4.发布者在本地定位和修正缺陷。

5.发布者把修正交付给用户。

问题报告——变更请求(CR)——bug报告

重现问题所需的信息被称为问题报告(PR),有时也叫做变更请求(CR)或简单称作bug报告。

为了得到适量的信息,通常的方法是创建一个列表,当撰写问题报告时,都应该包括该列表中的特定信息。该列表一般应该包含以下内容:

1.产品的发布版本:即产品的版本号或其他唯一标识。开发者可以利用这些信息重建该程序的本地拷贝,使该拷贝的版本和用户环境中安装的版本完全一致。

2.操作环境:通常是操作系统的版本信息。如果问题是由第三方组件的交互引发的,有关这些组件的信息也是很有用的。

3.系统资源:有关问题只在资源不足时才会出现。因此,建议在报告中包含系统内存、磁盘空间或CPU负载的相关信息。

4.问题历史:描述重现问题所必须执行的最少的一组操作步骤。通常情况下,还应该包含任何需要访问的资源,例如输入信息和配置文件等。

5.预期行为的描述:从用户的角度描述程序的行为应该是什么。

6.观察到的错误行为的描述:描述问题的症状,和预期行为不同的程序的实际行动。当把错误信息提交给开发人员时,一定要保持中立。任何幽默、讽刺或者攻击都会使开发人员偏离提高软件质量这个目标。一切要以事实为依据。

7.一句话总结:总结时要抓住问题的本质——它对于用户的影响。通常情况下这是确定问题严重性和问题修正的优先级的依据。

一些产品包含用来产生标准问题报告的特定功能或单独的工具。例如:记录发生故障那一瞬间的程序状态的核心转储core dump)信息,这种核心转储信息可以被开发人员阅读和检查。

核心转储:通常这个词的含义是一个动作,这个动作在系统收到特定的信号时由操作系统完成。信号可以由程序执行过程中的异常触发,也可以由外部程序发送。动作的结果一般是生成一个某个进程的内存转储的文件,文件包含了此进程当前的运行堆栈信息。这种内部信息会有极大的风险,可能会侵害用户隐私权。

如果程序在处理了一长串事件之后才发生错误,用户通常很难回忆起从程序启动到发生错误之间的所有步骤。因此,程序可以用一个日志文件来记录重要的事件,随后用户可以把这些日志文件转发给软件发布者,并由他们检查和重现问题。包含用户交互过程的日志文件也会被滥用已达到各种各样的目的,例如,第三方攻击等。基于以上原因,用户应该被告知你的产品将会收集并转发哪些信息。并且用户应该有权关闭所有记录功能。

2 跟踪问题

2.1 问题分类

提交一个问题报告,就必须提供所有必要的信息并把问题分类。以下列出了分类问题的各种属性,这些属性适用于大多数问题跟踪系统。

2.1.1 严重程度

每个问题都应该指定严重程度,用来描述该问题对开发或发布过程的影响。按照对用户的影响从最大到最小排列:

  • 阻塞(Blocker):问题会阻碍开发和测试工作的进一步进行,这种最高级别的严重性也被称为项目中断问题
  • 关键(Critical):关键问题会造成奔溃,数据丢失或严重的内存泄漏。
  • 重要(Major):主要功能的遗漏。
  • 常规(Normal)标准问题。
  • 次要(Minor):次要功能的遗嘱,或者可以使用简单的变通方法解决的问题。
  •  琐碎(Trivial):文字拼写错误、代码没有对齐等表面问题.
  •  增强(Enhancement):请求增强功能,意味着该问题根本不是故障,而是系统添加的新功能,但是不能和遗漏的功能相混淆。

理想情况下,只有修正了所有的严重问题,产品才能被发布——即必须解决所有重要、关键或阻塞问题,满足所有的需求。如果产品必须在某一个固定的时间发布,可以暂时屏蔽某些依然存在问题的可选功能。

2.1.2 优先级

每个问题都必须分配一个特定的优先级,优先级高的问题要尽快处理。优先级通常由管理人员确定,这是管理人员表达什么事情应该优先处理、什么事情可以延后处理的主要方式。优先级的重要性在于合理地控制开发进程,而不是过分地强调解决问题。

2.1.3 标识

每个问题都有一个唯一的标识——编号,开发人员在调试过程中可以使用该标识代指某个问题,例如:在E-mail、修改日志、状态报告和附件中。

2.1.4 注释

每个用户和开发人员都可以在问题报告中附加一些注释,例如:添加有关问题环境的信息、推测可能的问题起因、添加初步的结论或讨论如何修正问题。

2.1.5 通知

开发人员和用户可以在问题报告中附上邮件地址,每当问题报告发生变化,他们就会自动得到通知。

2.2 处理问题

假设有人在问题数据库中提交了一个新的问题报告,就必须立即处理它,整个处理过程就是问题报告的生命周期——从未确认一直到关闭。问题报告的状态决定了它处于生命周期的哪个位置。

  • 未确认(unconfirmed):任何一个刚提交进数据库的问题报告都处于该状态。
  • 新问题(new):该问题报告被确认有效:它包含相应的事实;它和已知的问题没有明显的重复。
  • 已分配(assigned):问题还没有被解决,但是已经分配给相应的开发人员。
  • 已解决(resolved):问题已经被解决。解决状态:已修正(fixed)无效(invalid,该问题不是真正的问题)重复(duplicate)无需修正(Won't Fix,最常见的情况可能是:问题最终确认是系统特性,而不是故障)无法重现(Cannot Reproduce)。
  • 已验证(verified):问题已被修正,并且也被证明是成功的。
  • 已关闭(closed):产品的新版本(或补丁)已经发布,在这个版本中问题不会再出现。
  • 重现开启(reopened)::前提是这个问题再次出现。

3 让程序出错

在调试程序前必须首先配置好程序的测试环境——即,带着使程序出错的意图运行程序。

3.1 调试测试

用户报告并不是了解问题的唯一手段。通常,大部分问题是在开发过程中通过测试发现的,用户根本不会发现这些问题。带着制造问题的意图运行程序的过程被称为测试。这种有关测试的传统观点被称为【确认测试】。

调试测试的步骤:

1 创建测试用例,以重现问题。

2 在调试过程中多次运行该测试用例。

3在新版本发布之前运行该测试用例,防止故障再次发生。

在调试过程中需要多次运行测试用例,所以要尽可能地采用自动化测试。

3.1.1 关于测试的基本原则

  • 规格说明:程序不可能自己证明其正确性,它只有遵循了描述其目标的规格说明书才能被认为是正确的。更准确规范的说,规格说明书描述了程序的所有行为,甚至包括异常行为。
  •  尽早测试:该原则建议不要等到整个系统集成之后才测试。而是完成一个单元后就运行相应的测试用例,从而保证你的系统是由仔细测试过的单元集成起来的。
  • 优先测试:在实现单元之前就编写测试用例。因为测试用例可以被看做是规格说明书。
  •  经常测试:至少要对系统的每个发布版本运行一遍测试用例。如果希望做的更好一点,应该在每次系统发生变化时运行一遍测试用例。通过自动化可以很好地实现经常测试。
  • 充分测试:衡量测试的覆盖程度。即有多少语句和分支被实际进行了测试,可以使用工具处理代码,已得到测试的覆盖程度,并且仔细设计测试用例使其达到足够的覆盖程度。
  •  专人测试:测试未知问题是一个否定性的工作,开发人员就不适合测试自己编写的代码,因此应该请无关的人员来测试你的程序,因为他们更容易持有客观的批评态度。

4 重现问题

调试过程的第一步就是重现当前的问题,即,创建一个测试用例使程序以某种特定的方式发生故障。

1重现程序运行的环境

2 重现程序运行的过程:重现数据、重现用户交互、重现通信、重现时间、重现随机性(游戏和加密程序)、重现操作环境、重现调度(线程和进程调度引入的不确定性)、物理影响、调试工具的影响

3 重现系统交互

5 简化问题

重现了问题之后,下一步就是简化它,即,找出与该问题无关的环境因素,并忽略它们,其结果就是得到一个只包含相关环境因素的测试用例。理想情况下,简化的测试用例产生的报告能立刻精确地定位缺陷。

简化测试用例不仅仅有助于查找故障起因,还能带来三个优点:

  • 简化的测试用例更易于交流:测试用例越简单,把它写下来、阅读其描述。以及重现它所花的时间就越小。
  • 简化的测试用例更有助于调试:典型地,简化的测试用例意味着更少的输入,所以只需要检查更简短的程序运行状态。最好的情况下,简化后的HTML标签应该能直接引发错误。
  • 简化的测试用例能标识出重复的问题报告:简化的测试用例可以使我们很容易地识别出几个仅仅是无关细节不同的重复问题报告。

6 科学调试

一种使调试过程清晰并缓解记忆压力的简便方法就是写下所有的假设和观察结果—即,记录日志。

LOG4J的核心原理是一个能接受日志请求,并进行日志记录的组件。每个日志对象(logger)。日志对象是一个能接受日志请求,并进行日志记录的组件。每个日志对象包含一个级别(level),从调试(DEBUG)、信息(INFO)、警告(WARN)、错误(ERROR)直到致命(FATAL)。以上这些级别的消息可以通过调用相应的日志类方法进行记录(debug()info()...fatal())。

 

 

阅读全文
0 0
原创粉丝点击