最近在项目代码中,遇见异常滥用的情形,分析下会带来哪些后果。

1. 代码可读性变差,业务逻辑难以理解

  异常流与业务状态流混在一起,无法从接口协议层面理解业务代码,只能深入到方法(Method)内部才能准确理解返回值的行为

  可看一下代码:

public UserProfile findByID(long user_id) {
Map<String, Object> cond = new HashMap<String, Object>();
cond.put("id", user_id);
UserProfile userInfo = null;
try {
userInfo = DBUtil.selecta(UserProfile.class, "user_info", cond);
} catch (Throwable e) {
log.error(e, "UserProfile findByID");
}
return userInfo;
}

  DAO层负责数据库的基本操作,该方法返回值为查询结果用户对象数据。代码强行抓了所有的异常,并以null返回,后来人无法确认null是代表该用户不存在还是出现异常。

2. 代码健壮性变差,异常信息被随意捕捉,甚至被吃掉

  同样上述代码,首先抓了Throwable这个所有异常,包括Error(后文会介绍异常体系)。代码内部隐藏了问题,只是打印了一行日志,并且让程序可以正常继续往后走,带来的不确定性和风险都很大,这也极大的影响代码的健壮。

3. 破坏架构的分层清晰,职责单一的原则,为系统扩展带来很大阻碍

  随着系统的发展,往往会沉淀出一些平台系统,比如调用监控。会负责统一采集系统的各类信息,因为这样的错误异常处理,将很难统一分离出异常信息。

那我们在实际编写代码的如何正确考虑异常的使用呢?

  首先了解下Java异常的设计初衷。

  Exceptions are the customary way in Java to indicate to a calling method that an abnormal condition has occurred. 一个很重要的概念——不正常情形。Java异常旨在处理方法调用时不正常情形,但我们该如何理解“不正常情形”?

  看下图,JDK给我们定义了以下异常体系:

  根节点是Throwable,代表Java内所有可以Catch的异常都继承此,向下有两类,Exception和Error,日常用到较多的都是Exception,Error一般留给JDK内部自己使用,比如内存溢出OutOfMemoryError,这类严重的问题,应用进程什么都做不了,只能终止。用户抓住此类Error,一般无法处理,尽快终止往往是最安全的方式,既然什么都干不了就没必要抓住了。Exception是应用代码要重点关心的,其下又分为运行时异常RuntimeException和编译时异常,各自区分就不在详述了。

  了解异常的结构后,接下来解决两个重要问题,何时抛异常和抛什么异常,何时抓异常和抓什么异常 何时会有异常抛出,总结起来有以下三个典型的场景:

  1. 调用方(Client)破坏了协议

    说白了就是调用方法时没有按照约定好的规范来传参数,典型的比如参数是个非空集合却传入了空值。这种破坏协议的还可以细分两类,一类是调用方从接口形式上不易觉察的规则但需要在出现时给调用方些强提示,带些信息上去,这时异常就是特别好的方式;另一类是调用方可以明确看到的规则,正常情况会正常处理协议,不会产生破坏,但可能因为bug导致破坏协议。

 public void method(String[] args) {
int temperature = 0;
if (args.length > 0) {
try {
temperature = Integer.parseInt(args[0]);
}
catch(NumberFormatException e) {
throw new IllegalArgumentException(
"Must enter integer as first argument, args[0]="+args[0],e);
}
}
// 其他代码
}

要求传入整数,但转换数字时出错,此时可抛出特定异常并附上提示信息

  1. (Method)知道有问题,但自己处理不了

    这里"有问题",可能是调用方破坏了协议,但是单独提出来是要从被调用方出发考虑,比如Method内部有读取文件操作,但发现文件并不存在

 public static void main(String[] args) {
if (args.length == 0) {
System.out.println("Must give filename as first arg.");
return;
}
FileInputStream in = null;
try {
in = new FileInputStream(args[0]);
}
catch (FileNotFoundException e) {
System.out.println("Can't find file: " + args[0]);
return;
}
// 其他代码
}
FileInputStream在创建时抛出了FileNotFoundException,显然出现该问题时FileInputStream是处理不了的。
  1. 预料不到的情形

    空指针异常、数组越界是这种典型的场景,一般是由于有代码分支被忽略

    了解了何时会出现异常,但是需要抛出异常时是选择编译时异常还是运行时异常呢? 很多人可能会说,很简单啊,需要调用方catch的就编译时,否则运行时。问题来了,什么时候需要调用方catch?

    分析编译时和运行时对代码编写的影响,可以总结出来区分时考虑的点有:调用方能否处理、严重程度、出现的可能性。

    调用方能处理->编译时

    调用方不能处理->运行时

    严重程度高->运行时

    出现可能性低->运行时

    本人细化了这个分类的考虑过程如下: 

    首先从调用方开始考虑,如果是调用方破坏了协议,则抛出运行时异常,这类异常一般出现可能性较低,调用方已知,所以没必要强制调用方抓此异常。

    然后如果问题出现被调用方,无法正常执行完成工作,这时候考虑该问题调用方是否可以处理,如果能处理,比如文件找不到、网络超时,则抛出编译时异常,否则比如磁盘满,抛运行时异常

    解决了何时抛异常和抛什么异常,接下来是调用这些有异常的代码时,何时catch和catch什么异常呢? 攻守不分离... 免不了俗,总结一下几点供大家探讨:

  2. 不要轻易抓Throwable,图省事可能会带来巨大的隐患

 try {
someMethod();
} catch (Throwable e) {
log.error("method has failed", e);
}
 
  应该尽量只去抓关注的异常,明确catch的都是什么具体的异常
  1. 自己处理不了,不要抓

    比如上文DB可能会有异常,在DAO层是处理不了这种问题的,交由上层处理。抓异常宜晚不宜早,抛异常宜早不宜迟。

  2. 切忌抓了,又把异常吞掉,不留下一丝痕迹

    抓住异常,打行日志完事儿,不是一个好习惯。

  3. 切忌抓异常了将异常状态流和业务状态流混在一起,这样你算是彻底抛弃了Java的异常机制

Java异常的正确使用姿势的更多相关文章

  1. 阿里巴巴Java开发手册正确学习姿势是怎样的?刷新代码规范认知

    很多人都知道,阿里巴巴在2017发布了<阿里巴巴Java开发手册>,前后推出了很多个版本,并在后续推出了与之配套的IDEA插件和书籍. 相信很多Java开发都或多或少看过这份手册,这份手册 ...

  2. Java日志正确使用姿势

    前言 关于日志,在大家的印象中都是比较简单的,只须引入了相关依赖包,剩下的事情就是在项目中“尽情”的打印我们需要的信息了.但是往往越简单的东西越容易让我们忽视,从而导致一些不该有的bug发生,作为一名 ...

  3. 玩转java多线程(wait和notifyAll的正确使用姿势)

    转载请标明博客的地址 本人博客和github账号,如果对你有帮助请在本人github项目AioSocket上点个star,激励作者对社区贡献 个人博客:https://www.cnblogs.com/ ...

  4. 基于winserver的Apollo配置中心分布式&集群部署实践(正确部署姿势)

    基于winserver的Apollo配置中心分布式&集群部署实践(正确部署姿势)   前言 前几天对Apollo配置中心的demo进行一个部署试用,现公司已决定使用,这两天进行分布式部署的时候 ...

  5. 浅谈java异常[Exception]

    学习Java的同学注意了!!! 学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:589809992 我们一起学Java! 一. 异常的定义 在<java编程思想 ...

  6. 10个关于Java异常的常见问题

    这篇文章总结了十个经常被问到的JAVA异常问题: 1.检查型异常VS非检查型异常 简单的说,检查型异常是指需要在方法中自己捕获异常处理或者声明抛出异常由调用者去捕获处理: 非检查型异常指那些不能解决的 ...

  7. Java 异常讲解(转)

    六种异常处理的陋习 你觉得自己是一个Java专家吗?是否肯定自己已经全面掌握了Java的异常处理机制?在下面这段代码中,你能够迅速找出异常处理的六个问题吗?   1 OutputStreamWrite ...

  8. Java异常机制

    Java异常分类 异常表明程序运行发生了意外,导致正常流程发生错误,例如数学上的除0,打开一个文件但此文件实际不存在,用户输入非法的参数等.在C语言中我们处理这类事件一般是将其与代码正常的流程放在一起 ...

  9. 一篇不错的讲解Java异常的文章(转载)

    http://www.blogjava.net/freeman1984/archive/2007/09/27/148850.html 六种异常处理的陋习 你觉得自己是一个Java专家吗?是否肯定自己已 ...

随机推荐

  1. NOIP2017 小凯的疑惑

    题目描述 小凯手中有两种面值的金币,两种面值均为正整数且彼此互素.每种金币小凯都有 无数个.在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付的.现在小 凯想知道在无法准确支付的物品中,最贵的 ...

  2. 【技术分析】DowginCw病毒家族解析

    作者:钱盾反诈实验室   0x1.背景 近期,钱盾反诈实验室通过钱盾恶意代码智能监测引擎感知并捕获一批恶意应用.由于该批病毒会联网加载"CWAPI"插件,故将其命名为"D ...

  3. Material04 MdCardModule和MdButtonModule综合运用

    设计需求:设计一个登陆页面 1 模块导入 1.1 将MdCardModule和MdButtonModule模块导入到共享模块中 import { NgModule } from '@angular/c ...

  4. Oracle学习笔记(7)——高级查询(1)

    在学习高级查询之前,我们先了解一下怎样查看Oracle数据库中的全部表.由于我们要使用到Oracle数据库中SCOTT用户下的几张表(这些表是Oracle数据库自带的表). 分组查询 分组函数的概念: ...

  5. OracleOraDb11g_home1TNSListener服务启动后停止,某些服务在未由其他服务或程序使用时将自己主动停止

    解决的方法,大家来分享一下 1:注冊表中 HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/services/OracleOraDb11g_home1TNSLis ...

  6. 在dotnet core web api中支持CORS(跨域访问)

    最近在写的Office add-in开发系列中,其中有一个比较共性的问题就是在add-in的客户端脚本中访问远程服务时,要特别注意跨域访问的问题. 关于CORS的一些基本知识,请参考维基百科的说明:h ...

  7. QQ 相册后台存储架构重构与跨 IDC 容灾实践

    欢迎大家前往云加社区,获取更多腾讯海量技术实践干货哦~ 作者简介:xianmau,2015 年加入腾讯 TEG 架构平台部,一直负责 QQ 相册平台的维护和建设,主导相册上传架构重构和容灾优化等工作. ...

  8. 自定义控件详解(七):drawText()

    比较基础的一个方法.即绘制文本 使用如下: Paint paint = new Paint(); paint.setColor(Color.RED); // 红色字体 paint.setStyle(P ...

  9. BSGS离散对数(Baby-Step-Giant-Step)

    BSGS离散对数(Baby-Step-Giant-Step) 题目: 给定\(x,y,p,\)求最小的自然数\(k\)满足\(x^k=y(\mod p)\)\(p\le2^{31}\)(满足一定有答案 ...

  10. 高性能管线式HTTP请求(实践·原理·实现)

      该篇实际是介绍pipe管线的原理,下面主要通过其高性能的测试实践,解析背后数据流量及原理.最后附带一个简单的实现     实践 先直接看对比测试方法 对于单一客户端对服务器进行http请求,一般我 ...