干净代码应用于 JavaScript — 第五部分:异常
介绍
优先使用异常而不是返回错误代码
不要忽视捕获的错误!
不要忽视被拒绝的承诺
异常层次结构
提供例外情况的背景信息
结论
介绍
异常是高质量软件开发中不可或缺的一部分,因为我们需要控制意外或未实现的情况。因此,开发人员有时会将错误处理与软件流程处理混淆。异常应该用于处理软件中不受控制或已实现的情况,而绝不能用来模拟业务逻辑的“返回”,从而引导软件流程朝某个方向发展。
在这篇文章中,我们将提供一些与处理异常相关的建议,这些建议将使您的代码能够使用异常保持干净
优先使用异常而不是返回错误代码
当编程语言支持异常处理时,使用异常比使用错误代码更好。这句话看似显而易见,但实际上并非如此,因为许多程序员学习的编程语言缺乏此功能,或者没有意识到它的潜力,从而忽略了它的使用。然而,使用异常会比在代码中管理错误代码更简洁。
下面的代码展示了一个类,其中没有使用异常,并且必须通过“if”语句手动管理不受控制的情况。相反,我们必须通过异常将所有这些繁琐而肮脏的任务委托给语言。观察第二个代码,其中业务逻辑已与错误管理分离。该代码具有以下优点:
- 业务逻辑和错误控制解耦。这是两个不同的问题,必须分开处理。
- 代码更简洁,更易于阅读。
- 错误代码的责任已经委托给编程语言,它必须为我们服务,而不是相反。
// Dirty
class Laptop {
sendShutDown() {
const deviceID = getID(DEVICE_LAPTOP);
if (deviceID !== DEVICE_STATUS.INVALID) {
const laptop = DB.findOne(deviceID);
if (laptop.getStatus() !== DEVICE_SUSPENDED) {
pauseDevice(deviceID);
clearDeviceWorkQueue(deviceID);
closeDevice(deviceID);
} else {
logger.log('Device suspended. Unable to shut down');
}
} else {
logger.log('Invalid handle for: ' + DEVICE_LAPTOP.toString());
}
}
}
// Clean
/*
The code is better because the algorithm
and error handling, are now separated.
*/
class Laptop {
sendShutDown() {
try {
tryToShutDown();
} catch (error) {
logger.log(error);
}
}
tryToShutDown() {
const deviceID = getID(DEVICE_LAPTOP);
const laptop = DB.findOne(deviceID);
pauseDevice(deviceID);
clearDeviceWorkQueue(deviceID);
closeDevice(deviceID);
}
getID(deviceID) {
throw new DeviceShutDownError('Invalid handle for: ' + deviceID.toString());
}
}
不要忽视捕获的错误!
请不要采取鸵鸟策略!
鸵鸟策略就是把头埋在地下,每当我们出现管理失误而什么也不做的时候,我们就会这么做。
务必记住,对错误执行console.log或system.out.println操作意味着什么都没做。事实上,这样做更危险,因为如果我们在异常发生时执行了这些错误的控制,我们就能亲眼看到它。因此,切勿忽视异常管理,异常是由意外情况引起的,必须妥善处理。
第一个代码是初级程序员或使用鸵鸟策略的程序员的惯常处理方式,由于错误不再中断应用程序,所以处理起来相当简单。但真正应该做的是第二个示例,即进行正确的处理。当然,我知道处理错误需要时间和精力。
try {
functionThatMightThrow();
} catch (error) {
console.log(error);
}
try {
functionThatMightThrow();
} catch (error){
console.error(error);
notifyUserOfError(error);
reportErrorToService(error);
}
不要忽视被拒绝的承诺
就像上一个例子一样,我们忽略了对错误的处理。JavaScript 中存在异步性,而处理异步性的工具之一就是 Promise。
承诺可能会被拒绝(这本身并不是一个错误),因此我们必须像对待错误一样对待它们。
在这种情况下,我们看到与前一种情况相同的例子,但应用于承诺。
getData()
.then(data => functionThatMightThrow(data))
.catch(error => console.log);
getData()
.then(data => functionThatMightThrow(data))
.catch(error => {
console.log(error);
notifyUserOfError(error);
reportErrorToService(error);
});
异常层次结构
创建异常层次结构。每种编程语言都有一组自己的低级异常:NullPointerException 或
ArrayIndexOutOfBoundsException。这些异常与我们的业务逻辑无关,它们不会给我们带来任何帮助。由于我们的代码是在建模业务逻辑,因此使用这些异常来控制代码中发生的错误毫无意义。因此,我们必须创建自己的异常层次结构,它代表我们的业务逻辑,并在业务逻辑中发生意外情况时触发。
在下面的示例中,创建了两个异常,分别称为 UserException 和 AdminException,这两个异常发生在两种类型的用户上,但不再发生在数据结构上。现在我们有了业务逻辑,实际上,这两个异常过于通用,我们可以定义以下类型的异常:UserRepeatException、UserNotFoundException 等……
我们对异常的语义价值做出了贡献,而这是我们通过其他方式无法获得的。
export class UserException extends Error {
constructor(message) {
super(`User: ${mesage}`);
}
}
export class AdminException extends Error {
constructor(message) {
super(`Admin: ${message}`);
}
}
// Client code
const id = 1;
const user = this.users.find({ id });
if(user){
throw new UserException('This user already exists');
}
提供例外情况的背景信息
虽然异常具有堆栈跟踪,使我们能够查看异常发生时的链式调用,但这理解起来很复杂。因此,我们为异常添加上下文来改进此功能。通常,我们会添加一条消息,解释软件中失败操作的意图。请不要使用难以理解的代码。需要注意的是,我们提供的信息不应该是最终用户看到的内容,因为我们应该妥善管理异常,因此这些代码不会显示在用户界面中,而是显示对他们更有用的内容。
如果我们开发出异常的层次结构,我们将为异常提供背景。
结论
在这篇文章中,我们提出了一些关于创建例外的建议。
异常是高质量软件开发中的一个基本部分,在很多情况下,它们会被忽略,或者只是试图保持不正确以重定向应用程序的流程。
无论如何,如果编程语言提供了此功能,我们就必须利用它并将其委托给语言以专注于业务逻辑。
最后,我们讨论的要点如下:
- 优先使用异常而不是返回错误代码
- 不要忽视捕获的错误!
- 不要忽视被拒绝的承诺
- 异常层次结构
- 提供例外情况的背景信息