防御式编程(P187)
防御式编程并不是说让你在编程时持“防备批评或攻击”的态度——“它就是这么工作!”你永远不能预先确定代码中会出什么错误。其核心想法是要承认程序都会有问题,都需要被修改,聪明的程序员应该根据这一点来编程序。
1.保护程序免遭受非法输入数据的破坏
对已经形成产品的软件而言,仅仅“垃圾进,垃圾出”还不够。不管进来什么,好的程序都不会生成垃圾。
通常有三种方法来处理进来垃圾的情况。
- 检查所有来源于外部的数据的值
- 检查子程序所有输入参数的值
- 决定如何处理错误的输入数据
2.断言
断言(assert)是指在开发期间使用的、让程序在运行时进行自检的代码。
断言对于大型的复杂程序或可靠性要求极高的程序来说尤其有用。通过使用断言,程序员能更快速地排查出因修改代码或者别的原因,而弄进程序里的不匹配的接口假定和错误等。
1)建立自己的断言机制
#define ASSERT( condition, message){ \
if(!(condition)){ \
LogError("Assertion failed: ", \
#condition, message); \
exit( EXIT_FAILURE); \
} \
}
2)使用断言的指导建议
3.错误处理技术
- 返回中立值
- 换用下一个正确的数据
- 返回与前次相同的数据
- 换用最接近的合法值
- 把警告信息记录到日志文件中
- 返回一个错误码
- 调用错误处理子程序或对象
- 当错误发生时显示出错消息
- 用最妥当的方式在局部处理错误
- 关闭程序
1)健壮性与正确性
正确性意味着永不返回不准确的结果,哪怕不返回结果也比返回不准确的结果好。
健壮性则意味着要不断尝试采取某些措施,以保证软件可以持续地运转下去,哪怕有时做出一些不够准确的结果。
人身安全攸关的软件往往更倾向于正确性而非健壮性。消费类应用软件往往更注重健壮性而非正确性。
2)高层次设计对错误处理方式的影响
既然有这么多的选择,你就必须注意,应该在整个程序里采用一致的方式处理非法的参数。
4.异常
异常是把代码中的错误或异常事件传递给调用方代码的一种特殊手段。
- 用异常通知应用程序的其他部分,发生了不可忽略的错误
- 只在真正例外的情况下才抛出异常
- 不能用异常来推卸责任
- 避免在构造函数和析构函数中抛出异常,除非你在同一地方把它们捕获
- 在恰当的抽象层次抛出异常
- 在异常消息中加入关于导致异常发生的全部信息
- 避免使用空的catch语句
- 了解所用函数库可能抛出的异常
- 考虑创建一个集中的异常报告机制
- 把项目中对异常的使用标准化
- 考虑异常的替换方案
“深入一种语言去编程”而非“在一种语言上编程”
5.隔离程序,使之包容由错误造成的损害
以防御式编程为目的而进行隔离的一种方法,是把某些接口选定为“安全”区域的边界。对穿越安全区域边界的数据进行合法性校验,并当数据非法时作出敏锐的反映。
让软件的某些部分处理“不干净的”数据,而让另一些部分处理“干净的”数据,即可让大部分代码无须再负担检查错误数据的职责。
6.辅助调试的代码
1)不要自动地把产品版的限制强加于开发版之上
应该在开发期间牺牲一些速度和对资源的使用,来换取一些可以让开发更顺利的内置工具。
2)尽早引入辅助调试的代码
3)采用进攻式编程
应该以这么一种方式来处理异常情况:在开发阶段让它显现出来,而在产品代码运行时让它能够自我修复。
4)计划移除调试辅助的代码
- 使用类似ant和make这样的版本控制工具和make工具
- 使用内置的预处理器
#if defined( DEBUG )
#define DebugCode( code_fragment ) { code_fragment}
#else
#define DebugCode( code_fragment)
#endif
...
DebugCode(
<span style="white-space:pre"> </span>//<span style="font-size:12px;">根据是否定义DEBUG符号,可选择是否编译DebugCode处的代码。</span>
statement 1;
statement 2;
...
statement n;
);
...
- 编写你自己的预处理器
- 使用调试存根
void DoSomething(
SOME_TYPE *pointer;
...
){
// 这行代码将调用检查指针的子程序
CheckPointer(pointer);
...
}
7.确定在产品代码中应该保留多少防御式代码
- 保留那些检查重要错误的代码
- 去掉检查细微错误的代码
- 去掉可以导致程序硬性崩溃的代码
- 保留可以让程序稳妥地崩溃的代码
保留调试代码 - 为你的技术支持人员记录错误信息
- 确认留在代码中的错误消息是友好的
8.对防御式编程采取防御的姿态
过度的防御式编程也会引起问题,要考虑好什么地方你需要进行防御,然后因地制宜地调整你进行防御式编程的优先级。