JAVA 异常处理的认知学习过程
没有异常处理
学生时代,我编写的java代码中,很少会有try catch.最主要的原因如下:
- 应用的规模很小
- 没有不确定因素
- 代码可控性高
如果规模小,往往就没有复杂的逻辑链路,整个软件的分层也很浅.很多地方的问题都是"编码"的问题.其次,学生时代的作品中,往往没有复杂的组件:数据库连接本地,没有rpc调用,没有微服务化的需求,而这些,往往容易带来网络、服务端的不确定因素.最后,整个工程往往由一个人编写,所以对哪些整个处理流程的全链路都可以掌控到,明白调用的模块到底有没有抛出异常.
很容易理解,这些特性决定了,即使完全不使用java的异常机制,整个应用也能跑起来.
这就是一个很简单的无异常处理的示例代码:
public boolean saveStudentInfo(File file) {
// 读取本地一个csv文件
List<StudentInfo> infoList = readFile(file);
// 通过网络发送到某个服务器
sendSocket(infoList);
}
全量try catch
到了工作的时候,第一堂课就是一定要捕获异常.如果spring MVC的controller不捕获异常,tomcat就会直接把抛出来的异常打印到页面上.对于一些面向用户的系统,会造成很大的影响.所以那个时候,从dao层,service层,到controller层,恨不得整个调用链路上的所有方法都通过捕获
Exception
来捕获一切异常,代码写成了这样:public boolean saveStudentInfo(File file) {
try {
List<StudentInfo> infoList = readFile(file);
sendInfo(infoList);
return true;
} catch (Exception e) {
logger.error("保存学生信息服务调用失败", e);
return false;
}
}
如果这么做的话,有以下几点问题:
- 无法针对不同异常做不同的处理
- 吞并了异常信息
- 无效化某些异常应该的走中断流程
整块代码块分别try catch
为了解决上述的第一个问题,我们会在代码块内部根据不同的异常情况,抛出不同的异常.然后在catch代码块中分别处理.
public boolean saveStudentInfo(File file) {
try {
List<StudentInfo> infoList = readFile(file);
sendInfo(infoList);
return true;
} catch (FileNotFoundException e1) {
logger.error("保存学生信息失败:文件没有找到", e1);
} catch (SocketTimeoutException e2) {
logger.error("保存学生信息失败:网络超时", e2);
} catch (Exception e3) {
logger.error("保存学生信息失败:未知异常", e3);
}
return false;
}
通过上述的做法,我们就可以区分不同的异常了.定位问题也能更准确.
分块 try catch
但上述方案依然有个明显的弊端:如果代码块中有多个地方可能抛出同一种异常,在catch到异常后也无法真正做到区分.为了解决这个问题,我们不仅要从catch的角度进行拆分,还要从try的角度进行拆分.
public boolean saveStudentInfo(File file) {
try {
List<StudentInfo> infoList = readFile(file);
} catch (FileNotFoundException e) {
logger.error("没有找到文件:{}", file);
return false;
} try {
sendInfo(infoList);
} catch (SocketTimeoutException e) {
logger.error("服务器连接超时");
return false;
}
}
非常有意思的一点是,为什么在两次调用中分别只catch了
FileNotFoundException
和SocketTimeoutException
?为什么不在最后catch住Exception
?其实是我想说明这样一个问题:异常catch精细化.所谓精细化,其实是对代码质量进行控制的一个结果.换句话说:为了提高代码质量,你应该尽可能弄清楚每块代码可能抛出什么异常,并进行针对的处理.上面这个例子中.如果你很清楚
List<StudentInfo> infoList = readFile(file);
这句调用只可能产生FileNotFoundException
这个异常.那么你就该精细到这个异常本身.专门去catch这个异常.并作出对应的处理.当然如果这里有第二种异常可能被抛出,你也应当专门去catch.当然这里也有个逻辑分层的概念,一般的业务代码遵循上述原则.但在调用链路上的某个重要环节,比如Controller,是有可能需要捕获全量异常的.
不catch异常
到现在,我们依然面临这些问题:
- 无法真正做到catch全部异常
- catch异常导致信息被吞没
- 中断流程无效化
第一点,如果调用别人的代码,就不知道运行时到底会抛出什么异常,或者RPC调用,可能混入中间件本身的异常.等等这些情况导致无法真正做到把所有异常都分类进行catch.所以,除非catch住
Exception
这个异常,否则必然会有一些漏网之鱼没有被catch到.第二点,有的异常比如:InterruptedException
,如果你catch住了,但是什么也不做(打印日志在某些情况下也约等于什么也不做),是有一定问题的.第三点,有的异常被设计出来就是要中断当前业务逻辑的.如果你catch住了,但是没有正确中断当前流程,会导致更严重的问题.要真正解决上述的问题,需要明白:异常处理本质上想达到的目的不是消除所有的异常本身,而是有效地向上传递错误信息和正确地中断当前处理流程.在前面讲的通过catch
Exception
来处理异常,实际上就是犯了想要消除异常本身的错误.也就是说,考量一个异常到底有没有被正确处理,指导思想是:
- 关于异常的信息有没有被正确传递
- 当前的处理流程有没有被正确中断
到这里,不catch异常,反倒成了某些情形下处理异常的最佳解决方案.
到底catch不catch?
那么,究竟是什么因素决定了是应该catch住异常,还是继续抛出异常呢?如果用最简单的语言描述,答案就是:这个异常当前能不能处理,如果不能,就继续往上抛.于是问题就变成了对处理的定义.一个经常出现的疑惑是:catch一个异常打印一行日志,到底算不算处理?
这个问题要从我们上文中的指导思想中找答案.首先看第二个维度,如果需要中断流程,而只是打印了日志,很明显就不是正确的处理.这个相对比较容易理解.接着再看第一个维度.关于这个信息.可能有人会认为:日志已经打印出去了,按理说信息已经成功传递了啊?
从宏观上,把错误日志打印出去≠100%正确传递信息.比如说某个很严重的问题发生了,虽然及时打印了日志,但是没有及时通知到人身上,而是导致服务挂掉,用户不能访问应用,反馈过来,才发现了日志文件中大量的异常.这就是:虽然信息传递出去了,但是却没有正确传递.所谓正确传递,应该是按照不同重要性,以不同方式,正确及时地通知到正确的处理方.比如用户输入个人信息,名字中带了特殊符号,前端直接提醒用户.这里的重要性就是低,方式就是前端反馈,处理方是用户.
这里看上去和java的try catch无关.但是实质上,却有相通之处.
对应到我们之前说的打印一行日志是否是正确传递信息的问题.如果你确定,应用对于这个异常的处理,只需要打印一样日志就ok了.既没有逻辑回滚,保证操作原子性的需要,也没有必须马上通知处理方的需要,就可以认为这里的处理是合适的.
结束了吗?
最后最有意思的一点来了.上述的东西都不复杂,为什么在实际应用中却难以真正做好呢?
我总结了一个有意思的原因:即使不通过异常,java代码也可以实现异常信息的传递和流程的中断,正是因为有两种方式并行存在,反倒让人疑惑到底应该使用哪一种.
A:
if (condition == -1) {
throw new Exception("异常信息");
} B:
try {
// do something
} catch (Exception e) {
logger.error("异常信息");
return false;
} // do something
上面的代码,分别就是这两种方式.都可以做到中断流程和信息传递.由于第二种的存在,很多时候反倒让人不愿意使用第一种,它看上去更复杂,更危险.而其实在正确考量之后,应该放心大胆得抛出异常.
最新文章
- 表单中Readonly和Disabled的区别
- 总结常见的ES6新语法特性
- iOS应用第三方推送的添加
- maven test 运行 指定类或方法 打包 mvn clean assembly:assembly
- API 菜单函数
- WebView增加一个水平Progress,位置、长相随意
- DirectoryEntry配置IIS出现ADSI Error:未知错误(0x80005000)
- BitmapSource ConvertTo Bitmap
- 对 Linux 专家非常有用的 20 个命令
- iOS8中添加的extensions总结(四)——Action扩展
- Linux sar使用
- XML解析器(TinyXML)的使用指南
- MyEclipse2014拷贝web工程
- Django用自定义cookies 实现登录,注册,退出
- Eclipse中debug调试java代码一直报Source not found的解决办法
- MongoDB--MapReduce分布统计s
- java.lnag.Throwable详细解读
- Metaphor of quotient space
- CodeForces 623E Transforming Sequence 动态规划 倍增 多项式 FFT 组合数学
- 记一次JAVAWEB项目部署
热门文章
- Centos在虚拟机VMware12上的安装
- ExtJS动态切换主题
- Java学习笔记七:Java的流程控制语句之switch
- 学习RUNOOB.COM进度一
- R语言学习笔记(九):fivenum()与quantile()
- [Cracking the Coding Interview] 4.6 Successor 后继节点
- BZ 600题祭
- elasticsearch 拼音+ik分词,spring data elasticsearch 拼音分词
- 1、Java多线程基础:进程和线程之由来
- mysql 导入CSV数据 [转]