这是我写的。这里的大部分内容 ,我仍然赞同。只有最后的异常类层次:我现在倾向于使用RuntimeException作为顶层类。

另外,异常还是要少用。异常太多也吃性能。能通过校验获取结果的话,就用校验。

WHY

异常是每个人都不愿意遇到,但是又都绕不开的东西。它标识着某种错误的、或者不正常的情况,需要业务人员换一种操作方式或者数据、需要运维人员修正系统环境、或者需要开发人员修改代码。

测试人员小A接到了一个待测版本。部署好服务器和数据库、点击"保存并提交文档”按钮之后,系统报错了:“请联系管理员处理!”
但是小A不知道错在什么地方。于是她去找开发人员小乙。
小乙放下手头的工作,花了一个小时,发现是数据库中少了一条错误信息。于是他把原因告诉了小A,并且把报错信息改为:“错误信息:系统参数未正常初始化!”
这样,后来的测试同事们遇到这个问题,都不用再找开发,而直接通过操作数据库解决了。于是测试happy了,开发也happy了,他们过上了幸福快乐的生活……

异常规范的作用,就在于明确指出异常原因、包含信息,以及处理方式。这样,可以避免为了一个简单的异常投入过多的测试、开发或运维人力。  

HOW

禁止“吃掉”异常

绝对禁止在catch到异常后,留下一个空的catch块(或者仅仅使用e.printStackTrace()来打印异常信息)。

如果无法“恢复现场”,则至少应当在catch块中,记录下异常的上下文日志(使用LOGGER.error(...)级别)。

如果可以“恢复现场”,则应当尽量保持流程可继续执行。

如下示例。parseTime方法首先按照TIME_FORMAT格式("yyyy-MM-dd HH:mm:ss")格式来解析时间;如果解析过程中发生异常,再使用DATE_FORMAT格式("yyyy-MM-dd")格式来解析。如果仍然解析失败,则记录日志,并返回nul值。

private static Date parseTime(String date) {
    Date time = null;
    try {
        time = DateUtils.parseDate(date,OasisLendRequestBuilder.TIME_FORMAT);
   } catch (ParseException e) {
       try {
           time = DateUtils.parseDate(date,OasisLendRequestBuilder.DATE_FORMAT);      
       } catch (ParseException e1) {
           OasisLendRequestBuilder.logger.error("无法解析日期:{}", date, e);
           time = null;
       }
    }
    return time;
}

异常日志应记录异常堆栈信息

堆栈信息的重要性是不言而喻的。

但是,如果用e.printStackTrace()来打印异常堆栈,这些信息不会被记录到日志中(如thread.log或thread_error.log)。

如果使用LOGGER.error(e.getMessage()),又无法记录到异常堆栈。

为了在日志中记录下异常堆栈信息,我们应当保证在LOGGER.error(...)方法的参数列表中,异常实例是最后一个参数。这样,LOGGER会自动把异常堆栈信息输出到日志中。

将异常堆栈信息打印到日志中

例如,有如下代码:

try {
    lendRequest = this.builder
        .buildAppLendRequestFromOasis(oaLendRequest);
} catch (Exception e) {
    OasisAuditResultProcessor.logger.error("opId={}",
        operation.getOperation_id(), e);    
    throw e;
}

上述代码得到的日志

2015-10-13 16:51:34,115 [ERROR] OasisAuditResultProcessor#processFinalAppLendRequest()@218  - opId=ff808081505f1f6701505f4ef83805b3
java.lang.NullPointerException
at cn.youcredit.thread.task.oasis.OasisLendRequestBuilder.buildAppLendRequestFromOasis(OasisLendRequestBuilder.java:1032)
at cn.youcredit.thread.task.oasis.OasisAuditResultProcessor.processFinalAppLendRequest(OasisAuditResultProcessor.java:216)
at cn.youcredit.thread.task.oasis.OasisAuditResultProcessor$$FastClassBySpringCGLIB$$9e319624.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)

父类throws声明中,尽量声明异常超类

相信THREAD组各位都见过这个问题。

类A的子类A1在重写方法service()时,声明了throws 某个异常。但是类A的方法service()内,没有任何地方抛出了这个异常。

结果,无论把InvalidDataException声明在什么地方,IDE(如eclipse)都会报错。

这种情况下,应当在父类中声明throws 异常的超类;子类中声明具体的异常类。

如下示例。handlePayProcess方法继承自父类中的方法。但子类方法多抛出了一个异常“JiBXException”。这个异常无论放在父类声明或子类声明中,eclipse都会提示出错。这时,将父类的throws声明改为 throws Exception,即可解决问题。  

publc class Father{
    public void handlePayProcess()throws IOException{
        // 略
    }
}

public class Son extends Father{
    /** 这里多出来的JiBException会导致编译报错。 */
    @Override
    public void handlePayProcess()throws IOException, JiBException{
        // 略
    }
}