Wetts's blog

Stay Hungry, Stay Foolish.

0%

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

使用异常而非返回码

如果使用返回码,调用者必须在调用之后即刻检查错误。

遇到错误时,最好抛出一个异常。调用代码很整洁,其逻辑不会被错误处理搞乱。

使用不可控异常

Java的第一个版本中引入可控异常时,看似一个极好的点子。每个方法的签名都列出它可能传递给调用者的异常,这些异常就是方法类型的一部分。

可控异常的代价就是违反了开放/闭合原则。如果你在方法中抛出可控异常,而catch语句在三个层级之上,你就得在catch语句和抛出异常处之间的每个方法签名中声明该异常。这意味着对软件中较低层级的修改,都将波及较高层级的签名。

如果你在编写一套关键代码库,则可控异常有时也会有用:你必须捕获异常。但对于一般的应用开发,其依赖成本要高于收益。

依调用者需要定义异常类

来看一个不太好的异常分类例子。下面的try-catch-finally语句是对某个第三方代码库的调用。它覆盖了该调用可能抛出的所有异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 {
...
}

语句包含了一大堆重复代码。在大多数异常处理中,不管真实原因如何,我们总是做相对标准的处理。

在本例中,既然知道我们所做的事不外如此,就可以通过打包调用API、确保它返回通用异常类型,从而简化代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
LocalPort port = new LocalPort(12);

try {
port.open();
} catch (PortDeviceFailure e) {
reportError(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 (ATM1212UnlockedException e) {
throw new PortDeviceFailure(e);
} catch (GMXError e) {
throw new PortDeviceFailure(e);
}
}
}

实际上,将第三方API打包是个良好的实践手段。当你打包一个第三方API,你就降低了对它的依赖:未来你可以不太痛苦地改用其他代码库。在你测试自己的代码时,打包也有助于模拟第三方调用。

打包的好处还在于你不必绑死在某个特定厂商的API设计上。你可以定义自己感觉舒服的API。在上例中,我们为port设备的错误定义了一个异常类型,然后发现这样能写出更整洁的代码。

对于代码的某个特定区域,单一异常类通常可行。伴随异常发送出来的信息能够区分不同错误。如果你想要捕获某个异常,并且放过其他异常,就使用不同的异常类。

别返回null值

返回null值,基本上是在给自己增加工作量,也是在给调用者添乱。只要有一处没检查null值,应用程序就会失控。

别传递null值

除非API要求你向它传递null值,否则就要尽可能避免传递null值。


整洁代码是可读的,但也要强固。可读与强固并不冲突,如果将错误处理隔离开看待,独立于主要逻辑之外,就能写出强固而整洁的代码。做到这一步,我们就能单独处理它,也极大地提升了代码的可维护性。