防御式编程
思想与要点
-
保护程序免遭非法输入数据的破坏
子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据。更一般地说,其核心想法是要承认程序都会有问题,都需要被修改,聪明的程序员应该根据这一点来编程序。
对已形成产品的软件而言仅仅“垃圾进,垃圾出”还不够。不管进来什么,好的程序都不会生成垃圾,而 是 做 到 “垃圾进,什么都不出”、“进来垃圾,出去是出错提示”或“不许垃圾进来”。按今天的标准来看,“垃圾进,垃圾出”已然成为缺乏安全性的差劲程序的标志。
通常有三种方法处理进来垃圾的情况:1检查所有来源于外部的数据的值。2检查子程序所有输入参数的值3 决定如何处理错误的输入数据。 -
断言
断言(assertion)是指在开发期间使用的、让程序在运行时进行自检的代码(通常是一个子程序或宏)。断言为真,则表明程序运行正常,而断言为假,则意味着它已经在代码中发现了意料之外的错误。
下面是关于使用断言的一些指导性建议。用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况断言是用来检查永远不该发生的情况,而错误处理代码(error-handling code)是用来检查不太可能经常发生的非正常情况
避免把需要执行的代码放到断言中如果把代码写在断言里,那么当你关闭
断言功能时,编译器很可能就把这些代码除排在外了。
用断言来注解并验证前条件和后条件前条件(preconditions) 和后条件(postconditions)是一种名为“契约式设计(design by contract)”的程序设计和开发方法的一部分(Meyer1997)。使用前条件和后条件时,每个子程序或类与程序的其余部分都形成了一份契约。前条件是子程序或类的调用方代码在调用子程序或实例化对象之前要确保为真的属性。前条件是调用方代码对其所调用的代码要承担的义务。后条件是子程序或类在执行结束后要确保为真的属性。后置条件是子程序或类对调用方代码所承担的责任。断言是用来说明前条件和后条件的有利工具。也可以用注释来说明前条件和后条件,但断言却能动态地判断前条件和后条件是否为真。
对于高健壮性的代码,应该先使用断言再处理错误对于每种可能出错的条件,通常子程序要么使用断言,要么使用错误处理代码来进行处理,但是不会同时使用二者。 一些专家主张只须使用一种处理方法即可(Meyer 1997) -
错误处理技术
断言可以用于处理代码中不应发生的错误。那么又该如何处理那些预料中可
能要发生的错误呢?根据所处情形的不同,你可以返回中立值(neutral value)、换用下一个正确数据、返回与前次相同的值、换用最接近的有效值、在曰志文件中记录警告信息\返回一个错误码、调用错误处理子程序或对象、显示出错信息或者关闭程序一或把这些技术结合起来使用
既然有这么多的选择,你就必须注意,应该在整个程序里采用一致的方式处理非法的参数。对错误进行处理的方式会直接关系到软件能否满足在正确性、健壮性和其他非功能性指标方面的要求。确定一种通用的处理错误参数的方法,是架构层次(或称髙层次)的设计决策,需要在那里的某个层次上解决。一旦确定了某种方法,就要确保始终如一地贯彻这一方法。 -
异常
异常是把代码中的错误或异常事件传递给调用方代码的一种特殊手段。如果
在一个子程中遇到了预料之外的情况,但不知道该如何处理的话,它就可以抛出一个异常,就好比是举起双手说“我不知道该怎么处理它一我真希望有谁知道该怎么办!” 一样。对出错的前因后果不甚了解的代码,可以把对控制权转交给系统中其他能更好地解释错误并采取措施的部分。用异常通知程序的其他部分,发生了不可忽略的错误异常机制的优越之处在于它能提供一种无法被忽略的错误通知机制(Meyers 1996)。 其他的错误处理机制有 可能会导致错误在不知不觉中向外扩散,而异常则消除了这种可能性。
只在真正例外的情况下才抛出异常;避免在构造函数和析构函数中抛出异常除非你在同一地方把它们捕获恰当的抽象层次抛出异常;在异常消息中加入关于导致异常发生的全部信息 -
隔离程序,使之包容包容由错误造成的损害
以防御式编程为目的而进行隔离的一种方法,是把某些接口选定为“安全”区域的边界。对穿越安全区域边界的数据进行合法性校验,并当数据非法时做出敏锐的反应。也同样可以在类的层次采用这种方法。类的公用方法可以假设数据是不安全的,它们要负责检查数据并进行清理。一旦类的公用方法接受了数据,那么类的私用方法就可以假定数据都是安全的了 -
对防御式编程采取防御的姿态
过度的防御式编程也会引起问题。如果你在每一个能想到的地方用每一种能想到的方法检查从参数传入的数据,那么你的程序将会变得臃肿而缓慢。更糟糕的是,防御式编程引入的额外代码增加了软件的复杂度。防御式编程引入的代码也并非不会有缺陷,和其他代码一样,你同样能轻而易举地在防御式编程添加的代码中找到错误—尤其是当你随手编写这些代码时更是如此。因此,要考虑好什么地方你需要进行防御,然后因地制宜地调整你进行防御式编程的优先级。