Java 异常使用

背景

写 Java 的人都知道, Java 异常是个很烦人的东西,因为正确的处理异常并不是一件简单的事情,一方面异常总是出人意料,另一方面,异常并不总是可见,且隐藏很深。

Java 异常的重要特性

Java 异常一个最重要的特性在于,异常会立即中断当前的处理流程,然后不断抛出,直到有地方处理为止。
另一个重要的特性在于,Java 有区分运行时期的异常,这个东西往往会带来一些意想不到的问题。

为什么要学会处理 Java 异常

所有的问题,都要从编程的终极目标说起,那个目标就是“功能”和“稳定性”。Java 异常会中断当前处理的流程,可能会导致功能无法正常使用,另一方面出现了意料之外的异常,往往也意味着系统不够稳定,异常也是我们发现问题的重要场所。本文是我处理异常的一点小经验。

NPE 最基础的异常

没有遇过 NPE 问题的 Java 工程师都是入门菜鸟。NPE 的问题在 Java 中总是无处不在。解决这个问题,没有什么好办法,只有事无巨细地将每个可能为 NULL 的值进行处理。

如何做判空校验

通常来说,我们要对所有的封装类型值做判空校验,所有 DO 类型,以及所有的集合类型都要做判空。

Long a = arg1;
if(a == null){
	return "arg1 can not be null";
}
BizDO do = arg2;
if(do == null){
	return "arg2 can not be null";
}

Set<Long> names = arg3;
if(CollectionsUtil.isEmpty(names)){
	return "arg3 can not be empty";
}

//现在 Java8 出来后,可以更简单的处理,不用检查,直接使用 Optional
Optional.ofNullable(names)
                .map(String::trim);

哪些地方需要 NPE 判断

理论上来说所有为空的地方都要做 NPE 判断。一般来说这些地方最有可能为 NPE

  • API 的入参,理论上要对所有的 API 入参做判断,除非你的业务需要对 NULL 做特殊处理,否则所有的参数,理论上都不能为 NULL。
  • RPC 调用的返回值,由于 RPC 调用的依赖方,你不能保证他们的接口返回的是正确的,所以对他们的返回值,也一定要做 NPE 判断。
  • 所有的函数调用都可能返回空。 如果可以尽可能对所有的函数返回结果做 NULL 判断,及时调用的函数方法是你自己写的,且一定不会为 NULL ,你还是要做空判断,因为一个系统往往多个人开发,你不能保证你的方法不被人修改,很有可能有人改了你的底层方法,结果他的错误,在你外部调用的代码中出现了错误。

如何处理异常

在我的经验里,异常处理主要两个原则

  1. 可恢复的异常,一定要捕获并处理。
  2. 不可恢复的异常,控制异常造成的影响。
  3. 未知的异常,一定要保留上下文。
捕获所有可以恢复的异常

在一些资料里,会提到对于可以恢复的异常,不要使用运行时异常,运行时的异常通常指的是不可恢复的异常。但是很可惜,真实的情况却不如这般美好,一方面并不是所有的人都知道这个原则,另一方面尤其是在使用开源 jar 包的时候,开源 jar 里定义的运行时异常,对于你的系统来说是否真的是不可恢复的,其实并没有一个统一标准。所以唯一的办法,是
枚举所有已知的异常,并捕获可能影响数据的未知异常

例如,我们写一段插入数据库的代码

try {
	xxxDAO.insert(xxxDO);
}catch(DuplicateException e){
	//处理索引冲突的情况
}

在这里索引冲突的 Exception 其实是一个 RuntimeException, 对于你的系统来说,如果出现了索引冲突,你可能只需要换一个值重试就好了,但是框架依旧可能返回你一个运行时的异常,而这个异常在你第一次遇见它之前,你从 API 层面根本无法感知到它的存在。这样的例子告诉我们一件事情

不要迷信所谓稳定的开源工具,和所谓大厂的工具包,因为你不知道里头会隐藏什么未知的异常,即使包本身没有什么问题,但是依旧可能会和你系统的需求产生冲突,所以在使用工具 jar 的时候,最好的方案是读源码,了解完整的来龙去脉,捕获全部可能的异常。
将异常造成的影响降到最低

如果能熟读源码当然是最好的,但是很多时候,我们很难有时间都搞定,所以在不确定的情况下,我们要做的事情是,将不确定的影响放到最低。什么东西是不确定的呢。通常来说

  1. 工具包的返回方法。
  2. RPC 调用
  3. 缓存框架的调用
  4. 消息发送
  5. 提交线程池(如果你使用的是默认拒绝策略)
  6. DB 请求

这几个是不确定性最大的地方。一般来说什么是最低影响
对于 RPC 调用来说,如果 RPC 调用出现异常,那么当出现异常的时候,可以走降级方案,如果没有降级方案,那么也要处理异常,假设我们最终服务需要产出 3 个数据,其中一个数据依赖某个合作方的 RPC 接口,如果这个接口出现异常,我们至少要保证另外两个数据的产出不会收影响,不要产生连带的影响。

保留异常上下文

虽然我们想办法降低了未知异常的影响,但是异常总是出现,依旧是系统稳定性的绊脚石,所以我们也需要有个机制,能帮助我们逐步减少异常。通常来说意料之中的异常好处理,难处理的是未知的异常,所以在我们捕获异常的过程中,一定要保留完整的上下文,包括方法的入参,出参,执行的位置,当前中间变量的值,这样才有助于我们记录现场,并在之后发现更深层次的问题,然后逐步去解决。

try{
	result = xxxRPCinterface.invoke(arg1,arg2,arg3);
	return result.getModel();
}catch(Exception e){
	LOGGER.log("invoke xxxPRC service occur exception arg1 is {} arg2 is {} arg3 is {} result is {} , and e is ",arg1,arg2,arg3,result,e);
}

这样我们在日志里就能看到完整的调用上下文了,注意日志在配置的时候一定要带上时间戳。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值