您的位置:首页 > 编程语言

读书笔记_代码大全_第8章_防御式编程

2012-12-14 15:54 295 查看
防御式编程

前注:希望我的读书笔记能带你迅速走过25页的书籍,有不妥之处,欢迎指正。http://www.cnblogs.com/jerry19880126/

1. 问题

这一章主要介绍如何编写出健壮性强的代码,简单地说,就是对各种可能的输入,程序都能够给出正确的处理结果。

举个例子,比如进行摄氏温度向热力学温度的转换,已知热力学温度=摄氏温度+273,程序的接口是:

int Celsius2Thermo(int Celsius)

糟糕的程序会直接是

int Celsius2 Thermo (int Celsius)

{

return Celsius + 273;

}

这种程序在大部分情况下是正确的,但万一输入的Celsius是-273呢?程序返回0从数值上看是没有问题的,但这个结果合理吗?热力学第三定律告诉我们,绝对零度不可达,所以这个结果是没有意义的。万一输入是INT_MAX(32位机上是2^31 – 1),结果又是什么呢?数值会上溢,得到的是一个绝对值很大的负数。还可以举出很多例子,比如有变量作为分母的情况,你有没有考虑这个变量可能为0呢?

千万不要说上面的情况在实际中不会出现,若这个子程序做成一个产品,你千万不要假设用户一定会做什么,一定不会做什么,事实上用户什么都会做,他们的行为会产生成千上万种的输入可能!

所以防御式编程主要考虑的就是程序对输入各种数据的稳健性,需要对各种可能的数据类型,以及可能的数据范围进行考虑。

2. 方法

(1) 普通错误处理方法

对付上面的问题,用普通的数据处理方法就足够了,只需要这样写:

int Celsius2 Thermo (int Celsius)

{

if(Celsius <= -273) {…}

else if(Celsius >= INT_MAX – 273) {…}

else

{ return Celsius + 273; }

}

就可以对付输入数据范围的问题了,在{…}里写上相应的警告就可以了,比如给用户提示“您输入的数据无效”等。

在更复杂的情况时,比如电视画面的一帧有一个像素的数据不对,该怎么办?有一些处理方法:一是丢弃这个帧,二是用默认值替换,三是换用上一次的数据,四是用最接近的合法值替换,五是记录到日志文件中。可以根据实际情况,来选择相应的处理方法。

但万一是一些程序本身不好处理的输入呢?比如一些恶意的漏洞攻击(使程序的内存用尽等)或者是在病理分析时,突然传来一个无效的数据(这时怎么处理这个无效数据,是采用默认值,还是不去管它?)高级编程语言提供两种处理这些严重错误的方法,简单一点是“断言”,复杂一点是“异常处理”。

(2) 断言

对于C++而言,在头文件中嵌入include <cassert>,然后调用assert(expression)就行了,其中,expression是逻辑表达式,当表达式值为真时,代码会继续执行,而在表达式为假时,程序就会报错,并强制终止(调用了abort())。

举个简单的例子:

int main()

{

int a = 10;

assert(a < 5);

cout << a << endl;

return 0;

}

这时运行会出现如下界面,可以看出expression的结果为假,所以出现了assertion failed的字样,同时还可以看到,系统自带的assert函数指出了是哪个表达式断言为假,也指出了出问题的源文件和行号,另外,弹框还说明了abort()被调用了,程序被强行终止。



注意系统自带的assert是只接收一个参数的(expression),不接收显示的错误处理信息(事实上,已经显示的很完善了),若用户还想显示自己独有的提示信息,则需要自定义一个assert,下面是我自己写的带双参数的assert子函数:

void myAssert(bool expression, const char* message)

{

if(!expression)

{

cout << message << endl;

exit(1);

}

}

这个函数可以显示出错的消息,但并不完善,因为好的断言函数还应该指明出错的源文件、行号,甚至是具体的哪一条表达式。

断言主要用于开发和测试环节,在产品时给用户看到大红叉就不好了,所以在产品发布的时候最好禁用断言,不要用// 或者 /* … */ 一行行地注释了,在include <cassert>之前写上

#define NDEBUG

就OK了,所有的断言都会失效,这里注意一定是在<cassert>头文件之前写#define。

《代码大全》上提倡先断言而后进行错误处理,我觉得这样不好,因为当断言表达式为假时,程序已经终止了,错误处理就不会执行了。《代码大全》可能是这样考虑的,开发的时候用断言,产品运行的时候定义NDEBUG,让所有断言失效,这样就可以执行错误处理程序了,但万一错误处理程序本身有问题呢?这样在开发和测试阶段就无法暴露问题了。

(3) 异常

C++中的异常是用try{…}catch{…}块来实现的,当try中的代码出问题时,会抛出异常由catch块处理,若本程序不好处理,则throw给其实程序处理。我自己写程序没怎么用过异常处理(因为没有接触过大项目的原因吧),所以这里也不好举例子了。就列出《代码大全》里做出的一些注意事项:

a) 不要在构造函数和析构函数中抛出异常,在构造函数中抛出异常将很可能无法进一步调用析构函数,造成资源的泄露;

b) 尽量局部处理,不要向外抛出;

c) 抛出抽象层次最好一致,比如抛出了EOFException的异常,其他别有用户的程序员就知道你这个模块有对文件的读操作了;

d) 避免偷懒的空catch块;

e) 不要滥用异常,这会使你的程序臃肿不堪的。

最后列出《代码大全》里本章的Key Points

一定不要“垃圾进,垃圾出”,一定要拴住垃圾的输入,不能让它扩散!用错误处理程序,用断言,用异常处理,拦截住垃圾输入,并给出好的处理方案;

处理不严重的错误时,直接用错误处理程序;处理严重错误时(可能需要终止程序)就用断言和异常,但千万不要滥用。<end>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: