[学习笔记]《代码整洁之道》(六)

[学习笔记] 《代码整洁之道》— 第7章 错误处理

错误处理很重要,但如果它搞乱了代码逻辑,就是错误的做法。

使用异常而非返回码

  • 很久以前,许多语言都不支持异常。这些语言处理和汇报错误的手段有限。

    • 要么设置一个错误标识,
    • 要么返回给调用着检查的错误码。
    public class DeviceController{
        ...
        public void sendShutDown(){
        	DeviceHandle handle = getHandle(DEV1);
            // Check the state of the device
            if(handle != DeviceHandle.INVALID){
                // Save the device status to the record field
                retrieveDeviceRecord(handle);
                //if not suspended, shut down
                if(record.getStatus() != DEVICE_SUSPENDED){
                    pauseDevice(handle);
                    clearDeviceWorkQueue(handle);
                    closeDevice(handle);
                }else{
                    logger.log("Device suspended. Uable to shut down");
                }
            }else{
                logger.log("Invalid handle for: " + DEV1.toString());
            }
        }
        ...
    }
    
    • 这类手段搞乱了调用者代码。调用者必须在调用之后即刻检查错误。
      • 很不幸,这个步骤很容易遗忘。
      • 遇到错误,最好抛出一个异常。
        • 调用代码很整洁,其逻辑不会被错误处理搞乱。
    • 采用异常处理的代码:
    public class DeviceController{
        ...
        
        public void sendShutDown(){
            try{
                tryToShutDown();
            }catch(DeviceShutDownError e){
                logger.log(e);
            }
        }
        
        private void tryToShutDown() throws DeviceShutDownError{
            DeviceHandle handle = getHandle(DEV1);
            DeviceRecord record = retrieveDeviceRecord(handle);
            
            pauseDevice(handle);
            clearDeviceWorkQueue(handle);
            closeDevice(handle);
        }
        
        private DeviceHandle getHandle(DeviceID id){
            ...
            throw new DeviceShutDownError("Invalid handle for: " + id.toString());
            ...
        }
        ...          
    }
    
    • 之前纠结的两个元素设备关闭算法和错误处理现在被隔离了。
    • 你可以查看其中任意元素,分别理解它。

先写Try-Catch-Finally语句

缺少基础知识储备,无法理解!留后再学习!

使用不可控异常

给出异常发生的环境说明

  • 你抛出的每一个异常,都应当提供足够的环境说明,以便判断错误的来源和处所。
    • 应创建信息充分的错误消息,并和异常一起传递出去。
      • 包括失败的操作和失败类型

依调用者需要定义异常类

  • 对错误分类有很多方式

    • 来源分类
    • 类型分类
  • 在应用程序中定义异常类时,最重要的考虑应该该是它们如何被捕获

    ACMEPort port = new ACMEPort(12);
    
    try{
        port.open();
    }catch(DeviceResponseException e){
        reportProtError(e);
        logger.log("Device response exception", e);
    }catch(ATM1212UnclockedException e){
        reportProtError(e);
        logger.log("Unlock exception", e);
    }catch(GMXError e){
        reportProtError(e);
        logger.log("Device response exception");
    }finally{
        ...
    }
    
    • 本例中,既然知道我们所做的事不外如此,就可以通过打包调用 API、确保他返回通用异常类型,从而简化代码。

      LocalPort port = new LocalPort(12);
      try{
          port.open();
      }catch(PortDeviceFailure e){
          reportProtError(e);
          logger.log(e.getMessage(), e);
      }finally{
          ...
      }
      
      • LocalPort 就是个简单的打包类,捕获并翻译由 ACMEPort 类抛出的异常。

        public class LocalPort{
            private ACMEPort innerPort;
            
            public LocalPort(int portNumber){
                innerPort = new ACMEPort(portNumber);
            }
            
            public void open(){
                try{
                    innerPort.open();
                }catch(DeviceResponseException e){
                    throw new PortDeviceFailure(e);
                }catch(ATM1212UnclockedException e){
                    throw new PortDeviceFailure(e);
                }catch(GMXError e){
                    throw new PortDeviceFailure(e);
                }
            }
            ...
        }
        
  • 对于代码的某个特定区域,单一异常类通常可行。伴随异常发送出来的信息能够区分不同错误。

定义常规流程

  • 如果在业务逻辑和错误处理之间有良好的区隔,大量代码会爱事变的像是整洁而简朴的算法。然而,这样做却把错误检测推到了程序的边缘地带。

    • 某记账应用的开支总计模块:业务逻辑是,如果消耗了餐食,则计入总额中。如果没有消耗,则员工得到当日餐食补帖。

      try{
          MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
          m_total += expenses.getTotal();
      }catch(MealExpensesNoFound e){
          m_total += getMealPerDiem();
      }
      

      异常打断了业务逻辑。如果不去处理特殊情况,代码看起来更简洁。如下:

      MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
      m_total += expenses.getTotal();
      

      修改 ExpenseReportDAO ,使其总返回 MealExpenses 对象。如果没有餐食消耗,就返回一个返回餐食补贴的 MealExpenses 对象。

      public class PerDiemMealExpenses implements MealExpenses{
          public int getTotal(){
              // return the per diem default
          }
      }
      
      • 特例模式(Special Case Pattern)
        • 创建一个类活配置一个对象,用来处理特例。

null 值

  • 别返回 null 值
  • 别传递 null 值
    • 在方法中返回 null 值是糟糕的做法,但将 null 值传递给其他方法就更糟糕了。
    • 除非 API 要求你向它传递 null 值,否则就要尽可能的避免传递 null 值。

小结

  • 整洁代码是可读的,但也要强固。可读和强固并不冲突。
  • 将错误处理隔离看待,独立于主要逻辑之外,就能写出强固而整洁的代码。

参考文献

[1] Robert C. Martin 著,韩磊 译,《代码整洁之道》,北京:人民邮电出版社,2010.1(2018.9 重印), ISBN 978-7-115-21687-8。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值