`

防御式编程

 
阅读更多

 

文章很好,转载于 http://www.cnblogs.com/HappyAngel/archive/2012/12/23/2829644.html

这里借花献佛,给远方的你和我

 

 

在软件开发过程中,不可避免的会遇到错误处理,而且这部分对于整个软件的健壮性有非常大的作用,它是软件除了功能性以外最重要的指标了,一个软件成功与否与其健壮性有很大的联系。我在以前的开发中也时常思考错误处理,因为这部分代码逻辑比较不容易梳理清楚。以异常的处理为例,以前通常就采用比较简单粗暴的处理方式:用try..catch加Exception把所有异常都包起来,这样简单省事,写的代码最少,相信很多童鞋曾经跟我一样写过这样的代码,很明显,这样写有很大的问题,最主要的问题在于:

  • Exception会吃掉所有可以处理的异常,使得对于某些我们关心的异常无法捕获,因为对于不同的异常我们可能需要做不同的处理,有些可以在本函数内处理掉,有些需要提示用户(例如文件不存在,网络无法访问),有些需要告诉上一层代码该如何处理,所有这些在直接用Exception处理异常时都无法做到,简而言之就是无法做到异常的精细化处理

那么怎么做才好呢?这部分代码真不少,虽然无关软件功能性,但是确是健壮性的基础。具体如何处理这些没有完全标准的答案,软件设计本来就是一项带有艺术色彩的智力劳动,没有一劳永逸的解决方案,最关键的在于掌握好基础知识,因地制宜地采取措施。下面主要谈谈实现健壮性的基本技术,基本的实现软件健壮性的技术有以下几种:

  • 断言
  • 错误处理
  • 异常
  • 从设计上简化异常处理的技术;隔离程序
  • 辅助调试的代码(print打印之类的小段函数)

1 断言

断言是个非常常用的软件设计技术,基本上现代的高级程序设计语言都支持它,那么断言到底是什么?很简单,就是一个判断一个布尔表达式的语句,如果这个布尔表达式为真,不会有任何效果,但是如果为假,根据不同实现技术会出现不同的效果,但是基本上都是会告诉程序员(注意,不是用户),这里有一个断言,去看一下。

上面是一个简单的形式化的定义,从定义中我们不难看到,断言是给程序员看的,为什么?用来查找bug。所以我们应该在内部逻辑的问题上使用断言去检查一些理论上不可能发生的情况,因为如果发生了就说明内部逻辑有问题,也就是有bug了。

举个简单的例子,例如某个函数有一个参数,这个参数是某数据流,这个数据流是软件下层通过读取文件传进来的,调用这个函数的时候,内部逻辑已经确定是正确读取到了文件,否则是不会调用这个函数的,那么,一般会在函数开头,对这个参数用断言加以检查,如果不幸,出现问题,就说明内部逻辑错了(读取失败仍然调用?内存被意外析构?),这就是典型的通过断言查找程序bug的例子。

有一点需要注意,断言是用来检测程序内部逻辑的,如果是和外部有数据交流,就不是断言的范畴,因为外部的情况,程序是不能假定的,既然不能假定,就无法设断言了,那应该是错误处理或者异常的范畴了,因此,理解断言的关键点在于,作用于内部逻辑,用来查找bug。通常,现代编译工具都会在编译release版本软件的时候去掉异常,因为异常是给程序员看的。

2 错误处理

错误处理可以说是软件健壮性的核心,程序员在编写软件的时候,应该尽可能的预测到可能发生的错误,并对这些错误进行处理,正常情况下要对这些错误进行分类,

  • 重大错误,这类错误一般不可恢复,通常的做法都是报告后直接退出,类似windows中的蓝屏,普通程序在遇到堆栈溢出,内存不足等错误时也是会这样做;
  • 无关用户的一般性错误,这类错误一般情况下不会导致程序退出,而且和用户没有直接的联系,这时最好的做法是能自动恢复并解决,如果不行,可以写入日志,以便以后进行排查,不过通常情况下需要用相对抽象的语言告诉用户(例如,程序遇到问题,可能是某些文件找不到),只是为了让用户知道这个操作没有成功,具体的技术原因可以写入日志。
  • 与用户相关的一般性错误,这类错误通常是由于用户输入错误数据引起,例如本来程序UI需要用户输入年龄,结果用户输错,填入的不是整数。这个时候,通常需要告诉用户,让用户重新输入,以达到自动恢复的作用。所以通常的做法,都是弹出对话框(有UI)或者输出提示到标准输出(无UI);

理解错误处理的关键在于分清楚项目需要处理错误的类型,以及如何处理(集中处理?写入日志吗?通过网络提交错误报告?),要根据项目的类型设计好采取的策略(例如Service一类的通常都是只记入日志(会有各种日志,函数调用日志,错误日志,性能日志等等),因为不直接和用户打交道),具体情况具体分析地设计错误处理策略,并对不同的错误采取恰当的处理方式。

3 异常

异常是指程序无法预料到的情况引发的错误,通常本函数不知道这种错误该如何处理需要让调用方决定(例如系统库函数,像.NET的库函数都会有抛出异常的列表)。这通常是由语言支持的,在遇到异常而又没有捕获时,会中断本函数的执行去查看调用方是否处理,这就有了一种直接中断函数处理的方式,有人会说为什么不直接return呢?是的,return可以达到中断函数执行,但是却无法像异常那样让调用方针对特定的异常做出特定处理,毕竟return的东西有限,无法表示错误的类型,通常都只能返回一个false。

以.NET的CLR对异常处理机制(两轮遍历)为例:

  • 发生异常后,CLR先去在引发异常的那一层搜索catch语句,看看有没有兼容此类型异常的处理代码,如果没有,则跳到上一层去搜索,如果还没有,则再上一层,直到应用程序的最顶层,此即为第一轮,查找合适的异常处理程序。
  • 如果在某一层找到了异常处理处理程序,CLR不会马上执行,而是回到事故现场再次进行第二轮遍历,执行所有中间层次的finally语句块。

可见,异常的出现使得我们对于无法在本函数(局部)处理的错误提供了一种强大的手段,使得我们能够清楚的告诉函数调用链的上层,某函数发生错误了,需要处理。所以,理解异常,就要知道它是处理无法在本函数处理的错误,同时,一般情况下不要用Exception吃掉所有的异常,而要对异常进行精细化处理。但是也不是完全不用它,因为没有处理的异常通常会导致程序直接崩溃,这对用户非常不友好,所以处理异常要特别谨慎,我通常会在函数调用链的顶层使用Exception,并计入日志,以防止这一情况的发生。

4 隔离程序以简化错误处理

这是一种在设计上简化错误处理的策略,事实上,如果所有的代码都做异常和错误处理,会使代码变得臃肿,可读性下降,我们需要在高层次上面避免这种情况的发生,这个思想来自代码大全,不过实际开发中也已经用到了,这里做个总结。我比较同意这种设计思想,本质上,它是将错误和异常处理集中化,通常的软件设计实际上都是对数据进行处理和再加工,以及展现,很大一部分的错误都是由于不正确的数据设置导致的,那么我们可以把数据的错误处理专门用一层来处理以使得内部的逻辑可以不用对数据进行检测,见下图:

 

image

上图很清晰的说明了这一过程,简而言之,就是专门增加了一层来专门处理数据,以解放内部逻辑,这样结构更加清晰

5 总结

我始终认为软件的好坏与其健壮性有很大的联系,所有的软件开发人员都要对它有足够的重视,从一点一滴开始做起,不要忽视任何的细节,不能盲目依赖测试去发现bug,而是以测试驱动编程,不断地思考可能发生的问题以进行预防,这才是防御式编程

分享到:
评论

相关推荐

    防御式编程 Defensive Programming.PPT完整版(精品课件)

    防御式编程 Defensive Programming.PPT完整版(精品课件) 大纲: 保护程序免遭非法输入数据的破坏 断言 错误处理技术 异常 隔离程序 辅助调试代码

    防御性编程之三

    防御性编程是一种软件开发策略,旨在提前预测并防止潜在错误,从而提高软件的稳定性和可靠性。在本主题“防御性编程之三”中,我们将会深入探讨如何在C++编程中利用源码和工具来实现有效的防御性编程。这篇博客文章...

    代码大全之编程基本功

    这些技术覆盖了代码的组织与布局、基本编程习惯以及防御式编程等核心主题。通过深入理解这些知识点,可以帮助开发者编写出更稳定、更易于维护的代码。 #### 核心知识点详解 ##### 一、防御式编程(Defensive ...

    微软_编程精粹

    - **防御式编程**:重点讲解了防御式编程的概念,包括如何在设计子系统时考虑各种可能的异常情况。 - **安全策略**:探讨了如何通过合理的输入验证和边界检查等手段来防止外部攻击或数据损坏。 4. **第4章 对程序...

    Hadoop专业解决方案-第5章开发可靠的MapReduce应用.docx

    【描述】:本章专注于提升MapReduce应用的可靠性,通过MRUnit创建单元测试,进行本地实例测试,理解和调试MapReduce,以及采用防御式编程策略处理可能出现的问题。 【标签】:技术方案 【正文】: 在开发...

    哈工大hit软件构造lab4实验报告

    实验报告的标题是“哈工大hit软件构造lab4实验报告”,主要涵盖了异常与错误处理、防御式编程以及测试和调试三个核心主题。这是一份针对2020年春季学期计算机学院《软件构造》课程的实验内容,旨在让学生深入理解...

    编程精粹.pdf

    - **第3章:为子系统设防** - 探讨了如何通过防御式编程来提高系统的健壮性和可靠性。 - **第4章:对程序进行逐条跟踪** - 指导读者如何进行详细的代码审查和调试过程。 - **第5章:糖果机界面** - 通过一个具体...

    C++黑客编程揭秘与防范源码(冀云)

    6. 安全编程模式:书中的源码会展示如何使用一些安全编程模式,如防御式编程、最小权限原则和编码规范,以减少安全风险。 7. 输入验证:有效的输入验证是防止攻击的第一道防线。书中可能详细阐述如何对用户输入进行...

    Lab-4 1160300314 朱明彦 report 1

    实验报告“Lab-4 1160300314 朱明彦 report 1”涵盖了软件构造课程中的重要主题,包括异常与错误处理、防御式编程、测试和调试。以下是这些知识点的详细说明: **异常与错误处理** 异常处理是软件开发中的关键部分...

    编程精粹───Microsoft编写优质无错C程序秘诀

    这种防御式编程方法能够显著提高软件的整体稳定性。 #### 第4章 对程序进行逐条跟踪 这一章节聚焦于程序调试技巧,特别是如何通过逐行分析代码来定位和修复错误。它涵盖了使用调试工具、设置断点和单步执行等技术...

    leetcode c++程序

    7. 防御式编程不被提倡:在LeetCode题解中,为代码简短而忽略了防御式编程的一些原则,如不检查malloc/new返回指针是否为nullptr,不检查函数入口参数的有效性等。 8. 编程学习:本书假定读者已经学习过《数据结构...

    微软一站式代码示例编码规范

    - **流行性**:示例代码应体现当前最佳编程实践,如使用Unicode、错误处理、防御式编程等。 - **可靠性**:代码示例必须符合法律、隐私和政策标准,不得包含侵入性或低质量的编程实践,且所有安装和执行步骤应可逆。...

    leetcode算法题答案PDF

    此外,文档也提到了防御式编程,这是一种在代码编写过程中加入额外检查以防止潜在错误的编程习惯,比如检查指针是否为空,验证函数输入参数的有效性等。然而,文档指出,在LeetCode的解题环境中,为了编写更简洁的...

    Thinking in C++ Vol.2

    本书详细阐述了防御式编程的原则和实践方法,包括预条件检查、断言使用等。这些技巧对于提高软件的质量和可靠性至关重要。 #### 三、标准C++库 **3. 深入字符串** 字符串是编程中最常用的数据类型之一。本章重点...

    个人评价1

    防御式编程是一种预防性的编程策略,旨在尽早发现并处理潜在错误,以避免程序在运行时出错。在未来的工作中,我需要更加注重编写健壮的代码,以防止可能的问题发生。 另外,我在项目架构设计方面还有待提升。在项目...

    leetcode题目详解

    但是,这一点对于实际的软件开发而言,防御式编程是非常重要的,因为它可以提高代码的健壮性和可维护性。 在如何使用LeetCode的问题上,作者建议根据不同的情况采取不同的策略。对于即将参加面试的人来说,他建议...

    技术经理要注重团队的发展与管理,本资源为真实公司研发团队内部技术交流ppt

    防御式编程和契约式编程则是提高代码质量的方法,前者要求对所有输入数据保持警惕并处理异常,后者则规定模块间应遵循统一的通讯协议,违反协议则抛出异常。 【团队合作】 技术团队的成功很大程度上取决于团队合作...

    浅谈C语言中影响程序安全性的几点因素.pdf

    《浅谈C语言中影响程序安全性的几点因素》 C语言作为一门强大的编程语言,其灵活性和高效性深受程序员喜爱,然而,这也...理解并掌握这些知识点,结合防御式编程,可以帮助程序员编写出更安全、更稳定的C语言程序。

Global site tag (gtag.js) - Google Analytics