[译] 由 no-constant-binary-expression 捕捉到的几个有趣的代码缺陷

Posted on:  at 

原文地址:https://eslint.org/blog/2022/07/interesting-bugs-caught-by-no-constant-binary-expression/
原文作者:Jordan Eldredge
原文发布于:2022 年 07 月 06 日

这个新规则能捕捉大量惊人惊讶的逻辑错误。

我在 ESLint v8.14.0 提交了一个新的核心规则 no-constant-binary-expression。我通过这个规则检测到大量的微妙、有趣的代码缺陷。

在这篇文章中,我将解释该规则的作用,并分享它在 Material UI、Webpack、VS Code 和 Firefox 等流行开源项目中检测到的一些真实错误示例,以及它在 Meta 内部发现的一些有趣错误。我希望这些示例能说服您尝试在自己的项目中启用该规则!

no-constant-binary-expression 有什么作用?

该规则检查运行时结果不会改变的比较(==!== 等),以及总是或从不短路的逻辑表达式(&&??||)。

举个例子:

  • +x == null 永远都是 fasle 的,因为 + 会把 x 强制变成一个数字,而数字永远不会是 nullish。
  • { ...foo } || DEFAULT 永远不会返回 DEFAULT,因为对象总是 truthy 的。

使用 no-constant-binary-expressions 在真实世界中发现到的错误

在本节中,我将与大家分享该规则可以捕捉到的一些错误类型。每种类型都至少包含一个在流行的开源项目中发现的具体实例。我选择在这里列举真实的例子,并不是要羞辱任何人或任何项目,而是要让大家明白,这些错误是任何团队都很容易犯的。

混淆运算符优先级

该规则发现的最常见错误是开发人员误解了运算符的优先级,尤其是一元运算符,如 !+typeof

if (!whitelist.has(specifier.imported.name) == null) {
  return;
}

来自 Material UI(另见:VS Code 12WebpackMozilla

混淆 ??|| 的优先级

在尝试定义默认值时,人们会对 a === b ?? c 这样的表达式感到困惑,并认为它会被解析为 a === (b ?? c)。而实际上,它将被解析为 (a === b) ?? c

shouldShowWelcome() {
  return this.viewModel?.welcomeExperience === WelcomeExperience.ForWorkspace ?? true;
}

_来自 _VS Code

_题外话: 观察到开发人员经常对运算符优先级感到困惑,这激发了我的灵感,我尝试使用 _VS Code 扩展来直观地说明优先级是如何解释的。

期望按值比较对象

在其他语言中,结构是通过值而不是引用来进行比较的,因此来自其他语言的开发人员很容易掉入这样一个陷阱:他们以为可以通过与新创建的空对象进行比较来测试对象是否为空。当然,在 JavaScript 中,对象是通过引用进行比较的,任何值都不可能等于新创建的对象字面。

在下面这个示例中,hasData 将始终设置为 true,因为 data 永远不可能等同于新创建的对象。

hasData = hasData || data !== {};

来自 Firefox(另见:FireFox

期望空对象为 falsenull

另一种常见的 JavaScript 错误是将空对象视为 nullish 或 falsy。对于来自 Python 等语言的用户来说,这很可能是一个容易犯的错误,因为在 Python 中,空列表和字典都是 falsy 的。

const newConfigValue = { ...configProfiles } ?? {};

来自 VS Code(另见:VS Code 12

>= 还是 =>

我只见过一次这样的错误情景,但我还是想把它写出来,因为它是一个很好的例子,说明这条规则可以捕捉到意想不到的错误类型。

在这里,开发人员的本意是测试一个值是否大于或等于零(>= 0),但却不小心颠倒了字符的顺序,创建了一个返回 0 && startWidth <= 1 的箭头函数!

assert(startWidth => 0 && startWidth <= 1);

_来自 _Mozilla


no-constant-binary-expression 捕捉到的其他错误

以上五类错误并不详尽。我最初在 Meta 的大型 monorepo 上运行这一规则的第一个版本时,发现了 500 多个问题。虽然许多问题属于上述类别,但也有一些其他有趣的长尾错误。其中包括:

  • 以为 || 支持集合操作:states.includes('VALID' || 'IN_PROGRESS')
  • 以为原生函数的返回结果是 nulls:Number(x) == null
  • 不知道原生对象的构造函数返回的是包装对象:new Number(x) === 10

我从来没有打算逐一找出这些具体问题,但通过努力找出任何“无用”的东西,我们得以发现并纠正这些问题。


结论

正如你现在看到的,no-constant-binary-expression 能够检测出各种不同类型的错误。该规则之所以能做到这一点,并不是因为它的程序设计就是为了查找这些特定的问题,而是因为所有这些错误都有一个共同点:它们表现为无用代码。由于开发人员通常不会编写无用代码,因此检测无用代码通常就能检测出错误。

如果您觉得这些示例很有说服力,请考虑在 ESLint 配置中启用 no-constant-binary-expression

// eslintrc
module.exports = {
  rules: {
    // Requires eslint >= v8.14.0
    "no-constant-binary-expression": "error"
  }
}

如果你这样做了,并且发现了错误,我很乐意听听你的意见

感谢 Brad Zacher 最初的观察,他的观察启发了这项工作,并建议将其作为新的核心规则。感谢 Milos Djermanovic 在代码审查过程中做出的重要贡献。