小课堂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);
}
}
}
}

小结

关于《例外处理设计的逆袭》这本书的介绍就到这里结束了,主要是讲了一些我认为比较重要的点,书中还有其他很多精彩的部分也欢迎大家去阅读。最后的最后,这本书的实战性是很强的,所以请大家在工作中多动手、多实践,只有这样才能把知识变成自己的技能。

小课堂Week10 例外处理设计的逆袭Part3的更多相关文章

  1. 小课堂Week9 例外处理设计的逆袭Part2

    小课堂Week9 例外处理设计的逆袭Part2 今天继续阅读<例外处理设计的逆袭>这本书,我们先看两个案例: 案例1 问:如果要设计一个依据学号到数据库中查询学生资料的函数,当找不到符合条 ...

  2. 小课堂Week8 例外处理设计的逆袭Part1

    小课堂Week8 例外处理设计的逆袭Part1 今天和大家讲一本书,书名是<例外处理设计的逆袭>. 为什么想讲这本书,是因为,例外处理在程序代码中到处存在,但是这些到底该如何写好,总觉得有 ...

  3. Spark小课堂Week7 从Spark中一个例子看面向对象设计

    Spark小课堂Week7 从Spark中一个例子看面向对象设计 今天我们讨论了个问题,来设计一个Spark中的常用功能. 功能描述:数据源是一切处理的源头,这次要实现下加载数据源的方法load() ...

  4. Spark小课堂Week2 Hello Streaming

    Spark小课堂Week2 Hello Streaming 我们是怎么进行数据处理的? 批量方式处理 目前最常采用的是批量方式处理,指非工作时间运行,定时或者事件触发.这种方式的好处是逻辑简单,不影响 ...

  5. Spark小课堂Week1 Hello Spark

    Spark小课堂Week1 Hello Spark 看到Spark这个词,你的第一印象是什么? 这是一朵"火花",官方的定义是Spark是一个高速的.通用的.分布式计算系统!!! ...

  6. 小课堂week15 年终小结

    年终小结 一年的最后,想和大家回顾一下今年讲过的技术和书,用一些问答,一起来提炼一下精华. Spark 为什么需要分布式计算? 计算的增长速度超过了硬件的增长,单一服务器无法负荷.多服务器带来的是复杂 ...

  7. 小课堂Week12 Clean Code Part1

    小课堂Week12 Clean Code Part1 今天的主题是函数,让我们看一个函数,找一找其中的"不整洁". 我们也根据这段代码,讨论下对于整洁代码的两个重要原则. publ ...

  8. 小课堂Week11 会说话的代码

    小课堂Week11 会说话的代码 今天主要讨论下,在编码过程中和"命名"相关的问题.因为命名方法比较自由,如果要提高可读性,我们需要尽量使其符合正规的英文语法习惯. 变量/属性 通 ...

  9. Spark小课堂Week6 启动日志详解

    Spark小课堂Week6 启动日志详解 作为分布式系统,Spark程序是非常难以使用传统方法来进行调试的,所以我们主要的武器是日志,今天会对启动日志进行一下详解. 日志详解 今天主要遍历下Strea ...

随机推荐

  1. windows获取时间的方法

    介绍       我们在衡量一个函数运行时间,或者判断一个算法的时间效率,或者在程序中我们需要一个定时器,定时执 行一个特定的操作,比如在多媒体中,比如在游戏中等,都会用到时间函数.还比如我们通过记录 ...

  2. MySQL(25):事务的隔离级别出现问题之 不可重复读

    1. 不可重复读 所谓的不可重复读(Non-Repeatable Read)是指事务中两次查询的结果不一致,原因是在查询的过程中其他事务做了更新的操作. 例如,银行在做统计报表的时候,第一次查询a账户 ...

  3. vb.net机房收费系统之组合查询

    我个人一直认为,组合查询是机房收费系统的一个难点,尤其是用到三层之后,如果要为组合查询中的每一个查询建立一个显然是太麻烦了. 下面介绍一下我的方法,对大家起个参考作用. 我将该表中可输入的内容定义为一 ...

  4. Java程序员必备的 15框开发工具

    15款Java程序员必备的开发工具 如果你是一名Web开发人员,那么用膝盖想也知道你的职业生涯大部分将使用Java而度过.这是一款商业级的编程语言,我们没有办法不接触它. 对于Java,有两种截然不同 ...

  5. VMware系统运维(十四)部署虚拟化桌面 Horzion View Manager 5.2 配置许可

    1.通过网页访问https://conntionserver.testad.local/admin,打开如下图所示页面:输入用户名密码 2.进来以后配置View 许可,点击"编辑许可证&qu ...

  6. jquery移除、绑定、触发元素事件使用示例详解

    这篇文章主要介绍了jquery移除.绑定.触发元素事件使用示例详解,需要的朋友可以参考下. unbind(type [,data]) //data是要移除的函数 $('#btn').unbind(&q ...

  7. hdu 4421 Bit Magic

    [题意] 这个函数是给A求B的,现在给你B,问你是否能有A的解存在. [2-SAT解法] 对于每个A[i]的每一位运行2-sat算法,只要跑到强连通就可以结束,应为只要判断是否有解,后面拓扑求解就不需 ...

  8. CentOS里下载百度网盘 / 360 网盘资源的方法

    wget -c --referer=引用地址 -O 文件名 "真实下载地址" 例如,我下载资源原在的页面为http://pan.baidu.com/s/1cfJXZ ,实际下载地址 ...

  9. hdu 3234 并查集

    虚拟一个根节点n,设其值为0.并且始终保持其为根. #include<map> #include<set> #include<cmath> #include< ...

  10. 【转】ASP.NET 高效分页存储过程

    代码 Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-- ...