静态分析

静态分析是一种测试类型,它提供对代码的自动检查,而无需实际运行代码或编写自动化测试。如果您使用像 VSCode 这样的 IDE,您可能已经见过这种类型的测试——TypeScript 执行的类型检查是一种静态分析,它可以显示为错误或警告下方的波浪线。

ESLint

ESLint 是一种工具,可以提供有关代码库中可能存在的问题的反馈。这些问题可能是类型安全的,但本身就是错误或非标准行为。ESLint 允许您应用许多在您的代码库中检查的规则,包括其“推荐”集中的许多规则。

ESLint 规则的一个很好的例子是其 no-unsafe-finally 规则。这可以防止您在 finally 代码块内编写修改程序控制流的语句。这是一个很好的规则,因为这样做是一种不寻常的 JavaScript 编写方式,可能难以理解。但是,这也是一个健康的code review 流程应该能够检测到的东西。

  try {
    const result = await complexFetchFromNetwork();
    if (!result.ok) {
      throw new Error("failed to fetch");
    }
  } finally {
    // warning - this will 'overrule' the previous exception!
    return false;
  }

因此,ESLint 不能替代健康的 review 流程(以及定义代码库外观的样式指南),因为它不会捕获开发人员可能尝试引入代码库的每一种非正统方法。Google 的 Eng Practices 指南有一个关于“保持简单”的 简短部分

ESLint 允许您打破规则并将代码注释为“允许”。例如,您可以通过如下注释来允许之前的逻辑

  finally {
    // eslint-disable-next-line no-unsafe-finally
    return false;
  }

如果您发现自己经常违反规则,请考虑将其关闭。这些工具鼓励您以某种方式编写代码,但您的团队可能习惯于以不同的方式编写代码,并且已经意识到这种方法的风险。

最后,在一个大型代码库上启用静态分析工具可能会对原本运行良好的代码产生大量无益的噪音(和繁琐的重构工作)。因此,在项目的生命周期早期启用它更容易。

用于浏览器支持的 ESLint 插件

您可以向 ESLint 添加一个插件,该插件标记使用未被广泛支持或您的目标浏览器列表不支持的 API。eslint-plugin-compat 包可以在 API 可能对您的用户不可用时警告您,因此您不必 постоянно 自己跟踪。

用于静态分析的类型检查

在学习 JavaScript 时,通常会向新开发人员介绍它是一种弱类型语言的概念。也就是说,可以将变量声明为一种类型,然后将相同的位置用于完全不同的东西。这类似于 Python 和其他脚本语言,但不同于 C/C++ 和 Rust 等编译语言。

这种语言可能适合入门——可以说正是这种简单性使 JavaScript 如此受欢迎——但它通常是某些代码库的故障点,或者至少是导致混淆错误发生的原因。例如,通过传递一个 number 在期望 string 或对象类型的地方,类型不正确的值可能会在各种库中传播,最终导致令人困惑的 TypeError

TypeScript

TypeScript 是解决 JavaScript 缺乏类型信息的最主流的解决方案。本课程大量使用它。虽然这不是关于 TypeScript 的课程,但它可以成为您工具箱的重要组成部分,因为它提供了静态分析。

举一个简单的例子,这段代码希望得到一个接受 string 名称和 number 年龄的回调

const callback = (name: string, age: string): void => {
  console.info(name, 'is now', age, 'years old!');
};
onBirthday(callback);

通过 TypeScript 运行时或甚至在 IDE 中悬停时,会生成以下错误

bad.ts:4:12 - error TS2345: Argument of type '(name: string, age: string) => void' is not assignable to parameter of type '(name: string, age: number) => void'.
  Types of parameters 'age' and 'age' are incompatible.
    Type 'number' is not assignable to type 'string'.

4 onBirthday(callback);
             ~~~~~~~~

Found 1 error in bad.ts:4
The code from the
  previous example, displayed in an IDE with the error message displayed in a
  pop-up.
VSCode 指示您传递了不正确的类型。

最终,使用 TypeScript 的目标是防止此类错误——年龄应该是 number,而不是 string——蔓延到您的项目中。这种类型的错误很难使用其他类型的测试来检测。此外,类型系统可以在编写测试之前提供反馈。这可以通过在您开发软件时而不是在代码最终运行时为您提供关于类型错误的早期反馈,从而使编写代码的过程更容易。

使用 TypeScript 最具挑战性的部分是正确设置它。每个项目都需要一个 tsconfig.json 文件,该文件虽然主要由 tsc 命令行工具本身使用,但也由 VSCode 等 IDE 以及许多其他构建工具和工具(包括 Vitest)读取。此文件包含数百个选项和标志,您可以在这里找到一些关于设置它的好资源

通用 TypeScript 提示

通过 tsconfig.json 文件设置和使用 TypeScript 时,请记住以下几点

  • 确保您的源文件实际上包含在内并经过检查。如果一个文件神秘地“没有错误”,那可能是因为它没有被检查。
  • .d.ts 文件中显式描述类型和接口,而不是在编写函数时隐式描述它们,可以使您的代码库更易于测试。当所涉及的接口清晰时,编写模拟和“伪造”版本的代码更容易。 .

TypeScript 隐式 any

TypeScript 最强大和最有益的配置选项之一是 noImplicitAny 标志。但是,它通常也是最难启用的,特别是如果您已经有一个大型代码库。(如果您处于 strict 模式,则默认启用 noImplicitAny 标志,否则不启用。)

此标志将使此函数返回错误

export function fibonacci(n) {
  if (n <= 1) {
    return 0;
  } else if (n === 2) {
    return 1;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
}

即使作为读者,很清楚 n 应该是一个数字,TypeScript 也无法自信地确认这一点。如果您使用 VSCode,将鼠标悬停在该函数上会将其描述如下

function fibonacci(n: any): any

此函数的调用者将能够传递类型为 any(一种允许任何其他类型的类型)的值,而不仅仅是 number。通过启用 noImplicitAny 标志,您可以在开发期间保护此类代码,而无需为代码在特定位置传递错误的数据类型编写广泛的业务逻辑测试。

这里简单的修复方法是将 n 参数和 fibonacci 的返回类型都标记为 number

noImplicitAny 标志不会阻止您在代码库中显式编写 any。您仍然可以编写一个接受或返回 any 类型的函数。它只是确保您为每个变量赋予一个类型。