第八章--防御式编程

第八章--防御式编程


8.1--保护程序免遭非法输入数据的破坏

“垃圾进、垃圾出”已经成为缺乏安全性的差劲程序的标志。

通常有三种方法来处理进来垃圾的情况。

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

        如取值范围、字符串长度、取值是否合乎用途等。

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

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


8.2--断言

断言:是指在开发期间使用的、让子程序在运行时进行自检的代码(通常是一个子程序或宏);断言为真,则运行正常,为假,则说明出现了意料之外的错误。

1. 可以用断言检查下面一些假定:

(1)输入参数或输出参数的取值处于预期的范围内。

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

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

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

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

(6)指针非空。

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

(8)表已经初始化,存储着真是的数据。

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

(10)一个经过高度优化的子程序运算结果和相对缓慢但代码清晰的子程序的运算结果一致。


2. 建立自己的断言机制

如果使用的语言不支持断言,可以自己写一个assert宏;


3. 使用断言的指导建议

(1)用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况。

(2)避免把需要执行的代码放到断言中。

(3)用断言来注解,并验证前条件和后条件。

        前条件:子程序或类的调用方代码在调用子程序或实例化对象之前要确保为真。

        后条件:子程序或类在执行结束之后要确保为真的属性。

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


8.3--错误处理技术

1.错误处理技术

(1)返回中立值。比如返回0,空指针或空字符串等。

(2)换用下一个正确的数据。(如温度计显示数据这种前后数据变化不大(或容许缺失一两个时间点得数据)的程序)

(3)返回与前一次相同的数据。(如温度计显示数据这种前后数据变化不大(或容许缺失一两个时间点得数据)的程序)

(4)换用最接近的合法值。比如如果发现某个值小于0,则用0来代替。

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

(6)返回一个错误码。

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

(8)当错误发生时显示出错信息。但要避免给系统潜在攻击者透露太多信息。

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

(10)关闭程序。


2. 健壮性与正确性

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

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


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

应该在整个程序里采用一致的方式处理非法的参数。一旦确认了一个方法,则要确保始终如一地贯彻这一方法。


8.4--异常

异常是把代码中得错误或者异常事件传递给调用方代码的一种特殊手段。

1. 下面介绍一下C++对异常相关的一些特性:

(1)支持try-catch,但不支持try-catch-finally;

(2)能抛出的异常:std::exception对象,或std::exception派生类的对象;对象指针;对象引用;string或int等数据类型。

(3)对于为捕获的异常所造成的影响:调用std::unexceptioned()函数,该函数在默认情况下将调用std::terminate(),而这个函数在默认情况下又调用abort();

(4)必须在类的接口中定义可能会抛出的异常吗?答:否

 (Java则需要)

(5)必须在类的接口中定义可能会捕获的异常吗?答:否

 (Java则需要)


2. 下面给出一点建议,以便在使用异常时能扬长避短:

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

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

 也就是说,仅在其他编码实践方法无法理解的情况下才使用异常。因为调用子程序需要了解被调用方可能会抛出的异常,所有弱化了封装性。

(3)避免在构造函数和析构函数中抛出异常,除非你在同一地方把它们捕获。C++中,只有在对象已经完全构造之后才能调用析构函数,如果在构造函数中抛出异常,就不会调用析构函数,可能造成资源泄露。

(4)在恰当的抽象层次抛出异常。在打算抛出异常时,请确保异常的抽象层次与子程序接口的抽象层次是一致的。

(5)在异常消息中加入关于导致异常发生的全部信息。

(6)避免使用空的catch语句。如果确实无法表现为调用方抽象层次的异常,则要在日志文件中写清楚用空catch的原因。

(7)了解所有函数库可能抛出的异常。如果函数库没有说明可能抛出哪些异常,可以通过编写一些圆形代码来演练该函数库,找出可能发生的异常。

(8)考虑创建一个集中的异常报告机制。以确保异常处理的一致性,

(9)把项目中对异常的使用标准化。

(10)考虑异常的替换方案。


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

1. 以防御式编程为目的而进行的隔离的一种方法,是把某些接口选定为安全区域的边界。对穿越安全域边界的数据进行合法性校验。如

(1)类的公用方法负责检查数据并进行清理,一旦公用方法接受了,类的私用方法就可以安全使用这些数据。最简单的方法是在得到外部数据时就立即进行清理。

(2)再输入数据时将其转换成恰当的类型。

2. 隔离与断言的关系

简而言之,隔栏外部的程序应该使用错误处理,在那里的数据做任何假定都是不安全的;隔栏内部的程序应该用断言,因为如果内部检测到了错误的数据,应该是程序错误而不是数据本身错误。


8.6 -- 辅助调试的代码

1. 不要自动把产品版的限制强加与开发版之上。应该在开发期牺牲一些速度和对资源的使用,来换取一些可以让开发更顺畅的内置工具。

2. 今早引入辅助调试的代码。

3. 采取进攻式编程。

 进攻式编程:在开发阶段让异常显示出来,而在产品代码运行时让它能自我恢复。

 下面是一些进攻式编程的方法:

(1)确保断言语句使程序终止运行。

(2)完全填充分配到的所有内存,以检测内存分配错误。

(3)完全填充分配到的所有文件或流,以检测文件格式错误。

(4)确保每个case语句中得default分支能产生严重错误。

(5)再删除一个对象之前将其填充垃圾数据。

(6)让程序把错误日志通过邮件发送给你。

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

(1)使用类似ant和make这样的版本控制工具和make工具。在开发模式和产品模式下编译不通过代码。

(2)使用内置的预处理器。

 除了直接定义DEBUG以外,还可以给它赋值,通过判断其值来区分不同级别的调试代码。

(3)编写你自己的预处理器。如果某种语言没有包含一个预处理器,可以自己写一个。如Java。但首先要确立一套声明调试代码的规则。

(4)使用调试存根。在开发阶段,可以调用一段子程序进行一些检查,而在产品代码里面,需要将这些检查移除,那么,可以用一个存根子程序代替检查子程序。存根子程序可以是个空函数,或者简单执行几个操作的子程序。


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

开发阶段:希望错误能够引人注目。

产品发布阶段:希望错误尽可能偃旗息鼓。

下面的建议可以帮助你决定哪些防御式编程工具可以留在产品代码里,哪些要排除在外。

(1)保留哪些检查重要错误的代码。

(2)去掉检查细微错误的代码。

(3)去掉可以导致程序硬性崩溃的代码。如果你的程序中存在可能导致用户数据丢失的代码,那么一定要把他们从最终软件产品中去掉。

(4)保留可以让程序稳妥地崩溃的代码。

(5)为你的技术支持人员记录错误信息。比如将断言改为向日志文件中记录错误信息,而不是彻底删除这些代码。

(6)确认留在代码中的错误消息是友好的。如通知用户说是发生了“内部错误”再留下可供报告该错误的电子邮件地址或电话号码等。


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

过度的防御式编程也会引起问题,比如导致程序臃肿而缓慢,增加软件复杂度等。所以要根据需要使用!


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值