如何处理异常
异常的发生不可避免,所以在软件开发中,合理的异常处理就成为了高质量代码不可或缺的一部分,只有处理好了异常我们才能对程序中的意外情况进行有效的控制。我们最容易容易犯的一个问题就是将异常处理和业务的流程混为一谈。
根据Clean Code的建议,面对异常我们可以遵循以下一些原则,提高代码质量:
Prefer Exceptions to Returning Error Codes
优先选择异常而不是错误码。
要理解这句话还是得结合例子,下面的第一段代码定义了一个Laptop类,在它的sendShutDown方法实现中,用if语句去检查了getID的返回值中是否存在无效的deviceID,错误检查会使调用者的代码变得复杂不易阅读业务逻辑,同时如果这个错误检查被遗漏也会导致代码出现问题,这个错误的处理可以交给语言让整个过程更加优雅。第二段代码中则将异常处理隔离了两个不同的逻辑,这样做会带来一些优势:
1、业务流程更加清晰易读,我们把异常和业务流程理解为两个不同的问题,可以分开去处理;
2、分开来的两个逻辑都更加聚焦,代码更简洁;
3、将处理程序异常的职责交给了编程语言,明确了边界;
// Dirty
class Laptop {
sendShutDown() {
const deviceID = getID(DEVICE_LAPTOP);
if (deviceID !== DEVICE_STATUS.INVALID) {
pauseDevice(deviceID);
clearDeviceWorkQueue(deviceID);
closeDevice(deviceID);
} else {
logger.log('Invalid handle for: ' + DEVICE_LAPTOP.toString());
}
}
getID(status) {
...
// 总是会返回deviceID,无论是不是合法有效的
return deviceID;
}
}
// Clean
class Laptop {
sendShutDown() {
try {
tryToShutDown();
} catch (error) {
logger.log(error);
}
}
tryToShutDown() {
const deviceID = getID(DEVICE_LAPTOP);
pauseDevice(deviceID);
clearDeviceWorkQueue(deviceID);
closeDevice(deviceID);
}
getID(status) {
...
throw new DeviceShutDownError('Invalid handle for: ' + deviceID.toString());
...
return deviceID;
}
}
Don't ignore caught error!
捕获到异常后不要忽略异常处理!
在之前的代码评审中就经常有看到我们同学会在catch块中什么都不做,或者迫于eslint的检查会写一个console.log(error),这同样意味着什么都没有做。属于眼睁睁看到异常发生了不采取任何措施,这样的处理方式非常危险,因为这些异常通常由我们没有考虑到的意外情况引起,从中能发现业务逻辑中不易发现的问题,一旦我们捕获了这些异常,顶层的错误监控也不能主动捕获到这些问题,程序也许没有崩溃但如果没有用户告知我们,我们就无法发现用户的哪些功能无法正常使用了,因此最起码也要对这些异常做日志上报;