防御式编程

防御式编程应该要检查数据的正确性,以及在出现错误数据时应该如何处理(避免程序崩溃,提示用户,让程序正确的崩溃,处理异常等)。 其核心思想是:子程序不应该因传入错误的数据而被破坏,要承认程序都会有问题,都需要被修改。

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

  • 检查所有来源于外部的数据

    当从文件,用户,网络或其它外部中获取数据时,要检查所有的数据值,以确保值在合法的范围内。

  • 检查输入的参数

  • 决定如何处理错误的数据

断言

断言对于大型的复杂程序或可靠性要求极高的程序来说尤其有用。通过使用断言,程序员能更快排查出因修改代码或者别的原因而导致的错误。

  • 输入和输出参数处于合理的范围
  • 子程序开始(结束)执行时,文件处于打开(关闭)状态
  • 文件或流已用只读‘只写或可读写的方式打开
  • 仅用于输入的变量没有被子程序所修改
  • 指针非空
  • 传入子程序的数组或其它窗口至少能容纳X个数据元素
  • 表已初始化,存储着真实的数值

断言只是用于开发和维护阶段。

  • 用错误处理代码来处理预期会发生的情况,断言来处理绝不应该发生的情况。
  • 避免把要执行的代码放到断言中
  • 用断言来注解前条件和后条件
  • 对于高健壮性的代码,应该先使用断言再处理错误

错误处理技术

当程序中出现错误数据时,可选的处理方式有如下几种

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

异常

异常是把代码的错误和异常事件传递给调用方的一种特殊手段。 异常和继承一样要审慎明智的使用。

  • 用异常通知程序的其余部分,发生了不可忽略的错误
  • 不能用异常来推卸责任
  • 避免在构造和析构函数中抛出异常,除非你在同一个地方捕获它们
  • 在恰当的抽象层次抛出异常
1
2
3
4
5
6
7
class Employee
{
  public TaxID GetTaxID() throw EOFException()
  {
      ...
  }
}

这个EOFException就和GetTexID的抽象层次不一致,暴露了内部是用读取文件的方式实现的。然而在这个抽象层次,调用方是不关心也不依赖于你是用什么方式实现的。所以正确的做法是再抽象一个Exception。

1
2
3
4
5
6
7
class Employee
{
  public TaxID GetTaxID() throw EmployeeDataNotAvailable()
  {
      ...
  }
}
  • 在异常消息中加入导致异常的全部消息
  • 避免使用空的catch语句
  • 了解所用函数库可能抛出的异常
  • 考虑创建一个集中的异常报告机制。 可以自己定制一个异常的机类,里面实现了如何记录异常的方法
  • 把项目中对异常的使用标准化
  • 考虑异常的替换方案
  • 使用防火墙的隔离技术,在某一层次上统一做数据的检查(数据验证层),下面的层次就可以不用做检查了。

    例如在类的public接口统一对传入的参数进行检测,private就不做这些检测了

  • 尽早引入辅助调试的代码和工具

  • 计划移除辅助调试代码
    • 使用类似ant和make这样的工具
    • 使用内置的预处理器 如DEBUG等
    • 使用自己编写的预处理器
    • 使用调试存根

采用进攻式编程

异常情况应该尽早的在开发阶段让它显现出来。

  • 确保断言语句使程序终止运行
  • 完全填充分配到的所有内存
  • 完全填充已分配到的文件或流
  • 确保每一个case语句中的default分支或else分支都能产生严重的错误。

Comments