代码整洁之道_07错误处理

第七章 错误处理

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

1. 使用异常,而非返回码

返回码缺点:搞乱了调用者代码,调用者必须在调用之后即刻检查错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 返回码案例
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. Unable to shut down");
}
} else {
logger.log("Invalid handle for: " + DEV1.toString());
}
}
...
}

抛异常优点:调用代码整洁,逻辑不会被错误处理搞乱;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 抛异常案例
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());
...
}
...
}

2. 先些 try-catch-finally 语句

3. 使用未检异常

已检异常的代价就是违反开放闭合原则:

  • 较低层级的修改,都将波及较高层级的签名。

4. 给出异常发生的环境说明

应创建信息充分的错误信息,并和异常一起传递出去,在消息中,应包括失败的操作和失败的类型。

5. 依调用者需要定义异常类

异常来源丰富:自身代码、其他组件、设备错误、网络错误、编程错误……

当我们在应用程序中定义异常类时,最重要的考虑应该是他们如何捕获;

例如:将第三方API打包(返回自己定义的异常)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 调用第三方库 ACMEPort
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 {

}

打包类:LocalPort,获取并翻译ACMEPort类抛出的异常。

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
LocalPort port = new LocalPort(12);
try {
port.open();
} catch (PortDeviceFailure e) {
reportError(e);
logger.log(e.getMessage(), e);
} finally {
…}

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);
}
}…
}

6. 定义常规流程

特例模式:创建一个类或配置一个对象,用来处理特例。(异常行为被封装到特例对象中,客户端就不用应对异常行为了)

1
2
3
4
5
6
7
// 开支总计模块:如果消耗了餐食,则计入总额中,如果没有消耗,则员工得到当日餐食补贴。
try {
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
} catch (MealExpensesNotFound e) {
m_total += getMealPerDiem();
}

期望代码:不处理特殊情况

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

创建特例对象:

1
2
3
4
5
public class PerDiemMealExpenses implements MealExpenses {
public int getTotal() {
// return the per diem default
}
}

7. 别返回null值

如果你打算在方法中返回null值,不如

  • 抛出异常
  • 返回特例对象

如果你在调用第三方API中可能返回null值的方法,考虑用新方法打包这个方法,然后

  • 抛出异常
  • 返回特例对象

8. 别传递null值