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

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. Usaco 2006Nov Round Numbers

    题意:定义Round Number为二进制表示下0的个数大于等于1的个数的数.求[l,r]中有多少圆环数 我们把二进制位用一颗01二叉树表示,如下: 我们依据二进制位来遍历这颗线段树,如果当前高度对应 ...

  2. Solr6.5.0配置solrcore图文详解

    准备环境: solr6.5.0安装完成 jdk1.8 solrhome配置成功 详情:

  3. DDD峰会归来话DDD

    一场大戏落幕,首届DDD中国峰会如大会主题色一般的红.或许在12月9日这一天,全中国的DDD粉丝大约有一半都汇聚在了国家会议中心.听起来是幸,其实是不幸,因为DDD在中国的人群基数实在是太少了. 因为 ...

  4. C++ queue deque

    queue queue 队,一种先进先出的数据结构,c++ stl里也叫容器适配器,它是以deque 或list为基础的一种数据结构 queue的相关操作 queue<int deque< ...

  5. Excel大写和小写转换函数

    Excel中的大写和小写转换函数 (1).转换为所有小写字母:lower函数 (2).转换为所有大写字母:upper函数 (3).转换为首字母大写,其余小写字母:proper函数 三种函数的使用方式. ...

  6. WARNING: Re-reading the partition table failed with error 16: Device or resource busy.

    在 mkfs.ext4 /dev/sda2 格式化硬盘空间时,可能出现这种错误. had this situation at office where I was told to re-partiti ...

  7. Android开发中有用工具之--Log工具类

    在开发的过程中.我们常常会使用Log来输出日志,帮助我们来调试程序 可是有时候并不能全然满足我们的须要 ,比方我想知道这个日志信息是来自于哪一个包 哪一个类 所以我们封装一个这个Log类.方便我们的使 ...

  8. 《跟我学IDEA》四、配置模板(提高代码编写效率)

    上一篇博文,我们学习了idea的一些实用配置,相信大家也对idea这个开发工具有了一个大概的了解.今天我们来学习模板的配置,idea提供很多模板从而提高编写代码的效率,比如说一些经常用的代码及生成文件 ...

  9. div元素宽度不定的情况下如何居中显示

    最近由于工作的原因碰到一个问题,就是在一个弹窗宽度不定的情况下还能是该弹窗居中显示,思考许久未找到合适办法,于是在网上找到一些办法在此总结记录下来方便以后的学习. 方法一:兼容IE67 <div ...

  10. mysql还原数据库时,提示ERROR 1046 (3D000) No database selected 的解决方法

    使用mysql数据库的朋友, 经常会使用mysqldump备案数据库, 然后到新服务器还原, 这个过程中, 有朋友会遇到ERROR 1046 (3D000)  No database selected ...