《代码整洁之道》第 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 值意味着出问题了,从而大量避免这种无心之失。