Clean Code Applied to JavaScript — Part V. Exceptions Introduction Prefer Exceptions to Returning Error Codes Don't ignore caught error! Don't ignore rejected promises Exceptions Hierarchy Provide context with exceptions Conclusions

2025-05-25

干净代码应用于 JavaScript — 第五部分:异常

介绍

优先使用异常而不是返回错误代码

不要忽视捕获的错误!

不要忽视被拒绝的承诺

异常层次结构

提供例外情况的背景信息

结论

介绍

异常是高质量软件开发中不可或缺的一部分,因为我们需要控制意外或未实现的情况。因此,开发人员有时会将错误处理与软件流程处理混淆。异常应该用于处理软件中不受控制或已实现的情况,而绝不能用来模拟业务逻辑的“返回”,从而引导软件流程朝某个方向发展。

在这篇文章中,我们将提供一些与处理异常相关的建议,这些建议将使您的代码能够使用异常保持干净

优先使用异常而不是返回错误代码

当编程语言支持异常处理时,使用异常比使用错误代码更好。这句话看似显而易见,但实际上并非如此,因为许多程序员学习的编程语言缺乏此功能,或者没有意识到它的潜力,从而忽略了它的使用。然而,使用异常会比在代码中管理错误代码更简洁。

下面的代码展示了一个类,其中没有使用异常,并且必须通过“if”语句手动管理不受控制的情况。相反,我们必须通过异常将所有这些繁琐而肮脏的任务委托给语言。观察第二个代码,其中业务逻辑已与错误管理分离。该代码具有以下优点:

  1. 业务逻辑和错误控制解耦。这是两个不同的问题,必须分开处理。
  2. 代码更简洁,更易于阅读。
  3. 错误代码的责任已经委托给编程语言,它必须为我们服务,而不是相反。
// 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.logsystem.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');
}

提供例外情况的背景信息

虽然异常具有堆栈跟踪,使我们能够查看异常发生时的链式调用,但这理解起来很复杂。因此,我们为异常添加上下文来改进此功能。通常,我们会添加一条消息,解释软件中失败操作的意图。请不要使用难以理解的代码。需要注意的是,我们提供的信息不应该是最终用户看到的内容,因为我们应该妥善管理异常,因此这些代码不会显示在用户界面中,而是显示对他们更有用的内容。

如果我们开发出异常的层次结构,我们将为异常提供背景。

结论

在这篇文章中,我们提出了一些关于创建例外的建议。

异常是高质量软件开发中的一个基本部分,在很多情况下,它们会被忽略,或者只是试图保持不正确以重定向应用程序的流程。

无论如何,如果编程语言提供了此功能,我们就必须利用它并将其委托给语言以专注于业务逻辑。

最后,我们讨论的要点如下:

  • 优先使用异常而不是返回错误代码
  • 不要忽视捕获的错误!
  • 不要忽视被拒绝的承诺
  • 异常层次结构
  • 提供例外情况的背景信息
文章来源:https://dev.to/carlillo/clean-code-applied-to-javascript-part-v-exceptions-1k9d
PREV
ES2017 特性及简单示例
NEXT
干净代码应用于 JavaScript — 第二部分:变量