异常处理原则

异常机制是现代主流语言的标配,但是异常处理问题虽然已经被讨论很多,也有很多经典书籍的论述,却一直都充满争议。很多人都觉得异常处理很难拿捏,同时也 难以理解一些语言或库的异常处理设计。我使用Java近10年,但直到最近我才感觉完全理清了对于异常处理的种种疑惑,下面就介绍一下我对Java异常处 理原理和原则的一些认识,欢迎交流探讨!

Exception和Error的区别

谈异常处理的第一个问题是:什么是异常?什么又不是异常?这个问题看似简单,其实很多人都没有分辨清楚,尤其是人们经常混用异常(Exception)、错误(Error)、失败(Failure)、缺陷(Bug)这些接近又有区别的词。

这里最需要对比区分的是Failure/Exception和Bug/Error。用例子来说,你尝试打开一个文件失败了,触发了一个IOException,这是一种运行时遇到的操作失败,它并不代表你的程序本身有问题。但是,Bug就不一样了,假设你有一个sort函数对数组进行排序,如果发现调用sort之后居然还有乱序情况,导致整个系统行为出错最后crash,那么这就不是异常而是错误,唯一的解决办法是修改程序解决Bug。所以,在Java中我们可以这样区分,异常(Exception)是一种非程序原因的操作失败(Failure),而错误(Error)则意味着程序有缺陷(Bug)。注意:其他语言术语可能不同,重要的是能从概念上区分它们。

Java的类继承体系非常清楚地区分了Exception和Error。

java.lang.Object
    java.lang.Throwable
        java.lang.Error
        java.lang.Exception

Exception下面是我们常见的各种异常类,Error下面最著名的就是AssertionError,它可以通过throw new AssertionError(...)显式抛出,也可以通过assert操作符产生。Java文档中明确说到:

An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.

就是说一般情况下不应该尝试用catch(Throwable)或者catch(Error)去捕 获Error,因为抛出这个Error就是希望整个程序马上停下来。可能有人会疑惑:“如果不捕获Error,程序crash了后果很严重啊”?这个就要 靠自己结合具体情况去判断了,让程序带着已经发作的Bug跑还是立刻停下来,到底哪个后果更严重?有时是前者,有时是后者。

声明异常和未声明异常的区别

Java可以在方法签名上显式地声明可能抛出的异常,但也允许抛出某些未声明的异常。那么,二者有何区别呢?我们自己在设计一个方法时如何决定是否在方法上声明某个异常呢?本质上讲,在方法签名上声明的异常属于方法接口的一部分,它和方法的返回值处于同一抽象层次,不随具体实现的变化而改变。比如,Integer类用于解析一个字符串到Integer型整数的valueOf方法:

/**
 * Returns an {@code Integer} object holding the
 * value of the specified {@code String}. The argument is
 * interpreted as representing a signed decimal integer, exactly
 * as if the argument were given to the {@link
 * #parseInt(java.lang.String)} method. The result is an
 * {@code Integer} object that represents the integer value
 * specified by the string.
 *
 * <p>In other words, this method returns an {@code Integer}
 * object equal to the value of:
 *
 * <blockquote>
 *  {@code new Integer(Integer.parseInt(s))}
 * </blockquote>
 *
 * @param      s   the string to be parsed.
 * @return     an {@code Integer} object holding the value
 *             represented by the string argument.
 * @exception  NumberFormatException  if the string cannot be parsed
 *             as an integer.
 */
public static Integer valueOf(String s) throws NumberFormatException {
    return Integer.valueOf(parseInt(s, 10));
}
public static int parseInt(String s, int radix)
            throws NumberFormatException{
    /*
     * WARNING: This method may be invoked early during VM initialization
     * before IntegerCache is initialized. Care must be taken to not use
     * the valueOf method.
     */

    if (s == null) {
        throw new NumberFormatException("null");
    }

    if (radix < Character.MIN_RADIX) {
        throw new NumberFormatException("radix " + radix +
                                        " less than Character.MIN_RADIX");
    }

    if (radix > Character.MAX_RADIX) {
        throw new NumberFormatException("radix " + radix +
                                        " greater than Character.MAX_RADIX");
    }

    int result = 0;
    boolean negative = false;
    int i = 0, len = s.length();
    int limit = -Integer.MAX_VALUE;
    int multmin;
    int digit;

    if (len > 0) {
        char firstChar = s.charAt(0);
        if (firstChar < ‘0‘) { // Possible leading "+" or "-"
            if (firstChar == ‘-‘) {
                negative = true;
                limit = Integer.MIN_VALUE;
            } else if (firstChar != ‘+‘)
                throw NumberFormatException.forInputString(s);

            if (len == 1) // Cannot have lone "+" or "-"
                throw NumberFormatException.forInputString(s);
            i++;
        }
        multmin = limit / radix;
        while (i < len) {
            // Accumulating negatively avoids surprises near MAX_VALUE
            digit = Character.digit(s.charAt(i++),radix);
            if (digit < 0) {
                throw NumberFormatException.forInputString(s);
            }
            if (result < multmin) {
                throw NumberFormatException.forInputString(s);
            }
            result *= radix;
            if (result < limit + digit) {
                throw NumberFormatException.forInputString(s);
            }
            result -= digit;
        }
    } else {
        throw NumberFormatException.forInputString(s);
    }
    return negative ? result : -result;
}

它声明抛出的NumberFormatException属于这个方法接口层面的一种失败情况,不管内部实现采用什么解析方法,都必然存在输入字符串不是合法整数这种情况,所以这时把这个异常声明出来就非常合理。相反,下面这个从帐户a向帐户b转账的transfer方法:

public boolean transfer(Account a, Account b, Money money) throws SQLException

它抛出SQLException就不对了,因为SQLException不属于这个transfer接口层面的概念,而属于具体实现,很有可能未来某个实现不用SQL了那么这个异常也就不存在了。这种情况下,就应该捕获SQLException,然后抛出自定义异常TransferException,其中TransferException可以定义几种和业务相关的典型错误情况,比如金额不足,帐户失效,通信故障,同时它还可以引用SQLException作为触发原因(Cause)。

public boolean transfer(Account a, Account b, Money money) throws TransferException {
    try {
        ...
        executeSQL(...);
    } catch (SQLException e) {
        throw new TransferException("...", e);
    }
}

什么情况下方法应该抛出未声明的异常?

前面谈到在编写一个方法时,声明异常属于接口的一部分,不随着具体实现而改变,但是我们知道Java允许抛出未声明的RuntimeException,那么什么情况下会这样做呢?比如,下面的例子中方法f声明了FException,但是它的实现中可能抛出RuntimeException,这是什么意思呢?

void f() throws FException {
    if (...) {
        throw new RuntimeException("...");
    }
}

根据上面提到的原理,未声明异常是和实现相关的,有可能随着不同实现而出现或消失,同时它又对应不到FException。比如,f方法依赖于对象a,结果在运行时a居然是null,导致本方法无法完成相应功能,这就可以成为一种未声明的RuntimeException了(当然,更常见的是直接调用a的方法,然后触发NullPointerException)。

其实,很多情况下抛出未声明的RuntimeException的语义和Error非常接近,只是没有Error那么强烈,方法的使用者可以根据情况来处理,不是一定要停止整个程序。我们最常见的RuntimeException可能要算NullPointerException了,通常都是程序Bug引起的,如果是C/C++就已经crash了,Java给了你一个选择如何处理的机会。

所以,抛出未声明异常表示遇到了和具体实现相关的运行时错误,它不是在设计时就考虑到的方法接口的一部分,所以又被称为是不可恢复的异常。有些Java程序员为了简便不声明异常而直接抛出RuntimeException的做法从设计上是不可取的。

如何捕获和处理其他方法抛出的异常?

下面例子中方法g声明了GException,方法f声明了FException,而f在调用g的时候不管三七二十一通过catch (Exception e)捕获了所有的异常。

void g() throws GException
void f() throws FException {
    try {
        g();
    } catch (Exception e) {
        ...
    }
    ....
}

这种做法是很多人的习惯性写法,它的问题在哪里呢?问题就在于g明明已经告诉你除了GException外,如果抛出未声明的RuntimeException就表示遇到了错误,很可能是程序有Bug,这时f还不顾一切继续带着Bug跑。所以,除非有特殊理由,对具体情况做了分析判断,一般不捕获未声明异常,让它直接抛出就行。

void g() throws GException
void f() throws FException {
    try {
        g();
    } catch (GException e) {
        ...
    }
    ....
}

但是,很遗憾有一种很典型的情况是g()是不受自己控制的代码,它虽然只声明了抛出GException,实际上在实现的时候抛出了未声明的但属于接口层面而应该声明的异常。如果遇到这种情况最好的做法是应该告诉g()的作者修改程序声明出这些异常,如果实在不行也只能全部捕获了。但是,对于自己写的程序来讲,一定要严格区别声明异常和未声明异常的处理,这样做的目的是理清Exception和Bug的界限。

自定义异常应继承Exception还是RuntimeException?

Java中区分Checked Exception和Unchecked Exception,前者继承于Exception,后者继承于RuntimeException。Unchecked Exception和Runtime Exception在Java中常常是指同一个意思。

public boolean createNewFile() throws IOException

上面的IOException就是一个著名的Checked Exception。Java编译器对Checked Exception的约束包括两方面:对于方法编写者来讲,Checked Exception必须在方法签名上声明;对于方法调用者来讲,调用抛出Checked Exception的方法必须用try-catch捕获异常或者继续声明抛出。相反,Unchecked Exception则不需要显式声明,也不强制捕获。

Checked Exception的用意在于明确地提醒调用者去处理它,防止遗漏。但是Checked Exception同时也给调用者带来了负担,通常会导致层层的try-catch,降低代码的可读性,前面例子中的Integer.valueOf方法虽然声明了NumberFormatException,但是它是一个RuntimeException,所以使用者不是必须用try-catch去捕获它。

实际上,对于自己编写的异常类来讲,推荐默认的是继承RuntimeException,除非有特殊理由才继承Exception。 C#中没有Checked Exception的概念,这种推荐的做法等于是采用了C#的设计理念:把是否捕获和何时捕获这个问题交给使用者决定,不强制使用者。当然,如果某些情况 下明确提醒捕获更加重要还是可以采用Checked Exception的。对于编写一个方法来讲,“是否在方法上声明一个异常”这个问题比“是否采用Checked Exception”更加重要。

总结

本文介绍了自己总结的Java异常处理的主要原理和原则,主要回答了这几个主要的问题:1)Exception和Error的区别;2)声明异常和 未声明异常的区别;3)什么情况下应抛出未声明异常;4)理解如何捕获和处理其他方法抛出的异常;5)自定义异常应继承Exception还是 RuntimeException。最后需要说的是,虽然有这些原理和原则可以指导,但是异常处理本质上还是一个需要根据具体情况仔细推敲的问题,这样才 能作出最合适的设计。

原文链接:http://www.cnblogs.com/weidagang2046/p/exception-handling-principles.html

时间: 2024-10-01 05:03:00

异常处理原则的相关文章

异常处理原则--good

异常机制是现代主流语言的标配,但是异常处理问题虽然已经被讨论很多,也有很多经典书籍的论述,却一直都充满争议.很多人都觉得异常处理很难拿捏,同时也难以理解一些语言或库的异常处理设计.我使用Java近10年,但直到最近我才感觉完全理清了对于异常处理的种种疑惑,下面就介绍一下我对Java异常处理原理和原则的一些认识,欢迎交流探讨! Exception和Error的区别 谈异常处理的第一个问题是:什么是异常?什么又不是异常?这个问题看似简单,其实很多人都没有分辨清楚,尤其是人们经常混用异常(Except

JAVA异常处理原则和log4j输出详细异常分析

1.多用try,catch;不要一个try,catch包含所有内容 好处:不同模块抓取不同异常,某一模块异常挂了,不影响其他模块的程序的进行 2.多写几个catche:尽量不要使用Exception这个大异常去包容所有异常 不要为了追求代码的简练,try,catch只写一个,使用Exception去抓取所有可能的异常,这只是理想状态,程序出错不是直接打印出来异常就完事了,应该在catche抓取异常的同时一方面给程序员输出错误日志,一方面做些处理反馈给用户,比如一些提示错误框或者错误页面,不能让用

异常处理 Exception

一.异常类 1.在C#中所有的异常都是使用一个异常类型的示例对象表示的,这些异常类型都是继承自System.Exception类型,或者直接使用System.Exception类型的实例对象: 2.在C#中,位于finally块中的代码可以保证不管代码是正常结束,还是进入异常处理代码块,其中的语句均会被执行. System.Exception类有一些属性值得注意,这些属性被所有从此类派生的异常类共享,这些属性是: Message:一个只读字符串,此属性为当前的异常提供了描述性信息: InnerE

java===Exception异常处理

package cn.china; /** * 异常:在运行时期发生的不正常情况. * 问题很多,意味着描述的类也很多, * 将其共性进行向上抽取,形成异常体系. * 最终问题就分成两大类: * throwable:无论是Error,还是异常,都是问题,就应该可以抛出,让调用者知道并处理. * 该体系的特点就在于throwable及其所有子类都具有可抛性. * 如何体现可抛性? * 其实是通过两个关键字体现的,throws,throw;凡是可以被这两个关键字操作的类和对象都具有可抛性. * 1.

黑马程序员_异常处理机制

???????????????????????????????-----Java培训.Android培训.iOS培训..Net培训.期待与您交流! 异常 异常 ??? 异常就是程序在运行时产生的异常情况. 异常的由来 ??? 问题也是现实生活中的具体事物,也可以通过java 类的形式进行描述,并封装成对象.异常就是java 对不正常情况进行描述后的对象的体现. 异常体系 ??????? Throwable ??????????? |--Error ??????????? |--Exception

程序健壮性之“异常处理”

定义 异常处理(Exceptional Handing)是代替Error Code的新法,分离了接收和处理错误代码. 基本模型 终止模型:将假设错误非常关键,将以致于程序无法返回到异常的地方继续执行,一旦异常被跑出就    表明错误已无法挽回,不能再继续执行. 恢复模型:异常处理程序的工作是修正错误,然后重新尝试调动出问题的方法,并认为第二次能够成功. 异常处理原则 反面示例: 1 OutputStreamWriter out = ... 2 java.sql.Connection conn =

C#异常处理总结

Exception类分析 常见的异常类 异常捕获 异常处理原则和建议 SystemException类继承Exception,前者是System命名空间中所有其他异常类的基类,在捕获异常的时候,我首先查看的就是Exception对象信息.Exception重要成员如下图 这里写图片描述 1.Message属性:产生异常原因的错误消息 [__DynamicallyInvokable] public virtual string Message { [__DynamicallyInvokable]

Java多层嵌套异常处理的基本流程

异常是程序中的一些错误,但并不是所有的错误都是异常,错误有时候是可以避免的.异常的对象有两个来源,一是Java运行时环境自动抛出系统生成的异常,而不管你是否愿意捕获和处理,它总要被抛出!比如除数为0的异常.二是程序员自己抛出的异常,这个异常可以是程序员自己定义的,也可以是Java语言中定义的,用throw 关键字抛出异常,这种异常常用来向调用者汇报异常的一些信息. 异常是针对方法来说的,抛出.声明抛出.捕获和处理异常都是在方法中进行的. Java异常处理通过5个关键字try.catch.thro

Java必知必会:异常机制详解

一.Java异常概述 在Java中,所有的事件都能由类描述,Java中的异常就是由java.lang包下的异常类描述的. 1.Throwable(可抛出):异常类的最终父类,它有两个子类,Error与Exception. Throwable中常用方法有: getCause():返回抛出异常的原因.如果 cause 不存在或未知,则返回 null. getMeage():返回异常的消息信息. printStackTrace():对象的堆栈跟踪输出至错误输出流,作为字段 System.err 的值.