防御式编程

目录:
1 保护程序免遭非法输入数据的破坏
2 断言
3 错误处理技术
4 异常
5 隔离程序,使之包容由错误造成的损害
6 辅助调试的代码
7 确定在产品代码中该保留多少防御式代码
8 对防御式编程采取防御姿态

内容:

防御式编程是提高软件质量技术的有益辅助手段。防御式编程的主要思想是:子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据。更一般地说,其核心想法是要承认程序都会有问题,都需要被修改,聪明的程序员应该根据这一点来编程序。

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

  • 垃圾进,垃圾出(garbage in, garbage out.)。这句话说的是软件开发领域的“出门概不退换”原则,让用户自己操心自己的事。
  • 按今天的标准来看,“垃圾进,垃圾出”已然成为缺乏安全性的差劲程序的标志。
  • 对已形成产品的软件而言,仅仅“垃圾进,垃圾出”还不够。
  • 不管进来什么,好的程序都不会生成垃圾。
  • 而是“垃圾进,什么都不出”
  • 或者“进来垃圾,出去是出错提示”
  • 甚至“不许垃圾进来”
  • 处理垃圾的三种情况:

    • 检查所有来源于外部的数据的值
    • 检查子程序所有输入参数的值
    • 决定如何处理错误的输入数据,参考第3节:错误处理技术

2 断言

  • 断言( assertion)是指在开发期间使用的,让程序在运行时进行自检的代码。
  • 断言为真,则表明程序运行正常。
  • 断言为假,则意味着它已经在代码中发现了意料之外的错误。
  • 断言对于大型的复杂程序或可靠性要求极高的程序来说尤其有用。
  • 通过使用断言,程序员能更快速地排查出因修改代码或者别的原因,而弄进程序里的不匹配的接口假定和错误等。
  • 断言通常含有两个参数:一个描述假设为真时的情况的布尔表达式,和一个断言为假时需要显示的信息。
  • 断言主要是用于开发和维护阶段。
  • 通常,断言只是在开发阶段被编译到目标代码中,而在生成产品代码时并不编译进去。
  • 在开发阶段,断言可以帮助查清相互矛盾的假定、预料之外的情况以及传给子程序的错误数据等。
  • 在生成产品代码时,可以不把断言编译进目标代码里去,以免降低系统的性能。
  • 用错误处理代码来处理预期会发生的错误状况,用断言来处理绝不应该发生的状况。
  • 避免把需要执行的代码放到断言中。
  • 用断言来注解并验证前条件和后条件
  • 前条件( preconditions)和后条件( postconditions)是一种名为“契约式设计(design by contract)”的程序设计和开发方法的一部分。
  • 前条件是子程序或类的调用方代码在调用子程序或实例化对象之前要确保为真的属性。前条件是调用方代码对其所调用的代码要承担的义务。
  • 后条件是子程序或类在执行结束后要确保为真的属性。后置条件是子程序或类对调用方代码所承担的责任。
  • 断言是用来说明前条件和后条件的有利工具。也可以用注释来说明前条件和后条件,但断言却能动态地判断前条件和后条件是否为真。
  • 对于高健壮性的代码,应该先使用断言再处理错误。

3 错误处理技术

可用的错误处理技术,包括:

  • 返回中立值
  • 换用下一个正确的数据
  • 返回与前次相同的数据
  • 换用最接近的合法值
  • 把警告信息记录到日志文件中
  • 返回一个错误码
  • 调用错误处理子程序或对象
  • 当错误发生时显示出错消息
  • 用最妥当的方式在局部处理错误
  • 关闭程序

正确性和健壮性:

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

4 异常

  • 用异常通知程序的其他部分,发生了不可忽略的错误。
  • 只在真正例外的情况下才抛出异常。
  • 不能用异常来推卸责任。
  • 避免在构造函数和析构函数中抛出异常,除非你在同一地方把它们捕获。
  • 在恰当的抽象层次抛出异常。
  • 在异常消息中加入关于导致异常发生的全部信息。
  • 避免使用空的 catch 语句。
  • 了解所用函数库可能抛出的异常。
  • 考虑创建一个集中的异常报告机制。
  • 把项目中对异常的使用标准化。
  • 考虑异常的替换方案。

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

  • 隔栏的使用使断言和错误处理有了清晰的区分。
  • 隔栏外部的程序应使用错误处理技术,在那里对数据做的任何假定都是不安全的。
  • 而隔栏内部的程序里就应使用断言技术,因为传进来的数据应该已在通过隔栏时被清理过了。

6 辅助调试的代码

  • 越早引入辅助调试的代码,它能够提供的帮助也越大。
  • 应该以这么一种方式来处理异常情况:在开发阶段让它显现出来,而在产品代码运行时让它能够自我恢复。这种方式称为“进攻式编程”。进攻式编程的方法如下:
  • 确保断言语句使程序终止运行。不要让程序员养成坏习惯,一碰到已知问题就按回车键把它跳过。让问题引起的麻烦越大越好,这样它才能被修复。
  • 完全填充分配到的所有内存,这样可以让你检测到内存分配错误。
  • 完全填充已分配到的所有文件或流,这样可以让你排查出文件格式错误。
  • 确保每一个 case 语句中的 default 分支或 else 分支都能产生严重错误(比如说让程序终止运行),或者至少让这些错误不会被忽视。
  • 在删除一个对象之前把它填满垃圾数据。
  • 让程序把它的错误日志文件用电子邮件发给你,这样你就能了解到在已发布的软件中还发生了哪些错误—如果这对于你所开发的软件适用的话。
  • 计划移除调试辅助的代码,可用的方法如下:
  • 伸用类似 ant 和 make 这样的版本控制工具和 make 工具
  • 使用内置的预处理器
  • 编写你自己的预处理器
  • 使用调试存根( debuging stubs)

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

  • 保留那些检查重要错误的代码
  • 去掉检查细微错误的代码
  • 去掉可以导致程序硬性崩溃的代码
  • 保留可以让程序稳妥地崩溃的代码
  • 为你的技术支持人员记录错误信息
  • 确认留在代码中的错误消息是友好的

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

  • 过度的防御式编程也会引起问题。
  • 如果你在每一个能想到的地方用每一种能想到的方法检查从参数传入的数据,那么你的程序将会变得臃肿而缓慢。
  • 更糟糕的是,防御式编程引入的额外代码增加了软件的复杂度。
  • 防御式编程引入的代码也并非不会有缺陷,和其他代码一样,你同样能轻而易举地在防御式编程添加的代码中找到错误—尤其是当你随手编写这些代码时更是如此。
  • 因此,要考虑好什么地方你需要进行防御,然后因地制宜地调整你进行防御式编程的优先级。

你可能感兴趣的:(防御)