code2读书笔记-第八章 防御式编程

防御式编程 这一概念来自防御式驾驶。在防御式驾驶中要建立这样一种思维,那就是你永远也不能确定另一位司机要做什么。这样才能确保在其他人做出危险动作时你也不会受到伤害。防御式编程的主要思想是:子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据。

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

对已形成产品的软件而言,仅仅“垃圾进,垃圾出”还不够。不管什么进来,好的程序都不会生成垃圾,而是做到“垃圾进,什么都不出”、“垃圾进,出去错误提示”或“不许垃圾进来”。通常有三种方法来处理进来垃圾的情况:

  1. 检查所有来源于外部的数据的值 当从文件、用户、网路或其他外部接口中获取数据时,应检查所获得的数据值,以确保它在允许的范围内。还要格外注意安歇狡猾的可能是攻击你的系统的数据,包括企图令缓存溢出的数据、注入的 SQL 命令、注入的 HTML 或 XML 代码、数据溢出以及传递给系统调用的数据,等等。
  2. 检查子程序所有出入参数的值 事实上和来源于外部的值一样,只不过数据时来自于其它子程序而非外部接口。
  3. 决定如何处理错误的输入数据 一旦检测到非法的参数,如何处理它,第3节“错误处理技术”会详细描述这些技术。

防御式编程的最佳方式就是在一开始不要在代码中引入错误。使用迭代设计、编码前先写伪代码、写代码前先写测试用例、底层设计检查等活动,都有助于防止引入错误。

2. 断言

断言(assertion)是指在开发期间使用的、让程序在运行时进行自检的代码(通常是一个子程序或宏)。一个断言通常有两个参数:一个描述假设为真时的情况的布尔表达式,和一个断言为假时需要显示的信息。

断言可以用于在代码中说明各个特定假设,澄清各种不希望的情形。可以用断言检查如下这类假定:

正常情况下,你不希望用户看到产品代码中的断言信息;断言主要用于开发和维护阶段。

使用断言的指导建议

3. 错误处理技术

断言可以用于处理代码中不应发生的错误。那么又该如何处理那些预料中的能要发生的错误呢?下面就来详细说明这些可用的技术:

健壮性与正确性

错误处理方式有时更侧重于正确性,而有时更侧重于健壮性。正确性 意味着永不返回不准确的结果。然而,健壮性 则意味着要不断尝试采取某些措施,以保证软件可以持续地运转下去,哪怕有时做出一些不够准确的结果。人身安全攸关的软件往往更倾向于正确性;而消费类应用软件更注重健壮性而非正确性。

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

对错误处理的方式直接关系到软件能否满足在正确性、健壮性和其他非功能性指标方面的要求。确定一种通用的处理错误参数的方法,是架构层次(或更高层次)的设计决策,需要在那里的某个层次上解决。

4. 异常

异常时把代码中的错误或异常事件传递给调用方代码的一种特殊手段。对错误的前因后果不甚了解的代码,可以把对控制权转交给系统中其他能更好地解释错误并采取措施的部分。异常和继承有一点是相同的,即:审慎明确地使用时,它们都可以降低复杂度;而草率粗心地使用时,只会让代码变得几乎无法理解。下面给出一些建议:

应对程序运行时发生的严重错误的最佳做法,有时就是释放所有已获得的资源并终止程序执行,而让用户去重新用正确的输入数据再次运行程序。

把异常当做正常处理逻辑的一部分的那种程序,都会遭受与所有典型的意大利面式代码同样的可读性和可维护性问题。

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

让软件的某些部分处理“不干净的”数据,而让另一些部分处理“干净的”数据,即可让大部分代码无须再负担检查错误数据的职责。

6. 辅助调试的代码

不要自动地把产品版的限制强加于开发版之上

尽早引入辅助调试的代码

采用进攻式编码

应该以这么一种方式来处理异常情况:在开发阶段让它展现出来,而在产品代码运行时让它能够自我恢复。– 进攻式编程
下面列出一些可以让你进行进攻式编程的方法:

计划移除调试辅助的代码

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

8. 对防御式编程采取防御的姿态

过度的防御式编程也会引起问题。如果你在每个能想到的地方用每一种想到的方法检查从参数传入的数据,那么你的程序将会变得臃肿而缓慢。更糟糕的是,防御式编程引入的额外代码增加了软件的复杂度。


要点

目录