《代码大全2》之---防御性编程 .

第二部分 防御性编程

 

本文为《代码大全2》的读书笔记,版权归代码大全所有。^_^

本文基址:http://blog.csdn.net/cugxueyu/archive/2007/12/10/1926751.aspx

 

  防御式编程的全部重点就在于防御那些你未曾预料到的错误。

  防御式编程的主要思想:子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生错误数据。更一般地说,其核心想法是要承认程序都会有问题,都需要被修改,聪明的程序员应该根据这一点来编程序。

 

一、保护程序免遭非法输入数据的破坏

  对已形成产品的软件而言:不管进来的数据如何,都不应该产程垃圾数据。(必要的错误提示)

  通常有三种方法来处理进来的垃圾数据

  1检查所有来源于外部的数据的值

    当从文件、用户、网络或其他外部接口中获取数据时,应检查所获得的数据值,以确保它在允许的范围内。

  2检查子程序所有输入参数的值

    数据来源于其他子程序,而不是外部接口。

  3决定如何处理错误的输入数据

    一旦检测到非法数据,就应该处理它。

    :防范看似微小的错误,收获可能远远超出你的想象

 

二、断言(Assertions

  断言定义:指在开发期间使用的,让程序在运行时进行自检的代码(通常可以是子程序或宏)。(对大型的复杂程序或可靠性要求极高的程序来说尤其重要)

  Java断言:两个参数—>assert(“bool表达式当判断条件为false时的错误信息”)

 

  断言检查如下这类假定

1、  输入参数和输出参数的取值处于预期的范围内。

2、  子程序开始(或结束)执行时,文件或流是处于打开(或关闭)的状态。

3、  子程序开始(或结束)执行时,文件或流的读写位置处于开头(或结尾)处。

4、  文件或流已用只读、只写或可读可写方式打开。

5、  仅用于输入的变量的值,没有被子程序所修改。

6、  指针非空

7、  传入子程序的数组或其他容器至少能容纳X个数据元素。

8、  表已经初始化,存储着真实的数据。

9、  子程序开始(或结束)执行时,某个容器是空的(满的)。

  :断言主要是用于开发和维护阶段,而在生成产品代码时并不编译到目标代码中,以免降低系统性能

 

  1、建立自己的断言机制

   C++JavaVB在内的很多语言都支持断言。

   C++中标准的assert宏并不支持文本信息

  

  2、使用断言的指导建议

   断言的指导建议

>    用错误处理代码来处理预期会发生的状况,用断言来处理决不应该发生的状况。

  异常发生,触发断言的情况下,就应该修改程序的源代码并重新编译。

 

>    避免把需要执行的代码放到断言中

   ※ 一种危险的断言使用方法(如果断言关闭,代码不能被编译):

     Debug.Assert( PerformAction() )

   ※ 安全地使用断言

    actionPerformed = PerformAction()

    Debug.Assert( actionPerformed )

 

  >    用断言来注解并验证前条件和后条件

   ※ 如果数据来源于系统外部,那么就应该用错误处理代码来检查和处理非法的数值。

   ※ 如果变量的值来源于可信的系统内部,那么使用断言是很合适的。

 

>    对于高健壮性的代码,应该先使用断言再处理错误

   Microsoft Word,在其代码中,对应该始终为真的条件都加上了断言,但同时也用错误处理代码处理了这些错误,以应对断言失败的情况。

 

三、错误处理技术

  如何处理那些预料中的程序错误

  ※ 返回中立值、换用下一个正确数据、返回与前次相同的值、换用最接近的有效值、在日志文件中记录警告信息、返回一个错误码、调用错误处理子程序或对象、显示出错信息或关闭程序。

 

 1返回中立值

   ※ 有时,处理错误的最佳做法就是继续执行操作并简单地返回一个没有危害的数值。

   ※ 例如:数值操作可以返回0、字符串操作可以返回空字符串、指针操作可以返回空指针。

 

 2换用下一个正确的数据

   ※ 例如:数据库记录读取、文件行信息读取等。

 

 3返回与前次相同的数据

 

 4换用最接近的合法值

   ※ 例如:汽车的速度表无法显示负的速度,所以在倒车时,它简单的显示为0

 

 5把警告信息记录到日志文件中

   ※ 要考虑是否能够安全地公开它,或者是否需要对其进行加密或实施其他方式的保护。

 

 6返回一个错误码

   ※ 可以决定在让系统得某些部分处理错误,其他部分则不在本地(局部)处理错误,而只是简单地报告说有错误发生。

   采用方法

   ※ 设置一个状态变量的值

   ※ 用状态值作为函数的返回值

   ※ 用语言内建的异常机制抛出一个异常

   :如果安全性很重要,请确认调用方的子程序总会检查返回的错误吗

 

 7调用错误处理子程序或对象

   ※ 可以把错误处理都集中在一个全局的错误处理子程序或对象中进行。

   优点:能把错误处理的职责集中在一起,从而让调试更为简单。

      缺点:整个程序都要知道这个集中点,并与之紧密耦合。

 

 8当错误发生时显示出错消息

   ※ 可以把错误处理的开销降低。

 

 9用最妥当的方式在局部处理错误

   ※ 给程序员带来灵活度的同时,也带来了显著的风险。即:系统的整体性能将无法满足对其正确性或可靠性的需求

 

 10关闭程序

   ※ 适用于人身安全攸关的应用程序。 

 

 健壮性与正确性

  ※ 处理错误最恰当的方式要根据出现错误的软件的类别而定。错误处理有时更侧重于正确性,有时更侧重于健壮性

  正确性:意味着永不返回不准确的结果,哪怕不返回结果也比返回不准确的结果要好。

  健壮性:意味着要不断尝试采取某些措施,以保证软件可以持续地运转下去,哪怕有时做出一些不够准确的结果。

 

 高层次设计对错误处理方式的影响

  ※ 在整个程序里采用一致统一的方式来处理非法的参数。

  ※ 确定一种通用的处理错误参数的方法,是架构层次(高层次)的设计决策

  ※ 一旦确定了某种方法,就要确保始终如一地贯彻这一方法。

  ※ 请在每个系统调用后检查错误码

 

四、异常

 处理异常建议

 1、用异常通知程序的其他部分,发生了不可忽略的错误

   ※ 异常机制的优越之处就在于它能提供一种无法被忽略的错误通知机制

 2、只在真正例外的情况下才抛出异常

   ※ 仅在其他编码实践方法无法解决的情况下才使用异常。

   ※ 异常同断言相似:都是用来处理那些不仅罕见甚至永远不该发生的情况

   异常的取舍

     1、异常是一种强大的用来处理预料之外的情况途径。

     2、程序的复杂度因此增加、性能也可能降低。

 3、不能用异常来推卸责任

   ※ 能在局部处理的错误,就应该在局部处理,不能当成异常抛出。

 4、避免在构造函数和析构函数中抛出异常,除非你在同一地方把他们捕获

   ※ 如果在构造函数中抛出异常,就不会调用析构函数,从而造成潜在的资源泄漏。

 5、在恰当的抽象层次抛出异常

   ※ 当你决定把一个异常传给调用方时,请确保异常的抽象层次与子程序接口的抽象层次相一致  

 6、在异常消息中加入关于导致异常发生的全部信息

   ※ 要确保异常信息中含有为理解异常抛出原因所需要的全部信息。

 7、避免使用空的catch语句

   ※ 注释或日志记录信息对这一情况文档化。

 8、了解所有函数库可能跑出的异常

   ※ 一定要了解所用的函数库都会抛出哪些异常。

   ※ 未能捕获由函数库抛出的异常将会导致程序崩溃。

 9、考虑创建一个集中的异常报告机制

   ※ 能为一些与异常有关的信息提供一个集中的存储,如发生的异常种类每个异常该如何被处理以及如何格式化异常信息

 

 10把项目中对异常的使用标准化

   ※ 如果使用象C++一样的语言,就应该规定到底可以抛出哪些种类的异常。(考虑只抛出std::exception基类派生出的对象)

   ※ 考虑创建项目特定的异常类(用作项目可能异常的基类,这样就能把纪录日志、报告错误等操作集中起来并标准化)。

   ※ 规定在何种场合允许代码使用try-catch语句在局部对错误进行处理

   ※ 规定在何种场合允许代码抛出不在局部进行处理的异常

   ※ 确定是否要使用集中的异常报告机制

   ※ 规定是否允许在构造函数和析构函数中使用异常

 

 11、考虑异常的替换方案

   请考虑你的系统是否真的需要异常

 

五、隔离程序,使之包容由错误造成的损害

  隔栏(barricade)是一种容损策略 

 

 1在类的层次采用这样的方法

  ※ 类的public方法可以假定数据是不安全的,它们要负责检查数据并进行清理,一旦类的公用方法接受了数据,那么类的私有方法就可以假定数据都是安全的了。

 

 2隔栏与断言的关系

  ※ 隔栏的使用使断言和错误处理有了清晰的区别。

  隔栏外部的程序应该适用错误处理技术,而隔栏内部的程序就应该使用断言技术

 

六、辅助调试的代码

  防御式编程的另一个重要方面就是,适用调试助手(辅助调试代码)

  1、不要自动地把产品版的限制强加于开发版之上

    ※ 程序员常常有这样一个误区:即认为产品级软件的种种限制也应该在开发版本中得到体现(速度、对资源的限制)。

    ※ 应该在开发阶段牺牲一些速度和资源使用,来换取一些可以让开发顺畅的内置工具(辅助代码)

 

  2、尽早引入辅助调试代码

    ※ 越早引入辅助调试的代码,它能提供的帮助也就越大。

 

  3、采用进攻式编程

    ※ 应该以这么一种方式来处理异常:在开发阶段让它显现出来,而在产品代码运行时让它能够自我恢复。--- 进攻式编程

    进行进攻式编程的方法

>    确保断言语句是程序终止运行。

>    完全填充分配到的所有内存,这样可以让你检测到内存分配错误。

 

  4、计划移除调试辅助的代码

    ※ 要事先做好计划,避免调试代码和程序代码纠缠不清。

 

    采取的方法

    ※ 使用类似antmake这样的版本控制工具和make工具

      (可以从同一套源码编译出不同版本的程序)

    ※ 使用内置的预处理器

    ※ 编写你自己的预处理器

    ※ 使用调试存根stubs

      (两套方案开发版本和发布版本代码)

七、确定在产品代码中该保留多少防御式代码

八、

对防御式编程采取防御的姿态

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值