Java编码中异常的使用建议

source_code.jpg

异常,精确计算机世界里的不安定份子。当然说的是异常的使用,似乎没有什么统一的标准。但是我觉得在一个团队或者是系统里面还是应该遵循一定的规矩。

无规矩不方圆。

下面列一下目前工作中总结的一些使用经验

首先看下Java异常的分类。

Java 异常的分类

  • 受检异常 (Checked Exception). 异常的处理由编译器来保证,如果方法声明异常但是没处理则编译失败。
  • 非受检异常 (Unchecked Exception). 编译器不会检查的一类异常
    • RuntimeException. IndexOutOfBoundsException, IllegalArgumentException 等
    • Error. 表示很严重的问题,应用自己也搞不定, 与代码编写者无关。 OutOfMemoryError 等。 不要继承Error来定义异常

异常机制提供了一种异常事件的冒泡处理机制,你可以选择你想处理这个异常的层次。 如果没有异常的话,你就要在很低的层次处理异常,并想办法通知上层

异常使用中的困惑

在我的使用或者团队大家的共识是,异常使用困惑的地方主要集中在下面两点:

  • 什么时候使用受检异常,什么时候使用非受检异常
  • 错误码和异常的使用

所以接下来主要围绕着两个困惑来进行解答

先来看看受检和非受检异常使用的时机,此处非受检指的是 RuntimeException

受检异常的使用时机

Use checked exceptions for conditions from which the caller
can reasonably be expected to recover

  • 当可以用来表示业务流转中的异常分支,给出更合理的错误提示时
  • 当你觉得调用方可以解决异常时
  • 当异常由外部不可控的因素导致时,例如用户输入数据库异常文件不存在网络

当你面临上面的情景时可以采用受检异常

非受检异常的使用时机

Use runtime exceptions to indicate programming errors (Bugs) - From Effective Java

RuntimeException 通常指程序运行时的错误,可以引申为调用方错误的使用了API或者类库设计者的问题。

Runtime exceptions can occur anywhere in a program, and in a typical one they can be very numerous. Having to add runtime exceptions in every method declaration would reduce a program's clarity. Thus, the compiler does not require that you catch or specify runtime exceptions - From Oracle The Java™ Tutorials

上面说到RuntimeException是程序中非常常见的错误,因此没有强制让编译器来检查这一类错误。但是程序员自己就需要做好预先检查的工作。RuntimeException 有个好处就是能够穿透到最上层,让最上层做决定。因此可以围绕下面几点来使用RuntimeException

  • 当你处理不了底层声明的异常时,将底层异常转换为RuntimeException。
  • 当用户调用的参数不符合程序预期,使用不当时

    一个强有力的证明: IllegalArgumentException 定义的是 RuntimeException

错误码和异常使用的时机

在没有异常机制的程序语言中,可能比较依赖错误码等来作为业务层面的流程判断。但是在Java中提供了异常的处理机制,这种冒泡处理机制,让你可以选择你想处理这个异常的层次。 如果没有异常的话,你就要在很低的层次处理异常,并想办法通知上层。

总结来讲,错误码和异常的特点就是

  • 异常是强类型且类型安全的分支控制技术
  • 返回错误值则是弱类型不安全

作为Java程序员,应该优先使用异常来反馈程序中的异常情况。

使用异常来区分正常的业务场景和错误的业务路径

    public String foo() IOException, FileNotFoundException {
        ...
    }

    // 一个好的使用方法应该是这样
    try {
        foo();
    } catch (IOException ioe) {
        // 给出友好的提示信息
    } catch (FileNotFoundException fe) {
        // 给出友好的提示信息        
    }

一种异常和错误码的混合方案

定义一个业务异常,然后异常中添加字段来表示错误码。

enum ErrorCode {
    INVALID_PARAM, INVALID_PATH, INVALID_STAUS;
}

class MyException extend Exception {
    public MyException(String msg) {
        super(msg);
    }
}

// 使用场景
public String foo() throws MyException {
    if (...) {
        throw new MyException(ErrorCode.INVALID_PARAM.getName());
    }

    ...

    if (...) {
        throw new MyException(ErroCode.INVALID_PATH.getName());
    }

    ...

    if (...) {
        throw new MyException(ErrorCode.INVALID_STATUS));
    }
}


错误码和异常使用的建议

  • 同构系统里优先使用异常来表示错误。
    • 比如两个Java类相互调用,方法之间就可以用异常来处理非正常情况,这样可以充分发挥异常类型的作用
  • 异构系统的情况使用返回错误信息的方式。
    • 比如提供一个webservice接口,就可以用不同的业务编码来表示错误情况

最后是异常使用的一些实践

异常处理的实践

  • 记得释放资源。特别是数据库还有网络资源,使用 try-catch-finally
  • 不要使用异常作控制流程之用
  • 不要忽略异常
  • 生产代码不要 printStatckTrace()
  • 尽量缩小异常的捕获范围

具体来讲就是

try-catch-finally 释放资源

OutputStreamWriter out = ... 
java.sql.Connection conn = ... 
try { 
    ...
} catch(SQLException sqlex) { 
    ...
} catch(IOException ioex) { 
    ...
} finally { 
    if (conn != null) { 
        try { 
            conn.close(); 
        } catch(SQLException sqlex2) { 
            ...
        } 
    } 

    if (out != null) { 
        try { 
            out.close(); 
        } catch(IOException ioex2) { 
        ...
        } 
    } 
}

不要将异常用作控制流(if-else)

虽然异常本身具有流程控制的属性,但是直接作为类似 if-else这样的方式来使用还是不应该的

public void check(String filePath) FileNotFoundException, FileExistException  {
    ...
} 

// 使用
try {
    check();
} catch (FileNotFoundException fne) {
    // do-file not found things ..
} catch (FileExistException fee) {
    // do-file found things
}

不要忽略异常

  • 可以只打个日志,但是不要什么都不做
  • 如果你什么都做不了,就不要抓或者转换为RuntimeException
try {
    Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException ex) {} //忽略的异常,这样要不得。出了问题坑自己、坑队友
    
// 接收异常
try {
    ...
} catch (Exception e) {  // 过分泛华的异常,不利于排查问题
    ...
}

// 抛出异常
try {
    ...
} catch (IOException ioe) {
    throw new Exception(ioe); // 泛化了异常, 外层调用丢失了异常类型的优势
}

// 自定义异常
try {
    ...
} catch (SqlException sqle) {
    throw new MyOwnException(); // 定义了新的异常,但是丢了原始异常信息
}

生产代码不要 printStackTrace();

// 不好的方式
try {
    ... 
} catch (IOException e) {
    e.printStackTrace();
}

try {
    ...
} catch (IOException e) {
    logger.error("message here" + e);
}

try {

} catch (IOException e) {
    logger.error("message here" + e.getMessage());
}

// 比较好的方式
try {
    ...
} catch (IOException e) {
    logger.error("message here", e);
}

异常的捕获范围

  • 循环的场景,注意try代码块的范围
// bad case
try {
    while(rs.hasNext()) {
        foo(rs.next());
    }
} catch (SomeException se) {
    ...
}

// good case
while(rs.hasNext()) {
    try {
        foo(rs.next());
    } catch (SomeException se) {
        ...
    }
} 

参考资料

http://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html

http://stackoverflow.com/questions/6115896/java-checked-vs-unchecked-exception-explanation

http://www.javapractices.com/topic/TopicAction.do?Id=129

http://www.ibm.com/developerworks/cn/java/j-lo-exception/index.html

http://www.blogjava.net/freeman1984/archive/2013/07/26/148850.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容