《编程匠艺》读书笔记之七
第九章 寻找缺陷
- bug是构建软件时不可避免的黑暗面,是生活的一个朴素的真相。
- 在很多情况下,犯错误只是因为没有专注于正在做的事情,大多数bug都是粗心大意造成的。
软件的bug可以分为以下几个主要的类别: - 编译失败。这是你遇到的错误类型中最好的一种,检测到缺陷所需的时间越长,修正它们的成本就越高。
- 运行时崩溃。
- 非预期的行为。这是真正难以处理的错误——你的程序并没有崩溃,而只是准备跳下悬崖。
运行时错误可以分为以下几类: - 句法错误。为了避免这种错误,最简单的方式就是将所有的编辑器警告都打开。
- 构建错误。构建错误虽然本省不是运行时缺陷,但是却只在运行时出现。明智的做法是对项目进行彻底的清理,然后从头开始重新构建它。
- 基本的语义bug。这种bug一般都可以使用静态分析工具发现。
- 语义bug。
常见的语义缺陷包括: - 段错误。也称为“保护缺陷”。源自程序访问那些并没有分配给它们的存储单元,涉及指针的输入错误或糟糕的指针算法,都非常容易造成这种错误。
- 内存溢出。由于写入哪些已为你的数据结构分配的内存造成的。内存溢出是一个常见的问题,很难检测到,尽可能使用安全的数据结构,以使你与这种灾难的可能性绝缘。
- 内存泄露。在哪些不具有垃圾回收功能的语言中常常发生。如果你忘记释放内存,你的策划那个徐将慢慢消耗掉越来越多的稀缺的计算机资源。
- 内存耗尽。
- 数学错误。
- 程序暂停。一般归因于糟糕的程序逻辑。
- 有些程序员天生就能够更加专注的执行任务,他们可以一遍将精力集中在正在编写的代码的细节上,一边还在头脑中保持全局的观念。这就是调试的艺术,它在很大程度上是一种需要学习的技能。
- 有太多的程序员试图通过胡乱的修改代码来修正缺陷,而从不认真的思考他们正在做什么,这样做的后果极少是有用的。
- 为“松散”的调试设定一个合理的时间限制,如果调试没有成功,就采取更加系统的方法。
- 即使缺陷很容易找到,如何修正它也不一定是显而易见的。
- 一种系统化的经过深思熟虑的调试技术认为移除bug分为两个不同的方面:找出造成bug的缺陷;对缺陷进行修正。
调试时的一些原则: - 查找一个缺陷的难易程度,取决于你对隐藏这个缺陷的代码的熟悉程度。
- 调试的难易度还取决于你对执行环境的控制能力,即你对运行中程序有多大的控制权,以及是否可以方便的查看它的状态。
- 不信任任何人的代码,并且抱有一种健康的怀疑态度。
如何搜寻bug,可以分为以下几种情况: - 编译时错误。当你的构建失败时,先看看第一个编译错误,这个错误的可信度要远远高于其后的消息。
- 运行时错误。如果你的程序中包含一个bug,那么很可能是你的代码中某个你认为成立的条件其实并不成立,唯一合理的方式是系统的执行这个过程。
- 调试是一项系统的工作,需要慢慢的接近缺陷所在的位置。不要把它看做是一个简单的猜谜游戏。
- 确定故障。调试将从你注意到程序没有执行它应该执行的操作开始。
- 使故障重现。确定缺陷位置的第一步,是查明如何可靠的使它再次出现。
- 定位缺陷。仅仅因为故障出现在某个模块中,并不一定表示这个模块有问题。从你知道的地方开始,例如程序崩溃的位置,然后从那里沿控制流向后查找发生故障的原因。
- 理解问题。缺陷通常都很隐蔽,代码会执行它应该执行的操作,以及那些你在编写它的时候认为它会执行的操作。当你认为你已找到一个bug的原因时,彻底的研究它以证明你是正确的,不要盲目的接受你最初的假设。
- 创建测试。编写一个可以证明存在这个故障的测试用例。
- 修正缺陷。
- 证明你确实修正了缺陷。只有在证明问题已经被解决之后,你才算完成了调试。
- 如果没有成功,你会发现向别人述说整个问题会对你有帮助。
- 修正缺陷时,要特别小心,不要冒在进行修改时破坏其它代码的风险。
- 确保你是真的找到了问题的根本原因,而不是隐藏了又一个征兆。
- 在修正一个bug时,检查一下,确定相关的代码部分是否存在相同的错误,永久的根除这个bug。
- 每修正一个缺陷,都要从中吸取教训,你怎样才能预防它的出现,怎样才能更快的发现它。
- 控制bug的最好的方式就是不引入它。优秀的编程就是遵循章法和注意细节,全面的测试可以防止缺陷隐藏在你发布的软件中。
我们在进行bug修正的过程中,可能会使用到的工具包括: - 调试器。调试器是一个交互式的工具,它允许你观察正在运行的程序的内部。与在调试器中摸索相比,对故障多进行一些思考,可能反而会更快的找到缺陷。当你遇到你无法解释的行为时,有节制的使用调试器,不要不停的使用调试器而不去理解你的代码是如何运行的。
- 内存访问校验器。用于检查你在运行的程序是否存在内存泄露和溢出。
- 系统调用跟踪。用于显示一个应用程序所发出的所有系统调用。
- 内核转储。用于表示当一个程序异常退出时,由操作系统生成的对该应用程序的快照。内存转储包含崩溃时程序在内存的副本、CPU寄存器的状态以及函数调用堆栈。
- 日志。程序的日志保存了程序活动的历史记录,可以帮助查明引发故障的外部情况。
- 静态分析器。用于检查你的源代码是否存在潜在的问题。
- 不要使用调试器,直到你确切的知道你需要从它获取什么消息为止????
以下步骤可以很好的避免内存泄露: - 使用可以使你遇到这种缺陷的可能性最小的语言。
- 使用“安全”的数据结构来管理内存。
- 使用很有帮助的语言习惯。
- 在处理内存时,要严密并且系统。
- 通过内存验证器工具运行你的代码,以确保没有残留任何bug。
- 调试是一种你需要逐渐开发的技术,它并不依赖于猜测,而是依赖于系统的检测和经过深思熟虑的修复。
- 优秀的程序员:1. 不培养bug,他们谨慎的编写代码,预先防止了bug的引入;2. 清楚他们的代码如何运行,并编写细致的测试以确保其代码不会被轻易的破坏;3. 谨慎并系统的寻找bug,而不是没有计划就轻率的开始;4. 了解自己的局限性,并且会在遇到困难的时候请求别人来帮助查找缺陷;5. 仔细的修改代码,即使实在进行一个“简单”的修复。
- 糟糕的程序员:1. 不进行调试,他们四处出击,沉没在糟糕代码的海洋中;2. 大部分时间都在调试器里度过,想搞清楚他们的代码在做什么;3. 遇到故障就试着把它隐藏起来——他们会主要避免进行调试;4. 对他们代码的质量和修正缺陷的能力有着不现实的预期;5. 通过隐藏征兆来“修改”bug,而不是跟踪问题已找到真正的原因。
作者:李潘
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。