文章咕咕咕了好久,有好多东西想写,以前想写的也来不及写
最近看了一下阿里的 Java 开发手册,又结合了一下自己的开发实践,归纳总结一下,过于基本的就省略了:
(一)通用最佳实践
-
【强制】Java 类库中可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 方式来处理,比如:NullPointException,IndexOutOfBoundsException 等等。
说明:无法通过预检查的异常除外,比如解析字符串形式的数字时,不得不通过 catch NumberFormatException 来实现。
-
【强制】捕获异常后必须进行处理,且只有两种操作可能:继续 throw,记录日志并返回错误状态;异常捕获后不要用来做流程控制、条件控制。
-
【强制】finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch. 如果是 JDK7 以上,可以使用 try-with-resources 的方式。
-
【强制】不要在 finally 块中 return.
说明:try 块中的 return 语句执行成功后,并不会马上返回,而是继续执行 finally 块中的语句,如果此处存在 return 语句,则在此直接返回,丢弃掉 try 块中的返回点。
-
【推荐】try-catch 的范围应尽可能小,catch 所期望的异常应尽可能具体。
(二)不同层级的异常处理逻辑
- 【强制】应用内部使用异常抛出处理异常流,被调用者将异常抛出给调用者。最外层的业务使用者必须处理异常,并将其转化为用户可以理解的内容,参照统一返回部分的规范处理。跨应用间的 RPC 调用使用 CommonResponse 的形式,同样参照统一返回部分的约定。
- 【强制】一个 Spring MVC 的 Web 应用中,Controller 不应再继续抛出异常而去让 ExceptionHandler 处理,直接进行 error 返回加上 log 是更容易理解的方式。
- 【推荐】分层异常处理参考:
- 在 DAO 层,产生的异常类型有很多,无法用细粒度的异常进行 catch,使用 catch(Exception e) 方式,并 throw new DAOException(e),不需要打印日志,因为日志在 Manager/Service 层(Manager 层是相对业务化的不同微服务而抽取出来的通用服务)一定需要捕获并打印到日志文件中去,如果同台服务器再打日志,浪费性能和存储。
- 在 Service 层出现异常时(在这里 web 往往是与 service 独立部署的),必须记录出错日志到磁盘,尽可能带上参数信息,相当于保护案发现场。
- Manager 层与 Service 同机部署,日志方式与 DAO 层处理一致,如果是单独部署,则采用与 Service 一致的处理方式。
- Web 层绝不应该继续往上抛异常,因为已经处于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面,尽量加上友好的错误提示信息。(此处的 web 特指用服务端界面渲染技术的 web,不是 REST 接口的 web)
- 开放接口层要将异常处理成错误码和错误信息方式返回。
(三)自定义异常
-
【强制】捕获异常与抛异常,必须是匹配的,或者捕获异常是抛异常的父类。
正例:上层调用 catch ServiceException,service 中的某一个方法抛出的异常是继承自 ServiceException 的自定义异常。
-
【强制】主动 throw 的异常必须是自定义异常:
-
不允许直接抛出 RuntimeException,甚至是 Exception 或者 Throwable.
-
对于 try-catch 捕获的异常,如果要再次 throw 需要将原异常作为 cause 入参构造自定义异常,以免丢失堆栈信息。
-
推荐业界常用的定义方式,DAOException / ServiceException.
说明:为什么编码中 throw 的异常需要是自定义异常?1 是为了区分异常的抛出者,自定义异常出现说明该异常已经是我们预期的且在理论上是被正确处理的,而非自定义异常出现通常意味着程序逻辑存在缺陷。2 是异常被 throw 最终都会被处理,而这个处理的终点就是最初的调用方,调用方又需要用错误码加上错误信息的方式回复给它的系统之外的真正请求者,自定义异常可以让我们方便地定义 code 来生成有帮助的响应。
-