针对文章编目准确性和健壮性的软件结构健壮性准确性Error类几种典型错误Exception类Runtime异常其他异常Checked异常: UnChecked异常的处理机制异常类断言的作用、应用场合防御式编程
面向正确性和健壮性的软件结构健壮性
系统未正常输入或不正常的外部环境也能显示正常程度。
面向鲁棒的编程:处理意外行为和错误终止,即使中止执行,也能准确/毫不含糊地向用户提示完整的错误消息。 错误信息对调试很有用。 总是假设用户是恶意的,假设自己的代码可能失败,认为可能会愚弄用户,输入什么。 关闭细节,限定用户的恶意行为。 考虑到极端的情况,没有“不可能”。
按照spec运行正确性程序的能力是最重要的质量指标。
正确性:不会给用户带来错误结果的健壮性:尽可能保持软件运行,而不是经常退出
准确性倾向于直接报告错误,健壮性倾向于容错。
安全软件和用户软件是不同的
错误类
说明Javaruntimesystems内很少发生的系统错误和资源不足。 例如,VirtualMachineError、LinkageError等。
注意:不应该抛出这种类型的对象。
内部错误:程序员通常什么也做不了。 一旦发生,我们将寻找让程序优雅地退出的方法
错误类型:用户输入错误、设备错误和物理限制。 解决不了!
一些常见错误VirtualMachineError会抛出所需的资源,以继续运行,指示Java虚拟机已损坏或已用尽。 内存外错:如果Java虚拟机因内存不足而无法分配对象,且垃圾收集器无法提供更多内存,则抛出。 StackOverflowerError :由于APP应用程序递归太深,在发生堆栈溢出时抛出。 internal error :抛出以指示JVM发生了意外的内部错误。 LinkageError :一个类对另一个类有某种依赖性; 但是,后者类在前者类编译后发生了无法比较的变化。 noclassdeffounderror :如果JVM或类加载器实例尝试加载类的定义,但找不到定义,则会抛出Exception类
说明程序引起的错误。 例如,文件非基础执行、io执行等。
这些错误可以由程序捕获和处理。 例如,执行替代操作或关闭所有文件、网络和数据库连接并以优雅方式退出。
异常:在程序运行中的异常事件中,程序无法按预期运行。 将错误的信息传递给高层的调用方,报告“事件现场”的信息。 这是return以外的第二条退出路线。
方法抛出封装错误消息的对象。 方法立即结束,不返回值。 此外,调用方法的代码不会重新开始执行; 相反,异常处理程序将开始查找可以处理此特定错误条件的异常处理程序,如果未找到异常处理程序,则整个系统将完全终止。
运行时异常执行时异常:原因是程序员在代码中处理不当
从运行时执行继承的异常存在以下问题:
-乱绕
过境数组访问
空指针访问
运行时异常是由程序源代码中引入的故障引起的,通过代码预先验证可以避免这些故障。
其他异常其他异常:由外部因素引起
试图读取超出文件末尾的内容
试图打开不存在的文件
-尝试搜索不表示现有类的字符串的类对象
运行时间以外的异常是由程序员不能完全控制的外在问题引起的。 即使在代码中预先验证,文件是否存在也无法避免完全禁用。
已检查异常:必须捕获并处理异常。 或者,必须声明方法并抛出异常,让编译器知道无法处理异常,然后使用该方法的代码才能处理异常。 如果无法处理异常,也可以声明抛出异常。 编译器检查是否完成了两个任务之一: catch或declare。
必须捕获并指定错误处理程序handler。 如果不指定,编译将失败。 类似于编程语言中的静态类型检查
5个关键字trycatchfinallythrowsthrow
throws :“这种方法可能会发生XX异常
throw :抛出xx异常
try、catch、finally :捕获xx异常进行处理
也可以使用throws声明或try/catch捕获异常,但大多数情况下不需要。 另外,——不应该掩耳盗音。 不要听信发现的编程错误
未选中异常:编译器不检查错误和运行时异常。 错误表示在APP应用程序外部发生的情况,如系统崩溃。 运行时异常通常由APP应用逻辑错误引起。 在这种情况下,你什么也做不了,但你必须重写程序代码。 所以这些不是编译器检查的。 在开发和测试过程中会发现这些运行时异常。 然后,需要重建代码以消除这些错误。
未选中异常: (错误和运行时异常)编译时无需通过try…catch等机制进行处理。
注:不处理也没关系。 编译没有问题,但运行时会出现错误
,代表程序中的潜在bug。类似于编程语言中的动态检查。 总结如果客户端可以通过其他的方法恢复异常,那么采用checked exception;如果客户端对出现的这种异常无能为力,那么采用unchecked exception;异常出现的时候,要做一些试图恢复它的动作而不要仅仅的打印它的信息。
尽量使用unchecked exception来处理编程错误,如果client端对某种异常无能为力,可以把它转变 为一个unchecked exception,程序被挂起并返回客户端异常信息。Checked exception应该让客户端从中得到丰富的信息。 错误可预料,但无法预防,但可以有手段从中恢复,此时使用checked exception。要想让代码更加易读,倾向于用unchecked exception来处理程序中的错误。可预料但不可预防,脱离了你的程序的控制范围
对特殊结果(即预期情况)使用Checked exception,使用unchecked exception来发出错误信号(意外故障)。应该仅使用unchecked exception来表示意外故障(即bug),或者如果您希望客户机通常会编写代码来确保异常不会发生,因为有一种方便且廉价的方法来避免异常;否则应使用checked exception。
Checked异常的处理机制(1)声明:throws抛出异常
“异常”也是方法和client端之间spec的一部分,在post-condition中刻画。标头更改以反映方法可以抛出的checked异常。
一个方法也可以抛出多个异常。Error 和 unchecked exceptions无法抛出。
抛出异常包括:1. 你所调用的其他函数抛出了一个checked exception——从其他函数传来的异常 2. 当前方法检测到错误并使用throws抛出了一个checked exception——你自己造出的异常
此时需要告知你的client需要处理这些异常,如果没有handler来处理被抛出的checked exception,程序就终止执行。
如果子类型中override了父类型中的函数,那么子类型中方法抛出的异常不能比父类型抛出的异常类型更宽泛。子类型方法可以抛出更具体的异 常,也可以不抛出任何异常。如果父类型的方法未抛出 异常,那么子类型的方法也不能抛出异常。
(2)抛出:throw异常
利用Exception的构造函数,将发生错误的现场信息充分的传递给client。
throw异常的方法:
找到一个能表达错误的Exception类/或者构造一个新的Exception类,构造Exception类的实例,将错误信息写入,抛出它。一旦抛出异常,方法不会再将控制权返回给调用它的client,因此也无需考虑返回错误代码。
(3)捕获与处理
异常发生后,如果找不到处理器, 就终止执行程序,在控制台打印出stack trace。
也可以不在本方法内处理, 而是传递给调用方,由client处理。
编译器严格执行throws说明符。如果调用抛出选中异常的方法,则必须处理或传递它。
尽量在自己这里处理,实在不行就往上传——要承担责任!但有些时候自己不知道如何处理,那么提醒上家,由client自己处理。
本来catch语句下面是 用来做exception handling的,但也可以在catch里抛出异常,这么做的目的是:更改exception的类型,更方便client端获取错误信息并处理
(4)清理现场、释放资源
当代码抛出异常时,它将停止处理方法中的剩余代码并退出该方法。如果该方法获取了一些只有该方法知道的资源(文件、数据库连接等),并且该资源必须被清除,则这是一个问题。如果异常发生前曾申请过某些资源,那么异常发生后这些资源要被恰当的清理。
一种解决方案是捕获并重新引用所有异常。但是这个解决方案很乏味,因为您需要清理正常代码和异常代码中两个位置的资源分配。
使用finally解决此问题。
Try-Catch-Finally:无论是否捕获到异常,finally子句中的代码都会执行。
自定义异常类如果JDK提供的exception类无法充分描述你的程序发生的错误,可以创建自己的异常类
要定义checked异常,创建java.lang.exception的子类(或子类的层次结构)
public class FooException extends Exception { public FooException() { super(); } public FooException(String message) { super(message); } public FooException(String message, Throwable cause) { super(message, cause); } public FooException(Throwable cause) { super(cause); }}有时会出现不希望强制每个方法在其throws子句中声明异常实现的情况。在这种情况下,您可以创建一个扩展java.lang.RuntimeException的unchecked异常。方法可以抛出或传播fooRuntimeException异常而不声明它。public class FooRuntimeException extends RuntimeException { ...} public void calculate(int i) { if (i < 0) { throw new FooRuntimeException("i < 0: " + i); }} 断言的作用、应用场合最好的防御:不要引入bug
如果无法避免,尝试着将bug限制在最小的范围内,限定在一个方法内部,不扩散。尽快失败,就容易发现、越早修复
断言:当不满足前提条件时,此代码通过抛出AssertionError异常终止程序。
检查前置条件是防御式编程的一种典型形式
–真正的程序很少没有bug。
–防御性编程提供了一种减轻bug影响的方法,即使不知道它们在哪里。
断言:在开发阶段的代码中嵌入,检验某些“假设”是否 成立。若成立,表明程序运行正常,否则表明存在错误。
每个断言都包含一个布尔表达式,该表达式在程序执行时为真。
如果不是真的,JVM将抛出AssertionError。此错误表示您有一个需要修复的无效假设。增强程序员对代码质量的信心:对代码所做的假设都保持正确。断言即是对代码中程序员所做假设的文档化,也不会影响运行时性能。
使用场景:
内部不变量: 断言某个值在某个约束内,例如,断言x>0。
表示不变量: 断言对象的状态在约束内。在方法执行之前或之后,类的每个实例必须是什么样的?类不变量通常通过私有布尔方法进行验证,例如checkRep()。
控制流不变量: 断言不会到达某个位置。例如,switch case语句的default子句。
方法的前置条件: 调用方法时什么必须为真?通常用方法的参数或对象的状态来表示。
方法的后置条件: 在一个方法成功完成后,什么是真的?
仅输入变量的值不会被方法更改
指针不为空
传入方法的数组或其他容器可以包含至少X个数据元素
表已初始化为包含实值
当方法开始执行(或完成)时,容器为空(或满)
高度优化、复杂方法的结果与较慢但编写清晰的例程的结果相匹配
断言主要用于开发阶段,避免引入和帮助发现bug。实际运行阶段, 不再使用断言,避免降低性能。
注意:程序之外的事,不受你控制,不要乱断言
比如:文件/网络/用户输入等
断言只是检查程序的内部状态是否符合规约,断言一旦false,程序就停止执行,代码无法保证不出现此类外部错误,要使用Exception机制去处理。
许多断言机制的设计使断言仅在测试和调试期间执行,并在程序发布给用户时关闭。断言是一个很好的工具,可以保护您的代码不受bug的影响,但是Java在默认情况下关闭了断言!要记得打开(-ea)
断言通常包括程序的正确性问题。使用断言处理 “绝不应该发生”的情况
–如果针对异常情况触发断言,纠正措施不仅仅是优雅地处理错误,而是更改程序的源代码、重新编译并发布新版本的软件。
Exception通常包括程序的健壮性问题。使用异常来处理你“预料到可以发生”的不正常情况
–如果错误处理代码用于处理异常情况,则错误处理将使程序能够优雅地响应错误。
防御式编程的基本思路防御式编程是一种防御性设计,旨在确保软件在不可预见的情况下继续运行。在需要高可用性、安全性或安全性的情况下,通常使用防御编程实践。
防御式编程技术:
保护程序不受无效输入的影响断言异常特定错误处理技术路障调试辅助工具防御式编码的最佳形式不是首先插入错误,可以将防御编程与其他技术结合使用。
保护程序不受无效输入的影响:对来自外部的数据源要仔细检查,例如:文件、网络数据、用户输入等;对每个函数的输入参数合法性要做仔细检查,并决定如何处理非法输入
设置路障:
类的public方法接收到的外部数据都应被认为是dirty的,需要处理干净再传递到private方法——隔离舱。“隔离舱”外部的函数应使用异常处理,“隔离舱”内的函数应使用 断言。
操作间技术。数据在进入操作间之前要消毒。操作间里的任何东西都是安全的。关键的设计决策是决定在错左键放什么,不放什么,门放在哪里,哪些例行程序被认为在安全区内,哪些在外面,哪些对数据进行了消毒。最简单的方法通常是在外部数据到达时对其进行消毒,但数据通常需要在多个级别进行消毒,因此有时需要进行多个级别的消毒。