zoukankan      html  css  js  c++  java
  • 读书笔记_代码大全_第8章_防御式编程

    防御式编程

    前注:希望我的读书笔记能带你迅速走过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

    1. 一定不要“垃圾进,垃圾出”,一定要拴住垃圾的输入,不能让它扩散!用错误处理程序,用断言,用异常处理,拦截住垃圾输入,并给出好的处理方案;
    2. 处理不严重的错误时,直接用错误处理程序;处理严重错误时(可能需要终止程序)就用断言和异常,但千万不要滥用。<end>
  • 相关阅读:
    项目中常用的linux命令
    Flutter移动电商实战 --(12)首页导航区域编写
    Flutter移动电商实战 --(13)ADBanner组件的编写
    Flutter移动电商实战 --(10)使用FlutterSwiper制作轮播效果
    Flutter移动电商实战 --(9)移动商城数据请求实战
    Flutter移动电商实战 --(8)dio基础_伪造请求头获取数据
    Flutter移动电商实战 --(7)dio基础_POST请求的使用
    Flutter移动电商实战 --(6)dio基础_Get_Post请求和动态组件协作
    Flutter移动电商实战 --(5)dio基础_引入和简单的Get请求
    Flutter移动电商实战 --(4)打通底部导航栏
  • 原文地址:https://www.cnblogs.com/jerry19880126/p/2818187.html
Copyright © 2011-2022 走看看