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

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. SharePoint Online Add-in 开发简介

    作者:陈希章 发表于 2017年12月22日 在 再谈SharePoint大局观 中我提到了SharePoint开发的一些新的变化,这一篇文章我将讲解SharePoint Add-in开发.其实早在2 ...

  2. [Maven实战](7)坐标

    1. 简单介绍 maven的世界中拥有数量很巨大的构件,也就是平时用的一些jar,war等文件. 在maven为这些构件引入坐标概念之前,我们无法使用不论什么一种方式来唯一标识全部这些构件. 因此,当 ...

  3. Python 练习冊,每天一个小程序

    Python 练习冊,每天一个小程序 说明:     Github 原文地址: 点击打开链接 Python 练习冊.每天一个小程序.注:将 Python 换成其它语言,大多数题目也试用 不会出现诸如「 ...

  4. 输入url会发什什么

    从输入url到页面加载完成发生了什么 整体来说有几个基本的点: 1.浏览器的地址栏输入url并按下回车 2.浏览器查找当前url是否存在缓存,并比较缓存是否过期 3.DNS解析url对应的IP 4.根 ...

  5. Java数组的一些使用方法及堆栈存储

    数组 用于存储一组同一数据类型数据的容器 数组会对放入其中的数据自动编号,编号是从0开始的---下标 定义格式 数据类型[] 数组名 = new 数据类型[数组的大小];---可以先声明再初始化 in ...

  6. 自学Python4.2 迭代器、生成器

    迭代器.生成器一.迭代器 迭代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退,不过这也没什么, 因为人们很少在迭代途中往后退.另外 ...

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

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

  8. 前端MVC Vue2学习总结(二)——Vue的实例、生命周期与Vue脚手架(vue-cli)

    一.Vue的实例 1.1.创建一个 Vue 的实例 每个 Vue 应用都是通过 Vue 函数创建一个新的 Vue 实例开始的: var vm = new Vue({ // 选项 }) 虽然没有完全遵循 ...

  9. centos6.5安装git

    1.git源码地址:http://codemonkey.org.uk/projects/git-snapshots/git/

  10. [array] leetcode - 48. Rotate Image - Medium

    leetcode - 48. Rotate Image - Medium descrition You are given an n x n 2D matrix representing an ima ...