异常处理最佳实践

一、异常的分类

常规分类:

1、运行时异常(RuntimeException);

2、编译时异常(CheckedException)

用途分类:

1、打断(终止)程序继续往下运行;

2、打断程序继续往下运行,并将异常原因和信息送往上层。

特点分类:

1、可以获得异常的原因;

2、可以获得异常的代号;

3、可以获得异常的错误行号;

4、可以获得异常的堆栈信息(程序运行轨迹);

5、可以获得异常的类型;

……等。

判断分类:

1、可以预判(预先判断)、自主定义的异常,比如我们自己写程序,在Service中,当 (id==null && type==2) 时,抛出一个AbcException异常;

2、不可预判、不透明的异常,比如工具库内部的异常(SQLException、IBEException)等。

针对这些不同类型的异常,他们的使用方式 和 处理方式,是不一样的,详见下面的分析。

二、异常的常见使用场景分析

1、举例1:假设有web、service、dao三层,但是在dao层报错,抛出SQLException或者其他DAO工具库内部的Exception。

对于这种情况,我们通常需要日志记录异常信息;而在web层反馈到view页面上,则不会告诉用户底层的错误原因,只是告诉用户出现了未知异常,或者大概是什么原因出错。

分析:1)工具库内部的错误,比如SQLException,对我们来说,是不可预知的,我们不知道何时、在什么情况下会出现错误,也就是说无法预先判断。2)dao层报错,需要通知上层,也需要记录日志,这个日志是在dao层记录,还是让上层决定 是否记录? 其实这是个普遍问题,放到service层或者通用工具类中,也有这个选择:到底是自己记录日志并且把异常信息往上层抛,还是自己不记录日志,只把完整的异常信息传递给上层,让上层决定是否记录日志。我赞成的是后者,通常情况,我们统一的在中间层都不记录日志信息,有异常直接往外层throw抛出,对开发者而言非常方便(这才是重点)。

2、举例2:接上例,假设我们在service层中,做了一个判断:当 (password不正确) 时,往外抛出一个自定义的异常,异常信息为 “密码错误!!”。对于这种情况,通常情况下,我们不需要记录日志,因为这个错误是我们自己定义的,而且是可以预料的,错误信息的作用是告诉用户,而不是作为日志分析作用。

分析:1)明确一点,对于这种类型的错误,我们不需要记录日志,但是仍然要向上层返回错误信息,并且注意到一点,我们只需要错误信息message,不需要异常的行号、运行时的堆栈信息等。

三、Java异常处理分析

找准异常处理的出口。

1)通常,异常的出口为:

web层(action、controller),最终其实归结到 Servlet 和 Filter。

所以,在我们编写程序的最外层,比如action中,是不应该再出现“未捕获的异常”的,因为这样我们就没办法控制了。

因此,我们要在最外层,做好应有的异常处理。

特别提醒,不只是action、controller,一些对外提供服务的Servlet或者Webservice,都算是异常的最终出口。

2)自定义的异常出口

除了web层外,其他地方也可以成为“自定义”的异常出口。

比如,一个utils抛出了异常,我在service里面就捕获了,然后“就地解决”掉,不再继续传播这个异常。

这种情况,就是所谓的“自定义的异常出口”。

异常的处理方式

根据异常的出口分类,有“web层出口的异常”和“自定义出口的异常”。

1、web层出口的异常

又可以分类如下:

1)需要实现异常消息的“国际化”(多语言版本)。

2)不需要“国际化”。

a)异常出口直接面向的是用户或者浏览器。

b)异常出口面向的是其他系统。

对于1),经典的解决方案就是:定义所有可以预知的错误信息(用errorCode和errorMsg),errorCode对应多个语言版本的errorMsg。

对于2),则可以不用errorCode和errorMsg,直接往外抛出错误信息即可。但是不推荐这么做。原因如下:

- 异常信息可能很简陋,如果用堆栈信息,那么堆栈信息又不方便在客户端展示。

- 异常信息也可能很大,例如一个sql错误,仅errorMessage就可能几千个字符,不便于web端展示。

- 原生的异常信息,通常是给开发人员看的,对于用户来讲,可能不好理解。

对于a),可以直接返回异常的具体信息,如果要支持国际化,也是可行的。

对于b),把异常信息直接返回给其他系统,不是一种好的做法,因为有可能其他系统需要,对异常类型做判断或加工。

故,最好是,返回errorCode和errorMsg,其他系统可以根据errorCode去做判断或加工。

总结:

综合考虑 1)2)a)b),一种较好的异常处理方式是: 定义所以可以预知的错误信息,用errorCode和errorMsg表示。必要是可以增加errorMsg的语言版本。

这就要求我们,在编写异常处理的代码时,不要用中文。要么把错误信息定义成errorCode,要么用简洁的英文描述错误信息。

例如,

try{

.........

}catch(Excpetion e){

throw new MyException(ErrorCode.C0001, e.getMessage());

}

或者

try{

........

}catch(Excpetion e){

throw new MyRuntimeException(" encode failure! n must big than 0.");

}

或者

try{

........

}catch(IOExcpetion e){

throw new ExceptionWrapper(e); //用ExceptionWrapper这个工具类,包装原始的错误信息

}

这三种写法,都是符合上面的规范的。但是使用场景不同。

第一种,面向的是较高层次的代码,比如service层、dao层,它可以直接传递到web层去。所以就地定义了ErrorCode。

第二种和第三种写法,面向的是较底层的代码,比如最底层的工具类。

在面向客户的项目中,建议只用第一种写法。第二、三种写法,更多的是用于那些面向服务的项目中。

2、自定义出口的异常

有时候,我们不需要把异常再通知外部,在自己内部处理就行了。

比如:

try{

InputStream in = IOUtils.getInputStream(filePath);

}catch(IOException e){

log.warn(e);

}

出错时,直接记录日志就OK了,不需要再把这个错误往外抛出。

有时候,也可以这么做:

public void doParse(){

.......

try{

........

}catch(IOExcpetion e){

throw new ExceptionWrapper(e); //用ExceptionWrapper这个工具类,包装原始的错误信息

}

......

}

public void doServiceAA(){

try{

doParse();

}catch(Excpetion e){

throw new MyException(ErrorCode.C0001, e.getMessage());

}

}

public void doServiceAA(){

try{

doParse();

}catch(Excpetion e){

// ignore this error

}

}

就是说,错误时,在这个地方不做处理,把错误往外抛出,交给上层去决定怎么来处理这个错误。

但这种用法还是比较少的。不建议滥用。

四、异常处理最佳实践

1、以 STC票控项目 为例,整个项目的异常,分为三层:

第一层:最基础的父类

BasicCheckedException (继承于Exception)

BasicRuntimeException (继承于RuntimeException)

(注意,整个项目,不要直接new新建 Exception 和 RuntimeException )

第二层:

NestedCheckedException (继承于BasicCheckedException)

NestedRuntimeException (继承于BasicRuntimeException)

第三层: 在上面4个异常类的基础上,扩展的异常类型。

目前有:

StcOrigException (原始异常,用于代替 Exception)-继承于BasicCheckedException

StcOrigRuntimeException (原始异常,用于代替 RuntimeException)-继承于BasicRuntimeException

StcNestedException (嵌套包装异常,用于将原始异常包装起来)-继承于NestedCheckedException

StcNestedRuntimeException (嵌套包装异常,用于将原始异常包装起来)-继承于NestedRuntimeException

StcI18nException (I18N国际化异常,包含errorCode和errorMsg,可对应中文、英文、繁体等异常信息)-继承于BasicCheckedException

StcNoLogException (打断但是不记录日志的异常,仅用于打断程序执行,但是外面不再记录异常信息)-继承于BasicCheckedException

2、最佳实践说明

说得简单点,其实项目中,用得最多的异常为 StcNestedException、StcI18nException、BasicCheckedException,前两者用于new创建异常,后者BasicCheckedException用于声明throws异常。举例如下:

 1 public class UserDAO {
 2
 3     pubic User queryById(Long id) throws BasicCheckedException {
 4
 5         if(id<0) {
 6
 7             throw new StcNestedException("ID小于0的用户不存在!");
 8
 9         }
10
11         Object uobj = null;
12
13         try {
14
15             uobj = getDao.query("select from User");
16
17         } catch (Exception e) {
18
19             throw new StcNestedException(e);
20
21         }
22
23         return UserTools.toUser(uobj);
24
25     }
26
27 }

这样写有几个好处:

1)对于new StcNestedException("ID小于0的用户不存在!"),我们不需要记录异常的堆栈和行号,只需要把错误信息往外抛出即可。此处也可以换成用I18n国际化errorCode代码标识的异常: new StcI18nException(ErrorCode.USR023, id)。

2)对于getDao.query("select from User")可能抛出的SQLException等异常,我们套用了StcNestedException,它可以原封不动的把原始异常的完整信息(比如message、行号和堆栈)保存下来。

四、异常和日志处理的关系

异常处理 和 日志处理 有密切关系,但是不能混为一谈。可以这样说:

1)出现异常 不一定要 记录日志。

2)记录日志 也不一定 是出现异常的时候。

就异常和日志处理的 常见关联点,举例做一个说明:

1、例(一)

Dao层报错,需要通知上层,也需要记录日志,这个日志是在dao层记录,还是让上层(比如Service层)决定 是否记录?

分析:

其实这是个普遍问题,放到service层或者通用工具类中,也有这个选择:到底是自己记录日志并且把异常信息往上层抛,还是自己不记录日志,只把完整的异常信息传递给上层,让上层决定是否记录日志。

我赞成的是后者,通常情况,我们统一的在中间层都不记录日志信息,有异常直接往外层throw抛出,对开发者而言非常方便(这才是重点)。

2、例(二)

因为某种业务需要,我们要在某个Prosessor类中记录日志信息,同时也要终止程序执行。

我们的需求:1)记录错误信息;2)打断程序运行。

传统做法:


1

2

3

4

5

6

try 

    doService(); 

catch(AbeException e) {

    log.error(e);

    throw e;

}

这种做法能满足上面的需求,但是又超过了我们的需求。因为它 throw e 虽然终止了程序的执行,但是它把错误信息和错误堆栈,都抛给了外层,而外层捕获到这个错误后,又可能会再次记录日志信息,这样就造成了日志信息多出重复,而且往往日志的堆栈信息非常长,看着很吃力。

以上问题就在于,我们只想“打断程序运行”,并不想把异常堆栈信息再继续往外传递。

这个时候,上面提到的“异常处理最佳实践”就提出了一种方案,一种只打断程序执行,不记录堆栈信息的异常——NoLogException。用NoLogException来改造上面的程序,就成了:


1

2

3

4

5

6

try 

    doService(); 

catch(AbeException e) {

    log.error(e);

    throw new NoLogException();

}

这样,外层可以判断是否为 NoLogException,如果是则外层不记录日志。退一步讲,即使外层不判断是否为NoLogException,它也可以记录日志信息,但是在NoLogException中没有任何异常的信息(只有一个错误代号),想记录也记录不到。

时间: 2024-08-06 03:43:59

异常处理最佳实践的相关文章

Java异常处理最佳实践及陷阱防范

前言 不管在我们的工作还是生活中,总会出现各种"错误",各种突发的"异常".无论我们做了多少准备,多少测试,这些异常总会在某个时间点出现,如果处理不当或是不及时,往往还会导致其他新的问题出现.所以我们要时刻注意这些陷阱以及需要一套"最佳实践"来建立起一个完善的异常处理机制. 正文 异常分类 首先,这里我画了一个异常分类的结构图. 在JDK中,Throwable是所有异常的父类,其下分为"Error"和"Excepti

Android 异常处理最佳实践

一个好的app 异常处理机制 我认为应该至少包含以下几个功能: 1.能把错误信息上传到服务器  让开发者可以持续改进app 2.错误信息至少应该包含 是否在主进程 是否在主线程 等可以帮助程序员定位的信息 3.最好包含手机硬件及软件信息. 4.主进程引发的异常 最好交由系统自己处理 也就是让用户可以感知到 那种(当然你也可以自己定义一套更有意思的感知系统对话框等,具体可参考各种有意思的404界面) 5.子进程引发的异常最好别让用户感知到.比如push之类的 这种 和用户感知弱关联的这种.最好发生

编程中关于异常处理的10个最佳实践

在实践中,异常处理不单单是知道语法这么简单.编写健壮的代码是更像是一门艺术,在本文中,将讨论java异常处理最佳实践.这些Java最佳实践遵循标准的JDK库,和几个处理错误和异常的开源代码.这还是一个提供java程序员编写健壮代码的便利手册. Java 编程中异常处理的最佳实践 这里是我通过在国内著名的IT培训平台扣丁学堂在线学习收集的10个java编程中进行异常处理的10最佳实践.在Java编程中对于检查异常有褒有贬,强制处理异常是一门语言的功能.在本文中,我们将尽量减少使用检查型异常,同时学

关于JAVA异常处理的20个最佳实践

在我们深入了解异常处理最佳实践的深层概念之前,让我们从一个最重要的概念开始,那就是理解在JAVA中有三种一般类型的可抛类: 检查性异常(checked exceptions).非检查性异常(unchecked Exceptions) 和 错误(errors). 异常类型 检查性异常(checked exceptions) 是必须在在方法的throws子句中声明的异常.它们扩展了异常,旨在成为一种"在你面前"的异常类型.JAVA希望你能够处理它们,因为它们以某种方式依赖于程序之外的外部因

[每日一题]说说异常处理机制和最佳实践

这个问题仁者见仁智者见智,每个人心中的最佳实践不见得一致,但是你要有想法,这个很关键,如果连思考都没有思考过,那就不太好了. 很多高级语言都提供了异常处理,比如Java.Python.Ruby,比较底层的语言,比如C,没有提供异常机制,最近时兴的Golang,也没有提供通常的try-catch异常机制. 异常机制是必须的么?显然不是,因为我们通常可以用多个返回值来解决,如果语言本身不支持多返回值,那异常机制就是必须的,否则这个语言写起来真的会很痛苦,你想想是不是这样?:) 异常,故名思议,就是不

【转】Java中关于异常处理的十个最佳实践

原文地址:http://www.searchsoa.com.cn/showcontent_71960.htm 导读:异常处理是书写强健Java应用的一个重要部分,Java许你创建新的异常,并通过使用 throw 和 throws关键字抛出它们. 异常处理是书写强健Java应用的一个重要部分,它是关乎每个应用的一个非功能性需求,是为了优雅的处理任何错误状况,比如资源不可访问,非法输入,空输入等等.Java提供了几个异常处理特性,以try,catch和 finally 关键字的形式内建于语言自身之中

Java 编程中关于异常处理的 10 个最佳实践

异常处理是书写 强健 Java应用的一个重要部分.它是关乎每个应用的一个非功能性需求,是为了优雅的处理任何错误状况,比如资源不可访问,非法输入,空输入等等.Java提供了几个异常处理特性,以try,catch和finally 关键字的形式内建于语言自身之中.Java编程语言也允许你创建新的异常,并通过使用  throw 和 throws关键字抛出它们.事实上,异常处理不仅仅是知道语法.书写一个强健的代码更多的是一门艺术而不仅仅是一门科学,这里我们将讨论一些关于异常处理的Java最佳实践.这些 J

【译】异常处理的最佳实践

译注:这是一篇2003年的文章,因为时间久远,可能有些观点已经过时,但里面讨论的大部分方法如今仍能适用. Best Practices for Exception Handling 异常处理的重要一点就在于知道何时处理异常以及如何使用异常.在这篇文章里,我会提到一些异常处理的最佳实践,我也会总结checked exception的用法. 我们程序员都想写出高质量的代码来解决问题.不幸的是,异常会给我们的代码带来副作用.没有人喜欢副作用,所以我们很快找到了方法来改善它们.我看见过许多聪明的程序员这

Java异常处理之最佳实践

引言: publicvoidconsumeAndForgetAllExceptions(){     try{         ...some code that throws exceptions     } catch(Exception ex){         ex.printStacktrace();     } } 采用上面这种方式处理异常时,catch代码段接管控制权,然后catch段之后代码继续执行,好像什么都没有发生.   publicvoidsomeMethod() thro