静态分析

静态分析是一种测试,无需实际运行代码或编写自动化测试,即可自动检查代码。如果您使用 VSCode 之类的 IDE,您可能已经见过这种测试 - TypeScript 执行的类型检查是一种静态分析,可能在错误或警告下方以波浪形行的形式显示。

ESLint

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

ESLint 规则的一个很好的例子是其 no-unsafe-Finally 规则。这样可以防止您在 finally 块中编写修改程序控制流的语句。这是一条很不错的规则,因为这是一种非常不寻常的 JavaScript 编写方式,难以遵循。不过,运行状况良好的代码审核流程应该能够检测到这个问题。

  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 不能替代健康的审核流程(以及定义代码库应该是什么样的样式指南),因为它无法捕获开发者可能尝试在代码库中引入的每一种非正统方法。Google 的工程实践指南中有一小段内容的主题是“保持简单”。

ESLint 可让您打破规则并将代码注释为“allowed”。例如,您可以通过按如下方式对前面的逻辑进行注解,以允许该逻辑:

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

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

最后,对大型代码库启用静态分析工具可能会对原本可以正常运行的代码产生大量无用的噪声(以及繁重的重构工作)。因此,在项目生命周期的早期阶段启用该功能会更加容易。

支持浏览器的 ESLint 插件

您可以向 ESLint 添加一个插件,用于标记那些未被广泛支持或不受目标浏览器列表支持的 API 的使用。当用户可能无法使用 API 时,eslint-plugin-compat 软件包会向您发出警告,这样您就不必不断自行跟踪。

静态分析的类型检查

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

这种语言可能适合入门使用,可以说,这种简单性使得 JavaScript 变得如此流行,但对于某些代码库来说,它常常是一个失败点,至少,至少能让发生令人困惑的错误。例如,通过传递预期为 string 或对象类型的 number,该类型错误的值可以在各个库中传播,最终导致令人困惑的 TypeError

TypeScript

对于 JavaScript 缺乏输入信息的情况,TypeScript 是最主流的解决方案。本课程广泛使用了该概念。虽然这并不是关于 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
上一个示例中的代码显示在 IDE 中,错误消息显示在弹出式窗口中。
VSCode 表示您传递的类型不正确。

最终,使用 TypeScript 的目的是防止此类错误(age 应为 number,而不是 string)侵入您的项目中。此类错误使用其他类型的测试可能很难检测到。此外,类型系统甚至可以在编写测试之前提供反馈。这样一来,您在开发软件时(而不是在代码最终运行时)就能获得有关类型错误的早期反馈,从而简化代码编写过程。

使用 TypeScript 的最大挑战在于如何正确设置。每个项目都需要一个 tsconfig.json 文件,虽然该文件主要供 tsc 命令行工具本身使用,但也可以由 VSCode 等 IDE 以及 Vitest 等许多其他构建工具和工具读取。此文件包含数百个选项和标志,您可以在以下位置找到一些实用的设置资源:

常规 TypeScript 提示

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

  • 请确保确实包含并检查了您的源文件。如果文件神秘地“没有错误”,可能是因为没有检查该文件。
  • .d.ts 文件中显式描述类型和接口,而不是在编写函数时隐式描述它们,这样可使代码库更易于测试。如果所涉及的接口清晰,编写模拟和“虚构”代码会更容易。.

TypeScript 隐式任何

noImplicitAny 标志是 TypeScript 最强大且奖励最丰厚的配置选项之一。不过,这通常也是最难以启用的,尤其是在您已经拥有大型代码库的情况下。(如果您处于 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 类型的函数。它只是确保您为每个变量指定一个类型。