小课堂Week10 例外处理设计的逆袭Part3
2016-09-09 18:46
169 查看
小课堂Week10
例外处理设计的逆袭Part3
今天是《例外处理设计的逆袭》这本书阅读的第三天,也是最后一天,我们会主要通过实例,对Part2中提出的例外处理等级进行解读。Level1
Level1的要求是立即中止运行 ,所有例外都往外抛,全部报告给使用者,或者开发者使用。案例1
我们看一个实例,如下代码存在一些什么样的问题:public int withdraw(int amount) { if (amount > 100) return -1; else return 100 - amount; }
问题:使用返回码来表示异常,造成了返回信息的二义性,会导致上游调用复杂,同时也不能清晰表达具体错误原因。
解决:用异常来代替返回码。
进一步讨论下,异常名称应该如何命名:
1.ATMException
2.WithdrawException
3.NotEnoughMoneyException
以上三个中,哪一个更好?
从Java异常的定义看,一个异常是能包含一个message和一个其他异常的。
message用来表示错误的原因,cause用来传递上级的异常。
所以我们排除选项3,这个应该在message中表示。
public Throwable(String message, Throwable cause) { fillInStackTrace(); detailMessage = message; this.cause = cause; }
对于选项1和选项2,都表示了异常的来源,都是可以的,这个要看我们具体模块划分的粒度,如果是一个银行系统,那么ATM这个粒度是合适的,而如果是ATM机的系统,withdraw这个粒度是合适的。
最后还要说明一点的就是,自定义异常,建议定义为unchecked exception,这样就对上游代码没有侵入,可以比较顺利的达到传递到最外层的Level1目标。
修改后如下:
public int withdraw(int amount) { if (amount > 100) throw new ATMException("余额不足"); else return 100 - amount; }
案例2
看下下面这段代码有没有什么问题:public void close(AutoCloseable res) throws Exception { try { res.close(); } catch (Exception e) { logger.error("关闭资源错误", e); throw new Exception("关闭资源信息", e); } }
问题:在catch段中,同时记录了日志和抛出异常
解决:根据实际场景,二选一。
何时抛出异常呢,一般是在非外层的模块中,如案例1所示情况。
何时记日志呢,有两种场景:
第一是在finnally的场景中,不建议抛出,因为此类清理操作的异常会覆盖掉正常的异常情况。而案例中的代码,也是一个清理操作,是和finnally中代码等价的。所以只要记录日志即可。
public void close(AutoCloseable res) throws Exception { try { res.close(); } catch (Exception e) { logger.error("关闭资源错误", e); } }
第二是在程序的最外层,这个时候要统一记录日志,但需要注意的是,Exception并不能覆盖Java中的所有的异常,从全面性角度看,我们应该捕获Throwable来记录日志。
public static void main(String[] args) { try { //do something } catch (Throwable e) { logger.error("关闭资源错误", e); } }
Level2
和Level1相同,Level2中异常都往外报,但在错误发生时,故障程序是可以继续运行的。这里主要讨论下在Level2会用到的一些模式:考虑如下代码逻辑,如何可以在某步执行异常时,确保程序继续运行?
public void runBatchJob(List<Integer> numList) { int temp = 100; for (Integer num : numList) { temp = temp / num; } }
我们可以引入一个checkpoint对象,包含establish、recover、drop三个方法。
class NumCheckpoint{ private Integer checkpointedNum; public void establish(Integer backupNum) { //备份数据 this.checkpointedNum = backupNum; } public Integer recover() { // 将备份数据还原 return this.checkpointedNum; } public void drop() { // 删除备份资料 } }
然后在代码的try..catch...finnally块中分别使用,实现异常的恢复
public void runBatchJob(List<Integer> numList) { //Checkpoint对象 NumCheckpoint numCheckpoint = new NumCheckpoint(); Integer temp = 100; for (Integer num : numList) { try { //establish() numCheckpoint.establish(temp); temp = temp / num; } catch (Exception e) { //recover() temp = numCheckpoint.recover(); } finally { //drop() numCheckpoint.drop(); } } }
此外,我们可以引入一个ErrorCollector来收集异常
public interface ErrorCollector { List<Exception> getErrors(); void addError(Exception error); void clear(); int size(); }
public void runBatchJob(List<Integer> numList, ErrorCollector errorCollector) { //Checkpoint对象 NumCheckpoint numCheckpoint = new NumCheckpoint(); Integer temp = 100; for (Integer num : numList) { try { //establish() numCheckpoint.establish(temp); temp = temp / num; } catch (Exception e) { //recover() temp = numCheckpoint.recover(); errorCollector.addError(e); } finally { //drop() numCheckpoint.drop(); } } } }
Level3
Level3是要求在Level2的基础上,提供应急处理方法,确保业务能正确执行。我们来看一下案例:
public String readUser(String name) { try { return readFromDatabase(name); } catch (Exception e) { try { return readFromRedis(name); } catch (Exception ex) { throw new RuntimeException(ex); } } }
这段代码实现了Level3的向前恢复的要求,当从数据库获取失败时,从redis来取数,从功能上看是可以的。
但从代码结构看不太好。因为其将一部分的处理放在了catch段中,出现了异常嵌套的情况,这个是异常处理代码的一个禁忌。
所以我们做一下改进,使用循环来替代异常嵌套,逻辑看起来更清晰,而且只需要通过调整循环中的变量,就能实现retry的策略。
public String readUser(String name) { int attempt = 1; while (true) { try { if (attempt <= 1) { return readFromDatabase(name); } if (attempt == 2) { return readFromRedis(name); } } catch (Exception ex) { attempt++; if (attempt > 3) { throw new RuntimeException(ex); } } } }
小结
关于《例外处理设计的逆袭》这本书的介绍就到这里结束了,主要是讲了一些我认为比较重要的点,书中还有其他很多精彩的部分也欢迎大家去阅读。最后的最后,这本书的实战性是很强的,所以请大家在工作中多动手、多实践,只有这样才能把知识变成自己的技能。相关文章推荐
- 小课堂Week8 例外处理设计的逆袭Part1
- 小课堂Week9 例外处理设计的逆袭Part2
- TFS CMMI实施笔记:在设计单元测试的时候,发现设计有缺陷的时候,是怎样处理的过程?(欢迎大家讨论)
- Henry手记—使用Template Method设计模式的.NET事件处理机制(二)
- 智能客户端体系结构与设计指南 第 2 章 — 处理数据
- 设计模式学习的阶段性总结(创建型设计模式part)
- 文本处理的三种典型设计模式
- 「资讯处理用中文分词规范」设计理念及规范内容
- Asp.net2.0中创建控件时对应设计模式下的一些处理
- 进销存软件之OO设计--中间层处理(二)
- JavaScript高级应用:例外处理
- Henry手记—使用Template Method设计模式的.NET事件处理机制(一)
- UOP设计思想之异步处理
- JavaScript的例外处理(try...catch...finally)
- 搜索引擎设计实用教程(1)-以百度为例 之一:查询处理以及分词技术
- 反思以前对“多对多”关系处理的设计
- 进销存软件之OO设计--中间层处理(二)
- Visual C++多媒体设计及图形、图像处理
- JavaScript高级应用:例外处理
- 数据仓库自动抽取:通过 SQL Server 企业管理器中的数据转换服务 (DTS) 设计器 创建 Analysis Services 处理任务