代码大全《Code Complete》——防御式编程(第8章)

333 阅读7分钟

第八章 防御式编程

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

检查所有来源于外部的数据、检查子程序的输入参数、决定如何处理错误的输入数据

8.2 断言

下面是我用Idea写的一个简单示例

image.png

此时断言错误

image.png

否则就是正常打印a的值1。 注意,在idea中使用assert,需要先配置虚拟机参数 -ea,如下图

image.png 如果没有vm options那一栏就在modify actions里打开

断言通常只出现在开发阶段,在正式产品里应该被去除,以避免降低系统的性能。

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

用断言来验证前条件和后条件(preconditions和postconditions)

不要在断言里执行代码(子程序)!

8.3 错误处理技术

返回中立值

例如字符串生成出错时返回null,集合出错时返回空集合,必定为正数的计算结果返回0等。

换用下一个正确的数据

当执行一些只是有概率失败的代码时,可以重试,直到获得正确的数据为止。

返回上一次的正确数据

换用最接近的合法值

记录警告信息到日志中

返回错误码

调用错误处理子程序或对象

try catch或者专门的纠错函数等

显示出错信息

consol.log、alert等

结束程序

总的来说,要根据程序对于正确性和健壮性二者的要求程度来决定采用何种错误处理机制。不过,一旦确定了错误处理方式,就最好在整个程序中保持一致性。

8.4 异常

避免在构造函数中抛出异常(即使有,也要立刻catch掉)

抛出的异常应该与接口的层次保持一致。比如数据层抛出了异常“写数据失败,主键重复”,接着服务层捕获后如果还想继续抛出,那么就把它封装为”保存用户数据失败“之类的符合当前层次的异常,否则将会暴露底层的实现细节,同时使不同层次之间的代码耦合起来了。

在异常消息中加入导致异常发生的全部信息。

考虑创建一个集中的异常报告机制。

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

即在接收数据的外层程序中对数据做验证和转换(栅栏),则所有内层的程序在调用这些数据时都可以默认它们是安全的。

展览的外部程序应该使用错误处理技术,内部的可以使用断言。

8.6 辅助调试的代码

采取进攻式编程

确保断言语句使程序终止运行。不要让程序员养成坏习惯,一碰到已知问题就按回车键把它跳过。让问题引起的麻烦越大越好,这样它才能被修复。

完全填充分配到的所有内存,这样可以让你检测到内存分配错误。

完全填充已分配到的所有文件或流,这样可以让你排查出文件格式错误。

确保每一个case语句中的default分支或else分支都能产生严重错误(比如说让程序终止运行),或者至少让这些错误不会被忽视。

在删除一个对象之前把它填满垃圾数据。

让程序把它的错误日志文件用电子邮件发给你,这样你就能了解到在已发布的软件中还发生了哪些错误——如果这对于你所开发的软件适用的话。

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

当产品进入到发布阶段,对待错误的态度应该发生一百八十度转弯——从尽力暴露出来到尽力隐藏起来。下面是一些指导建议

保留那些检查重要错误的代码

你需要确定程序的哪些部分可以承担未检测出错误而造成的后果,而哪些部分不能承担。

去掉检查细微错误的代码

你也可以把错误检查代码保留下来,但应该让它不动声色地把错误信息记录在日志文件里。

去掉可以导致程序硬性崩溃的代码

保留可以让程序稳妥地崩溃的代码

如果你的程序里有能够检测出潜在严重错误的调试代码,那么应该保留那些能让程序稳妥地崩溃的代码。(尽量记录详细的日志,保存用户在崩溃前的信息)

确认留在代码中的错误消息是友好的

如果你在程序中留下了内部错误消息,请确认这些消息的用语对用户而言是友好的。有一种常用而且有效的方法,就是通知用户说发生了“内部错误”,再留下可供报告该错误的电子邮件地址或电话号码即可。

CHECKLIST:Defensive Programming

一般事宜

子程序是否保护自己免遭有害输入数据的破坏?

你用断言来说明编程假定吗?其中包括了前条件和后条件吗?

断言是否只是用来说明从不应该发生的情况?

你是否在架构或高层设计中规定了一组特定的错误处理技术?

你是否在架构或高层设计中规定了是让错误处理更倾向于健壮性还是 正确性?

你是否建立了隔栏来遏制错误可能造成的破坏?是否减少了其他需要 关注错误处理的代码的数量?

代码中用到辅助调试的代码了吗?

如果需要启用或禁用添加的辅助助手的话,是否无须大动干戈?

在防御式编程时引入的代码量是否适宜——既不过多,也不过少?

你在开发阶段是否采用了进攻式编程来使错误难以被忽视?

异常 你在项目中定义了一套标准化的异常处理方案吗?

是否考虑过异常之外的其他替代方案?

如果可能的话,是否在局部处理了错误而不是把它当成一个异常抛到外 部?

代码中是否避免了在构造函数和析构函数中抛出异常?

所有的异常是否都与抛出它们的子程序处于同一抽象层次上?

每个异常是否都包含了关于异常发生的所有背景信息?

代码中是否没有使用空的catch语句?(或者如果使用空的catch 语句确实很合适,那么明确说明了吗?)

安全事宜 检查有害输入数据的代码是否也检查了故意的缓冲区溢出、SQL注入、 HTML注入、整数溢出以及其他恶意输入数据?

是否检查了所有的错误返回码?

是否捕获了所有的异常?

出错消息中是否避免出现有助于攻击者攻入系统所需的信息?

Key Points

  • 最终产品代码中对错误的处理方式要比“垃圾进,垃圾出”复杂得多。

  • 防御式编程技术可以让错误更容易发现、更容易修改,并减少错误对产品代码的破坏。

  • 断言可以帮助人尽早发现错误,尤其是在大型系统和高可靠性的系统中,以及快速变化的代码中。

  • 关于如何处理错误输入的决策是一项关键的错误处理决策,也是一项关键的高层设计决策。

  • 异常提供了一种与代码正常流程角度不同的错误处理手段。如果留心使用异常,它可以成为程序员们知识工具箱中的一项有益补充,同时也应该在异常和其他错误处理手段之间进行权衡比较。

  • 针对产品代码的限制并不适用于开发中的软件。你可以利用这一优势在开发中添加有助于更快地排查错误的代码。