文章目录
面向正确性与健壮性的软件构造
1 健壮性和正确性
-
健壮性:系统在不正常输入或不正常外部环境下仍能够表现正常的程度
-
面向健壮性的编程
- 处理未期望的行为和错误终止
- 即使终止执行,也要准确/无歧义的向用户展示全面的错误信息
- 错误信息有助于进行debug
-
健壮性定律:(Postel’s Law)
- 程序员应总是假定用户恶意、假定自己的代码可能失败
- 把用户想象成白痴,可能输入任何东西(因此要向用户返回明确错误信息)
- 对别人宽容点,对自己狠一点(保证自己的代码必须全部安全、放宽用户的输入要求)
-
如何实现健壮性:
- 封闭实现细节,限定用户的恶意行为(用户不应该访问库、数据结构、指针等)
- 考虑极端情况,没有“不可能”:开发人员考虑如何处理极不可能发生的情况,并相应地执行处理。
-
和健壮性的区别:
- 正确性:永不给用户错误的结果,没有结果比不准确的结果要好;倾向于直接报错。有利于生成更简洁、更容易理解和维护的代码
- 让开发者变得更容易:用户输入错误,直接结束
- 健壮性:尽可能保持软件运行而不是总是退出,即使这会导致有时不准确的结果;倾向于容错
- 让用户变得更容易:出错也可以容忍,程序内部已有容错机制
- 正确性:永不给用户错误的结果,没有结果比不准确的结果要好;倾向于直接报错。有利于生成更简洁、更容易理解和维护的代码
-
使用方式: 对外的接口,倾向于健壮性;对内的实现,倾向于正确性。在内外部之间做好隔离,防止“错误”扩散
-
可靠性:系统在规定条件下执行所需功能的能力
2 Throwable
3 Error/Runtime异常、其他异常
- Unchecked exceptions
- Error
- Runtime异常
- 编译器不会检查error和运行时异常,此外的异常统称为。因为即使出现也解决不了,只有在运行时才会报错,此时需要重新编写代码。
- 对于Unchecked exceptions而言,不需要在编译时使用try…catch等机制处理。可以不处理,编译没问题,但执行时出现就导致程序失败,代表程序中有潜在的bug。类似于编程语言中的动态类型检查。
- Checked exceptions
- 其他异常
4 Checked异常、Unchecked异常
这是从异常处理机制角度所做的分类
Java中的异常结构层次:(红色编译时不报错,蓝色编译时报错)
5 Checked异常的处理机制:
-
通过throws声明已检查的异常
- 如果遇到无法处理的情况,Java方法可以throw一个异常。一个方法不仅会告诉Java编译器它可以返回什么值,还将告诉编译器可能抛出的异常类型。这样的话,需要在规约的前置条件中写出。
- 如果遇到无法处理的情况,Java方法可以throw一个异常。一个方法不仅会告诉Java编译器它可以返回什么值,还将告诉编译器可能抛出的异常类型。这样的话,需要在规约的前置条件中写出。
-
如何抛出异常
- 找到一个能表达错误的Exception类,或者构造一个新的Exception类
-构造Exception类的实例,将错误信息写入抛出
- 找到一个能表达错误的Exception类,或者构造一个新的Exception类
-
捕获异常
-
异常发生后,如果找不到处理器,就终止执行程序,在控制台打印出栈轨迹。
-
要捕获异常,请设置try/catch块
-
如果try块中的任何代码引发catch子句中指定的类的异常,则try中后面的代码不会再执行,执行捕获异常的catch代码;如果try中没有抛出异常,则catch中代码不会被执行
-
catch中也可以继续向上抛出异常,不在本方法内处理,而是传递给调用方,由client处理(“推卸责任”)
-
原则:
- 尽量在自己这里处理,实在不行就往上传——要承担责任;但有些时候自己不知道如何处理,那么提醒上家,由client自己处理。
- 如果父类型中的方法没有抛出异常,那么子类型中的方法必须捕获所有的checked exception,因为子类型方法中不能抛出比父类型方法更多的异常。
-
6 自定义异常类
-
如果JDK提供的exception类无法充分描述你的程序发生的错误,可以创建自己的异常类
-
可以创建checked exception,也可以创建unchecked exception。前者需要在方法签名中声明继承自exception类或者其子类,并在方法体中捕获或抛出;后者需要继承运行时异常类,不需要捕获,也不需要声明。
7 断言的作用、应用场合
-
断言:在开发阶段的代码中嵌入,检验某些“假设”是否成立。若成立,表明程序运行正常,否则表明存在错误
-
每个断言都包含一个您认为在程序执行时为真的布尔表达式。如果断言不为真,则JVM 会抛出一个断言异常,这意味着代码中有错误,需要被修复;如果断言为真,则表示程序员代码编写没问题,可以继续下一步操作。
-
断言的两种形式:
assert condition;
assert condition : message;//message在发生错误时显示给用户,便于快速发现错误所在
- 应用场合
-
断言主要用于开发和维护阶段,避免引入和帮助发现bug; 实际运行阶段,不再使用断言,避免降低性能
-
如果条件语句或switch语句不能覆盖所有可能的情况,那么最好使用断言来阻止非法情况。
-
8 防御式编程的基本思路
-
对于无效输入的保护机制
-
断言
-
异常
-
特殊的错误处理机制
-
对外隔离(设置路障)
- 设置路障的一种方法是将某些接口指定为“安全”区域的边界,检查跨越安全区域边界的数据的有效性,如果数据无效,则做出相应响应。
- 设置路障的方法:
- 使用public方法充当隔离舱
- 类中的public方法会假定数据不安全,它们负责检查数据并对其进行清理;一旦数据被类的公共方法接受,类的私有方法就可以假定数据是安全的。
- 操作间技术
- 路障的使用使得断言和错误处理之间有了明显的区别:
- 路障之外的例程应该使用异常处理函数,因为对数据进行任何假设都是不安全的;
- 路障内的例程应该使用断言,因为传递给它们的数据已经经过清理,如果路障内的某个例程检测到错误数据,则这是程序中的错误,而不是数据中的错误。
-
调试辅助手段