使用异常还是错误码?

异常使用和处理建议

使用异常代替错误码
这曾经是个社区争论不休的话题,多数人是选择异常替代错误码的。反对者中,有人认为不应该使用异常去控制程序流程,也有些人认为只有"数据库连不上"这种系统异常才算异常,还有些人认为异常会有额外的性能开销…
使用异常代替错误码,可以使得代码更清晰,可读性更好,更符合面向对象。况且有些方法压根就是void的无返回值的。至于性能开销这个通常可以忽略。
当然,这个建议并不是在所有地方都适合,在业务层中比较合适

不要通过异常捕获来代替基本判断
比如,最常见的NPE的防范,判断非空这种操作是必须的,不能试图在外面套个try catch(NullPoint…)来替代基本判断。
也可以使用Java8中的Optional避免NPE

使用自定义异常

  • 使用自定义的异常并包含业务含义,而不要直接抛出new RuntimeException()之类的

引发异常
何时主动引发异常这是一个让很多人疑惑的问题。那么我举几个简单的例子:

  • 在更新用户信息的业务方法中发现该用户根本就不存在,在一个注册的方法中发现用户已经存在了…那么建议直接向上层抛出异常
  • 对于方法参数做校验发现不符合,是直接引发异常还是直接return(或者return默认值)? 这个要看实际场景要求,不能一概而论,具体:
    1.业务上不想因为这里的个别参数问题影响到外层的主流程,可以在该方法内记录下警告日志然后直接return,外层直接忽略继续执行主流程
    2.如果对业务主流程是有直接影响的,希望内部方法提前终止并通知到外层,那么建议直接抛出自定义异常,风格统一(使用自定义异常代替错误码)。当然,这种情况返回错误码或者返回个默认值(NULL)也不能说就是错的,只是可读性并不是特别好,容易理解出错。还有中特殊情况,内部方法定义为void类型,即便return了外层其实也不感知的,那这种肯定是要引发异常的
    3.对于可控范围内的输入输出,使用返回或者抛异常都可以。对于不可控的,比如:恶意的输入,或者继续运行可能引发资源泄露等不可恢复的严重问题…都应该直接抛出异常及时终止程序运行

捕获异常

  • 不要不加区分的统一捕获Excetion甚至Throwable,应该区分异常类型,仅仅捕获处理你关注的异常
  • 千万不要捕获了异常但是什么都不做,或者printStackTrace,最起码也应该打印日志记录异常堆栈信息
  • 如果底层的异常上下文信息对上层来讲毫无意义,那么考虑捕获处理异常。例如:底层对用户输入的内容做类型转换时发生了异常,应该捕获『类型转换异常』并向上抛出『参数异常』
  • 如果需要包装额外的信息传递给上层,可以考虑捕获异常并引发新异常
  • catch代码块中的代码应该尽可能简单、稳定,避免出错
  • 不要不分青红皂白给整个方法或者大段代码加上try catch,一来这样本来就不对,而且可读性不好,也影响性能。这是我见过的很多新手『害怕程序出错』的惯性做法
  • 避免异常捕获嵌套,或者在循环内使用异常
  • 如果有使用事务,在发生异常时做事务回滚

finally中不要包含return
finally中的return返回后方法就结束执行了,try中的return就执行不到

finally中释放资源
在finally中关闭连接、关闭流等避免资源泄露,也可以使用try-with-resources

一些实践

  • 在restful API中,涉及到跨语言跨平台问题,尽量不要直接往外部抛出异常,建议使用错误码+错误消息替代,不要和http的状态码混淆使用。除了接口自身考虑异常处理外,应该要有全局异常处理器来做兜底
  • 在rpc接口中,虽然多数情况是相同语言的而且多数rpc框架也可以往外抛出并传递异常堆栈信息(前提是特定的异常在消费者、服务提供者都有定义,否则无法解析)。但是也不建议直接抛出异常,rpc接口响应结果推荐使用统一包装的结果类,返回自定义错误码和错误信息,同时也应该有全局的异常处理器来做兜底
  • 在最外层面向用户的系统中,应该捕获处理异常,并转换成友好的提示信息,不要把异常信息甚至堆栈直接丢给用户
  • 日志中要打印完整的异常堆栈信息,否则排查问题困难。请检查你使用的日志框架相关方法,到底是打印出来的异常完整信息还是只打印的"异常类的全限定名"。包括堆栈信息,包括上下文信息,便于排查
  • 在web层、api层都有全局的统一异常处理来兜底,在spring boot/mvc中可以考虑使用@ControllerAdvice、@ExceptionHandler来实现全局异常处理

猜你喜欢

转载自blog.csdn.net/dinglang_2009/article/details/93346333
今日推荐