读书笔记:《代码大全第2版》 05.创建高质量的代码之防御式编程
防御式编程
防御式编程的主要思想:方法应该不因传入错误数据而被破坏,哪怕是由其他方法产生的错误数据。
防御式编程的最佳方式就是在一开始不要在代码中引入错误。使用迭代、编码前先写伪代码、写代码前先写测试用例、低层设计检查等活动,都有助于防止引入错误。
1、保护程序免遭非法输入数据的破坏
- 检查所有来源于外部的数据值(确保外部数据合法,在允许的范围内)
- 检查子程序所有输入参数的值
- 决定如何处理错误的输入数据
2、断言
断言(Assertion)是在开发期间使用的,让程序在运行时进行自检的代码(通常是一个子程序或者宏)。断言为真,表示程序运行正常,断言为假,则意味着它已经在代码之中发现了意料之外的错误。
断言可以用来处理如下的假定:
- 输入参数或者输出参数的取值处于预期的范围内
- 子程序开始(或者结束)执行时,文件或流的读写位置处于开头或者结尾处
- 子程序开始(或者结束)执行时,文件或流是处于打开(或关闭)的状态
- 文件或流已用只读、只写或可读可写方式打开
- 仅用于输入的变量的值没有被子程序所修改
- 指针非空
- 传入子程序的数组或其它容器至少能容纳X个数据元素
- 表已初始化,存储着真实的数值
- 子程序开始(或结束)执行时,某个容器是空的(或满的)
- 一个经过高度优化的复杂子程序的运算结果和相对缓慢但代码清晰的子程序的运算结果一致
使用断言的指导性建议:
- 用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况
- 错误处理代码是在编码时能预料到的不太可能经常发生的非正常情况,通常用来检查有害的输入数据
- 断言用在那些你知道绝对不会发生的事情上,但是人难免会犯错,所以捕捉的是程序员自己的错误
- 避免把需要执行的代码放到断言中
- 用断言来注解并验证先条件和后条件
- 前条件是子程序或类的调用方代码在调用子程序或实例化对象之前要确保为真的属性
- 后条件是子程序或类的在执行结束后要确保为真的属性
- 对于高健壮性的代码,应该先使用断言再处理错误
3、错误处理技术
通常断言用来处理代码中不应发生的错误,错误处理代码用来处理预料中可能发生的错误。
常用的错误处理技术:
- 返回中立值(比如数值计算可以返回0,字符串操作可以返回空字符串)
- 换用下一个正确的数据
- 返回与前次相同的数据
- 换用最接近的合法值(比如超过最大量程的数据就当做最大的量程)
- 把警告信息记录到日志文件中
- 返回一个错误码(将不能处理的部分交给上游程序处理)
- 调用错误处理子程序或对象
- 当错误发生时显示出错消息
- 用最妥当的方式在局部处理错误
- 关闭程序(在一些重要的场合,关闭程序比给出错误的数据更好)
正确性意味着永远不返回不准确的结果,哪怕不返回结果也比返回不准确的结果好。
健壮性则意味着要不断尝试采取某些措施,以保证软件可以持续地运转下去,哪怕有时做出一些不够准确的结果。
4、异常
异常是把代码中的错误或异常事件传递给调用方代码的一种特殊手段。
异常用得好的话可以降低复杂度,但如果用得不好的话会使代码变得几乎无法理解。
使用异常时的注意事项:
- 用异常通知程序的其他部分,发生了不可忽略的错误
- 只在真正例外的情况下才抛出异常
- 不能用异常来推卸责任,错误能局部处理就处理掉
- 避免在构造函数中抛出异常,除非你在同一地方把它们捕获
- 在恰当的抽象层次抛出异常(P200)
- 在异常消息中加入关于导致异常发生的全部消息(比如抛出数组下标越界异常时,应指明其上界、下界和非法的下标值等信息)
- 避免使用空的catch语句
- 了解所用函数库可能抛出的异常
- 考虑创建一个集中的异常报告机制(比如Spring的全局异常处理机制)
- 把项目中对异常的使用标准化
- 考虑异常的替换方案(有些错误可以使用错误处理技术就使用,而不要因为语言有异常处理机制就用异常)
5、隔栏(Barricade)
让软件的某些部分处理“不干净”的数据,而让另一些部分处理“干净的”数据,即可让大部分代码无须再担负检查错误数据的职责。就好似进入手术室之前的都要经过消毒处理,外科医生默认接收到的手术器材都是安全的。
隔栏外部的程序使用错误处理技术,在那里对数据的做任何假定都是不安全的;隔栏内部的程序使用断言技术,如果此时内部的程序有错误,那就是程序的错误而不是数据的错误了。
6、线上代码中该保留多少防御式代码
- 保留那些检查重要错误的代码
- 去掉检查细微错误的代码
- "去掉"指使用版本控制、预编译开关等技术来编译不包含这段特定代码的程序,而不是永久地删除
- 去掉可以导致程序硬性崩溃的代码
- 保留可以让程序稳妥地崩溃的代码
- 为你的技术支持人员记录错误信息
- 确认留在代码中的错误消息是友好的
7、总结
过度的防御式编程也会引起问题。如果你在每一个能想到的地方用每一种能想到的方法检查从参数出入的数据,那么你的程序员将会变得臃肿而缓慢,增加软件的复杂度。