no-unused-binary-expressions:从代码审查小问题到生态系统改进

实现 ESLint 规则如何导致人们编写 JavaScript 方式的改变

四年前,当我在工作中进行代码审查时,我很惊讶 Flow 没有对不必要的空值检查发出警告。上个月,TypeScript 5.6 发布了带有验证规则的新版本,该规则不允许无用的 nullish 和 truthy 检查,这在 GitHub 上排名前 800 的 TypeScript 仓库中揭示了近 100 个现有的 Bug。

🌐 Four years ago, while doing a code review at work, I was surprised that Flow had not warned about an unnecessary null check. Last month, TypeScript 5.6 released with validation rules that disallows useless nullish and truthy checks which uncovered nearly 100 existing bugs in the top 800 TypeScript repos on GitHub.

这两个事件是相关的,因为四年前在代码审查中的那一刻促使我编写了 no-constant-binary-expressions 规则,该规则可以捕捉各种各样的错误。示例如下:

🌐 The two events are connected because that moment in code review four years ago led me to write the no-constant-binary-expressions rule which catches a wide variety of bugs. Examples include:

// Expecting empty objects to be falsy
const foo = { ...config } || {};

// Confusing precedence of !
const foo1 = !x == null;

// Confusing ?? or || precedence
const foo2 = x === y ?? true;

no-constant-binary-expression 规则,反过来,帮助激发了新增加的 TypeScript 验证。

🌐 The no-constant-binary-expression rule, in turn, helped inspire the newly added TypeScript validations.

鉴于时间跨度长且中间步骤繁多,我认为回顾我们是如何走到这一步的会很有趣。一次代码审查中的观察是如何滚雪球般演变成对开发者产生重大积极影响的呢?而这个雪球又如何能继续增长呢?

🌐 Given the protracted timeline and the many intermediate steps, I thought it would be interesting to reflect on how we got here. How did this observation in one code review snowball into a significant positive impact to developers? And how could the snowball continue to grow?

要回答这些问题,有助于回顾事件的时间线。

🌐 To answer these questions, it helps to review the timeline of events.

时间线

🌐 Timeline

  • 2020年5月: 我在工作中审查一个将可空值变为非空值的拉取请求。我注意到作者留下了一个 if 条件,用于处理现在已经不可能出现的值为 null 的情况。我开始想,为什么 Flow 没有自动指出这一点。
  • 2020年5月: 我在一个内部群组发帖,向 Flow 团队询问相关情况。答案是,Flow 像 TypeScript 一样,并不完全安全。例如 arr[x] 被标记为不可为空类型,但在运行时实际上可能是 undefined。Flow 过去实现过这些检查,但这导致了严重问题,因为它会告诉人们他们的空值检查可以安全移除,而实际上并非如此,因此他们移除了这些检查。Brad Zacher 偶然看到了这条帖子,并发表了一个观点:即使在功能上不如类型方法强大,基于语法的方法也可能是安全的。
  • 2020年8月: 我将语法验证方法作为内部 ESLint 规则实现,并意识到它不仅限于空检查,而是可以推广到所有常量比较。我在 Meta 的单一代码库中运行它,发现它识别了数百个现有的 bug。
  • 2020年10月: Brad Zacher 建议我把它提议为 ESLint 的一条新核心规则。 我做了,他们也 喜欢这个想法
  • 2021年11月 - 2022年4月:重写了开源规则,这花费了意外的精力,因为在JSX和风格等问题上存在不同的观点。
  • 2022年7月: 我决定写一篇关于新规则的博客文章。ESLint 团队当时正在重做他们的网站,并且在寻找更多的博客内容,所以他们问我是否想在官方博客上写这篇文章。
  • 2023年11月: 这篇博客文章登上了 Hacker News 的首页。这篇文章很可能是某个看到我发布的 推文 的人分享的,我在推文中提到,从 ESLint v9.0.0 开始,这个功能将默认启用在 eslint:recommended 中。
  • 2024年4月: 该规则终于在 ESLint v9.0.0 中默认启用。由于在 eslint:recommended 中启用新规则是一个破坏性更改,因此这一变化不得不等待新主版本。
  • 2024年7月: TypeScript 团队希望在检查特定类型的常量条件的想法上进行扩展,他们找到了这篇博客文章和代码。他们扩展了执行的验证集,包括 ESLint 规则执行的绝大多数检查。他们得到了类似的结果。在前800个 TS 仓库中发现94个真实错误。ESLint 规则的成功让他们有信心在 tsc 中默认启用该检查。
  • 2024年9月: TypeScript 5.6 随附默认验证,以禁止对常量进行 nullish 和 truthy 检查。

反思

🌐 Reflections

有若干关键因素促成了这一在代码审查中小小的观察,帮助推动了整个生态系统的显著改进:

🌐 There were a number of key factors that contributed to this small observation in code review helping spur a meaningful ecosystem wide improvement:

  • Meta的内部文化让我这个普通工程师能够直接与Flow团队的成员进行对话。那次对话发生的场合,ESLint专家Brad Zacher正好可能路过并发表意见。
  • Meta 的单一代码库让我可以直接访问一个庞大的代码库,这让我能够轻松运行规则的早期草稿,以评估验证对大量真实代码的影响有多大。
  • Meta 的自主文化让我有自由去承担写这条规则的额外任务,尽管这并不是我团队的职责范围。
  • ESLint 的插件化架构使我能够编写自己的规则,并轻松地在整个公司范围内部署,而无需说服任何守门人。
  • ESLint 团队对新贡献者添加新的核心规则持开放态度并积极支持,尽管根据2020 政策只有当新规则涉及新的语言特性时才会被接受。
  • 关于我发起的工作以及 ESLint 团队通过博客文章和推文形式宣传的工作的积极沟通。这些使 TypeScript 团队能够将他们收到的关于禁止 if (/regex/) 的更具体请求,与检测恒定条件的更广泛想法联系起来。
  • 不仅仅满足于在代码审查中指出一个没用的空检查,当我怀疑对那类问题有一个根本的解决方案时。我有些执着,直到这个解决方案不仅对我、我的团队或我的公司可用,而且对更广泛的生态系统也可用,我才感到满意。

接下来是什么

🌐 What’s Next

从布拉德在那个内部帖子上的最初观察起,到这一点已经过去了四年,但我认为这里的想法有可能引起更广泛的共鸣:

🌐 It’s taken four years for the ripple of Brad’s initial observation on that internal post to reach this point, but I think the ideas here have the potential to resonate even further:

  • TypeScript 和 Flow 可以在内部跟踪它们恰好知道是正确的类型,并基于这些数据机会性地报告错误,从而允许在更多情况下执行这样的检查。
  • 其他以前出于与 Flow 相同的原因而避免报告不必要检查的不安全语言,也可以使用这种方法来捕捉逻辑错误。
  • 更广泛地说,死代码消除是编译器设计中一个被充分理解的字段,拥有许多成文的技术和方法。然而,它们几乎总是作为编译器后端的优化来应用。我怀疑许多这些相同的死代码消除技术也可以被应用到编译器前端,用于检测和报告错误。

结论

🌐 Conclusion

这一努力跨越了多年和多个组织。

🌐 This effort spanned multiple years and multiple organizations.

作为Meta(一家盈利公司)内的一名有积极性的个人,我能够引发一次对话,从而产生一个想法。然后,我能够利用ESLint来在内部验证这个想法。一旦验证通过,ESLint项目(一个非盈利组织)通过其广泛的采用、推荐的规则集和博客,将这个想法带给了更大的受众。在想法被大规模记录和验证之后,微软(另一家盈利公司)的工程师们能够通过他们的开源项目TypeScript将这些验证成果带给更大的受众。

🌐 As a motivated individual within Meta, a for-profit company, I was able to spark a conversation which spawned an idea. I was then able to leverage ESLint to validate that idea internally. Once validated, the ESLint project, a not-for-profit organization, brought the idea to a large audience via it’s broad adoption, recommended rule set, and blog. With the idea documented and validated at scale, engineers at Microsoft, another for-profit company, were able to bring the validations to an even larger audience via their open source project TypeScript.

每个组织根据其优势和地位扮演了不同但关键的角色。我认为贯穿这一过程的一个共同线索,使这种规模的合作成为可能,是思想的积极社交化。从在代码审查中提问,到向本地专家提问,再到在公开博客文章和推文中分享想法,每一个社交化的圈子都改善了这个想法,并最终帮助它传达到今天所达到的广泛受众。

🌐 Each organization played a different, but key, role according to its strengths and position. I believe a common thread throughout this process, which enabled this scale of collaboration, was the active socialization of ideas. From asking questions during code review, to asking questions of local experts, to sharing ideas in public blog posts and tweets, each expanding circle of socialization improved the idea and ultimately helped bring it to the broad audience it has reached today.


感谢 Brad Zacher 最初的关键观察和持续的鼓励,感谢 Nicholas C. ZakasMilos Djermanovic 在代码审查过程中对规则的重大贡献,以及感谢 Ryan Cavanaugh 将这些相同类型的验证引入 TypeScript 生态系统。

🌐 Thanks to Brad Zacher for his initial key observation and ongoing encouragement, to Nicholas C. Zakas and Milos Djermanovic for significant contributions to the rule during code review, and to Ryan Cavanaugh for bringing these same types of validation to the TypeScript ecosystem.

最新的 ESLint 新闻、案例研究、教程和资源。

ESLint v10.3.0 发布
1 min read

ESLint v10.3.0 发布

我们刚刚发布了 ESLint v10.3.0,这是 ESLint 的一次小版本升级。此版本添加了一些新功能,并修复了上一版本中发现的几个错误。

ESLint v10.2.1 发布
1 min read

ESLint v10.2.1 发布

我们刚刚发布了 ESLint v10.2.1,这是 ESLint 的一个补丁版本升级。本次发布修复了上一版本中发现的几个错误。

ESLint v10.2.0 发布
2 min read

ESLint v10.2.0 发布

我们刚刚发布了 ESLint v10.2.0,这是 ESLint 的一次小版本升级。此版本添加了一些新功能,并修复了上一版本中发现的几个错误。