《代码整洁之道》第 7 章 错误处理

第 7 章 错误处理

7.1 使用异常而非返回码

在很久以前,许多语言都不支持异常。这些语言处理和汇报错误的手段都有限。你要么设
置一个错误标识,要么返回给调用者检查的错误码。代码清单7-1中的代码展示了这些手段。

// 代码清单7-1 DeviceContollerjava
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) ;
      	clearDeviceWorkQteue(handle) ;
      	closeDevice(handle) ;
      } else {
      	logger.log ("Device suspended. Unable to shut down") ;
      }
    } else {
      logger.log ("Invalid handle for: " + DEV1.toString());
    }
   }
	...    
}

这类手段的问题在于,它们搞乱了调用者代码。调用者必须在调用之后即刻检查错误。不幸的是,这个步骤很容易被遗忘。所以,遇到错误时,最好抛出一个异常。调用代码很整洁,其逻辑不会被错误处理搞乱。
代码清单 7-2 展示了在方法中遇到错误时抛出异常的情形。

// 代码清单7-2 DeviceController.java (采用异常处理)
public class DeviceController {
  ...
  public void sendShutDown() {
    try {
      tryToShutDown();
    } catch (DeviceShutDownError e) {
      logger.log(e);
    }
	}
  
  private void tryToShutDown() throws Devi ceShut DownError {
    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()) ;
    ...
  }
  ...
}

注意这段代码整洁了很多。这不仅关乎美观。这段代码更好,因为之前纠结的两个元素设备关闭算法和错误处理现在被隔离了。你可以查看其中任一元素,分别理解它。

7.2 先写 Try-Catch-Finally 语句

异常的妙处之- -是,它们在程序中定义了一个范围。执行 try-catch-finally 语句中 try 部分的代码时,你是在表明可随时取消执行,并在 catch 语句中接续。
在某种意义上,try 代码块就像是事务。catch 代码块将程序维持在一种持续状态, 无论 try 代码块中发生了什么均如此。所以,在编写可能抛出异常的代码时,最好先写出 try-catch-finally 语句。这能帮你定义代码的用户应该期待什么,无论 try 代码块中执行的代码出什么错一样。

7.4 给出异常发生的环境说明

你抛出的每个异常,都应当提供足够的环境说明,以便判断错误的来源和处所。在 Java 中,你可以从任何异常里得到堆栈踪迹(stack trace);然而,堆栈踪迹却无法告诉你该失败操作的初衷。
应创建信息充分的错误消息,并和异常一起传递出去。在消息中,包括失败的操作和失败类型。如果你的应用程序有日志系统,传递足够的信息给 catch 块,并记录下来。

7.5 依调用者需要定义异常类

对错误分类有很多方式。可以依其来源分类:是来自组件还是其他地方?或依其类型分
类:是设备错误、网络错误还是编程错误?不过,当我们在应用程序中定义异常类时,最重
要的考虑应该是它们如何被捕获。
来看一个不太好的异常分类例子。下面的try-catch-finally语句是对某个第三方代码库的
调用。它覆盖了该调用可能抛出的所有异常:

ACMEPort port = new ACMEPort(12) ;

try {
	port.open() ;
} catch (DeviceResponseException e) {
	reportPortError(e) ;
	logger.log("Device response exception", e);
} catch (ATM1212UnlockedException e) {
	reportPortError(e) ;
	logger.log("Unlock exception", e);
} catch (GMXError e) {
	reportPortError(e);
	logger.log("Device response exception") ;
} finally {
	...
}

语句包含了一大堆重复代码,这并不出奇。在大多数异常处理中,不管真实原因如何,我们总是做相对标准的处理。我们得记录错误,确保能继续工作。

7.7 别返回 null 值

这种代码看似不坏,其实糟透了!返回 null 值,基本上是在给自己增加工作量,也是在给调用者添乱。只要有一处没检查 null 值,应用程序就会失控。

对于从应用程序深处抛出的 NullPointerException 异常,你到底该作何反应呢?

7.8 别传递 null 值

在大多数编程语言中,没有良好的方法能对付由调用者意外传入的 null 值。事已如此, 恰当的做法就是禁止传入 null 值。这样,你在编码的时候,就会时时记住参数列表中的 null 值意味着出问题了,从而大量避免这种无心之失。

posted @ 2023-08-02 16:06  CoolGin  阅读(25)  评论(0编辑  收藏  举报