CHAPTER 12 面向正确性与健壮性的软件构造
12.1 正确性与健壮性
--编写良好的代码应在内外部之间做好隔离,防止错误扩散。
--对外倾向于健壮性,对内倾向于正确性。
--复习:正确性 + 健壮性 = 可靠性
12.2 正确性与健壮性的衡量标准
--MTBF:平均失效间隔
12.3 Error & Exception
--内部错误:难以处理,选择合适方式结束运行
--异常:抛出->捕获->处理全流程
--Error举例:用户输入错误/设备错误/物理限制
12.3.1 Exception Handling
--让程序执行更清晰。
12.3.2 异常分类
--常见RuntimeException:
--强转错误;空指针异常;数组越界
--可避免的:在代码中对这些错误提前进行验证
--区别于非运行时异常:如文件不存在,即使代码前验也难以确保不会失效。
12.3.3 Checked and unchecked exceptions
--编译器可对异常进行初步检查。
--关于unchecked Exceptions:
--常见非检查异常:没有必要进行抛捕handle
--java.lang.NullPointerException
--java.lang.ArrayIndexOutOfBoundsException
--java.lang.NumberFormatException
--java.lang.ClassCastException
--常见检查异常:
--FileNotFoundException
--IOException
--区别于运行时/非运行时异常(两种维度)
--尽量使用非检查异常来处理编程错误。
--当无法预防的错误产生且有修复的手段,选择检查异常。如:
12.3.4 通过抛出声明检查异常
--异常应属于post-condition,是spec的一部分
--关于方法规约
--要求程序员在spec中写明所有checked exceptions
--用于表示意外失败的未经检查的异常-客户端或实现中的错误-不是方法后置条件的一部分,因此它们不应出现在@throws 或 throws 中。例如,NullPointerException 永远不需要在规范中提及。
--checked exceptions来源:调用函数/自己抛出
--继承关系下的异常抛出说明:
//复习
--LSP是一种子类型关系的特殊定义,称为(强)行为子类型化,它依赖于以下限制:
--不能在子类型中加强先决条件。
--不能在子类型中削弱后置条件。
--超类型的不变量必须保留在子类型中。
--子类型中方法参数:逆变。
--子类型方法的返回值:协变。
--子类型的方法不应抛出新的异常,除非这些异常本身是超类型方法抛出的异常的子类型。
12.3.5 抛出异常
--找到一个合适的异常类:找到一个能表达错误的异常类,或者构造一个新的Exception类。
--创建该类的对象构造Exception类的实例,将错误信息写入。
--一旦一个方法抛出异常,它就不会返回给它的调用者,因此也不需要考虑返回错误代码。
12.3.6 创建异常类:
--只需从Exception或从Exception的子类(例如 IOException)派生它。
12.3.7 try-catch-finally语句块,已在之前实验中多次实践
12.3.8 Stack Trace
--当Java方法内部发生异常时,该方法会创建一个Exception对象并将该Exception对象传递给JVM(即该方法“抛出”一个异常)。Exception对象包含异常的类型,以及发生异常时程序的状态。
--JVM负责寻找异常处理程序来处理异常对象。它在调用堆栈中向后搜索,直到找到与该特定类Exception对象匹配的异常处理程序。如果JVM在调用堆栈中的所有方法中都找不到匹配的异常处理程序,它将终止程序。
12.4 断言
--静态检查:通过在编译时捕获它们来消除许多错误。
--动态检查:Java通过动态捕获它们使数组溢出错误成为不可能。 如果您尝试使用数组或列表边界之外的索引,则Java会自动产生错误。
--不变性:不可变类型是其值一旦创建就永远不会改变的类型。
--不可变值:通过final,可以分配一次但永远不会重新分配。
--不可变引用:通过final,这使得引用不可重新分配,但引用指向的对象可能是可变的或不可变的。
12.5 防御式编程:结合其他技术使用。