什么是防御性编程
防御性编程并不是让你在比编程得时候抱着“它就是这样工作的!”的态度来编码。防御性编程的主要思想是:子程序应该不因传入错误数据而被破坏,哪怕是由其它子程序产生的错误数据。
编程时要承担保护自己子程序的责任,哪怕是其它子程序或者其他程序员犯的错误,都不会影响我们的子程序。
防御性编程技术可以让错误更容易被发现、更容易修改,并减少错误对产品代码的破坏。
对于平时的工作中调用别人的接口的情况是经常发生的,你是不是也经常因为调用别人的接口而返回一些不知所以的异常,还要去和接口的开发者确认是什么原因,这样的情况不知道你是否经常遇到,反正我是遇到的太多了。这种情况多了,简直就是噩梦。
所以我个人是非常看重防御性编程的,很多时候还能规避责任,至少不会让人莫名其妙的把锅甩自己头上。
防御性编程:核对表
这个核对表是《代码大全2》中所提供,其目的就是让我们可以通过这张表来确定自己是否又在进行防御性编程:
一般事宜 | |
---|---|
1 | 子程序是否保护自己免遭有害输入数据的破坏? |
2 | 你用断言来说明编程假定吗?其中包括了前条件和后条件吗? |
3 | 断言是否只是用来说明从不应该发生的情况? |
4 | 你是否在架构或者高层设计中规定了一组特定的错误处理技术? |
5 | 你是否在架构或高层设计中规定了是让错误处理更倾向于健壮性还是正确性? |
6 | 你是否建立了隔栏来遏制错误可能造成的破坏?是否减少了其他需要关注错误处理的代码数量? |
7 | 代码中用到辅助调式代码了吗? |
8 | 如果需要启用或禁用添加的辅助助手的话,是否无需大动干戈? |
9 | 在防御式编程时引入的代码量是否适宜———既不过多,也不过少? |
10 | 你在开发阶段是否采用了进攻式编程来使错误难以被忽视? |
异常 | |
1 | 你在项目中定义了一套标准化的异常处理方案吗? |
2 | 是否考虑过异常之外的其它替代方案? |
3 | 如果可能的话,是否在局部处理了错误而不是把它当作一个异常抛到外部? |
4 | 代码中都否避免了在构造函数和析构函数中抛出异常? |
5 | 所有的异常是否都与抛出他们的子程序处于同一抽象层次上? |
6 | 每个异常是否都包含了关于异常发生的所有背景信息? |
7 | 代码中是否没有使用空的catch语句?(或者如果使用空的catch语句确实很合适,那么明确说明了吗?) |
安全事宜 | |
1 | 检查有害输入数据的代码是否也检查了故意的缓冲区溢出、SQL注入、HTML注入、整数溢出以及其他的恶意输入数据? |
2 | 是否检查了所有的错误返回码? |
3 | 是否捕获了所有的异常? |
4 | 出错消息中是否避免出现有助于攻击者攻入系统所需的信息? |
由表中的内容也可以看出,进行防御式编程会用到 断言、异常、隔栏、辅助调试代码、进攻式编程。
断言
是指在开发期间使用的、让程序在运行时进行自检的代码。断言对于大型的复杂的程序或可靠性要求极高的程序来说尤其有用。下面是书中对使用断言的指导建议:
- 用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况
- 避免把需要执行的代码放到断言中(因为断言的逻辑在关闭断言功能后就会失效)
- 用断言来注解并验证前条件和后条件
- 对于高健壮性的代码,应该先使用断言再处理错误(以Microsoft Word为例,在其代码中对应始终为真的条件都加上了断言,但同时也用错误处理代码处理了这些错误,以应对断言失败的情况。)
上面提到了健壮性,与之对应的是正确性
健壮性:意味着要不断尝试采取某些措施,以确保程序可以持续的运转下去,哪怕有时做出一些不够准确的结构。健壮性也是我们能接触到的大多数软件所倾向的。
正确性:意味着永不返回不准确的结果,哪怕不返回结果也比返回不准确的结果好。通常涉及到人身安全攸关的软件更倾向于正确性,验证的时候哪怕是让软件停止运行,也不允许返回不准确的结果。
异常
下面是书中对使用异常的建议:
- 用异常通知程序的其他部分,发生了不可忽略的错误
- 只在真正例外的情况下才抛出异常
- 不能用异常来推卸责任(如果某些错误情况可以在局部处理,那就应该再局部处理掉)
- 避免在构造函数和析构函数中抛出异常,除非你在同一地方把他们捕获
- 在恰当的抽象层次抛出异常
- 在异常消息中加入关于导致异常发生的全部信息
- 避免使用空的catch语句(注意这里是避免,并不是不能使用,只要你确保使用是合理的就可以使用)
- 了解所用函数库可能抛出的异常(在Java、C#这样的编程语言中,很多程序员习惯性的用Exception来捕获所有异常,这个在你不清楚你所要捕获的所有异常类型的情况下其实是不可取的,不要因为图方便就滥用Exception来捕获所有异常)
- 考虑创建一个集中的异常报告机制
- 把项目中对异常的使用标准化
- 考虑异常的替代方案
隔栏
隔栏是一种容损策略,防火墙就是隔栏的具象化。
辅助调试代码
其实就是在调试代码过程中可以帮助你快速检查程序正确性或者检测程序错误的代码,通常都是需要及时移除的。
进攻式编程
进攻式编程是指,在开发阶段让它显现出来,而在产品代码运行时让他能够自我修复。
假设有一段case语句,期望它处理5类事件,在开发期间,应该让针对默认情况的case分支显示警告信息,甚至于抛出异常,这样可以及时发现问题。而在生产环境的代码中则要稳妥一些,把警告或者异常换成记录错误日志。
对防御式编程采取防御的姿态
过度的防御式编程也会引起问题,如果在没一个能想到的地方用每一种能想到的方法检查从参数传入的数据,那么你的程序将会变得臃肿而缓慢。更糟糕的是,防御式编程引入的额外代码增加了软件的复杂度。保留那些检查重要错误的代码,但不要过度检查;去掉检查细微错误的代码;去掉可以导致程序硬性奔溃的代码;保留可以让程序稳妥的奔溃的代码;为你的技术支持人员记录错误信息;确认留在代码中的错误消息是友好的。
结语
防御式编程固然好,但如果没有正确的认识什么是防御式编程,而是一味的追求防御,只会适得其反。世界上没有什么东西是绝对好的,保持批判性的思维,不盲目的推崇某项技术好坏,物极必反。秉持中庸之道,才能有利于更好的成长。