zoukankan      html  css  js  c++  java
  • 小课堂Week10 例外处理设计的逆袭Part3

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

    小结

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

  • 相关阅读:
    分布式锁_00_资源帖
    JVM_总结_03_Java发展史
    JVM_总结_02_Java技术体系
    JVM_总结_00_资源帖
    分布式事务_03_2PC框架raincat源码解析-事务提交过程
    Java企业微信开发_15_查询企业微信域名对应的所有ip
    分布式事务_02_2PC框架raincat源码解析-启动过程
    Disruptor_学习_00_资源帖
    Git_学习_09_Commit message 和 Change log 编写指南
    分布式_事务_01_2PC框架raincat快速体验1
  • 原文地址:https://www.cnblogs.com/dt-zhw/p/5857756.html
Copyright © 2011-2022 走看看