[译] 我并不讨厌箭头函数

877 阅读18分钟

我并不讨厌箭头函数

文章篇幅较长,可以直接看下面的总结

箭头函数在某些场景下表现很好,但是在很多情况下,仍然有可能降低代码的可读性,因此要谨慎使用。

尽管箭头函数显然已经在社区中被普遍接受(虽然并非一致支持),但事实证明,对于如何用好 =>,大家有各种各样的看法。

可配置的 lint 规则是解决箭头函数的多样性和分歧的最佳解决方案。

我发布了带有一些可配置规则的 proper-arrows ESLint 插件,用于控制代码库中的 => 箭头函数。

观点总是见仁见智的

任何关注我(包括推特、书籍、课程等)很久的人,都知道我总是有很多观点。事实上,这是我唯一擅长的事情(这也是我自己的观点),而且从来不会对它们感到困惑。

我不赞同「强观点,弱坚持」 (译注:原文 strong opinions, loosely held,大意为当存在若干现有事实时,要自信而强硬地表达观点;而遇到更强有力的论据时,则要懂得妥协让步) 这种信条。我不会「松散地持有」我的观点,因为没有足够理由支持的观点本来就没有任何意义。我花了很多时间研究、修改、写作、尝试各种想法,然后才形成一个可以公开分享的观点。在这一点上,我的观点是非常坚定的,这是必然的。

更重要的是,我是基于这些观点去教授来自世界各地不同公司的数千名开发人员的 —— 这让我有机会通过无数的讨论和辩论来深入审视我的观点。能身居教学之位我深感荣幸。

这并不意味着我不能或不会改变我的观点。事实上,我曾经最坚定的观点之一 —— 「JS 类型及强制类型转换在 JS 中很有用」最近已经发生了很大程度的变化。我对 JS 类型及类型检测工具为何有用有了更全面和深入的认识。甚至我对 => 箭头函数(本文的主题)的看法也在不断发展和深化。

但是很多人告诉我他们欣赏我的一点是,我不仅陈述观点,还用严密的、深思熟虑的推理来支持这些观点。即使当人们强烈反对我的观点时,他们通常也会称赞我至少拥有那些有理有据的观点。

我试图通过我的演讲、教学和写作来给其他人同样的灵感。我不在乎你是否同意我的观点,我只在乎你能知道自己为什么会在技术上持有这么一个观点,并且可以用你自己的推理来认真地捍卫它。对我来说,这是一种与技术「和谐共处」的方式。

=> 箭头函数 != function

我真心觉得 => 箭头函数并不适合替换 JS 代码中所有(或者至少说大多数)的 function 函数代码。我发现在大多数情况下,箭头函数并没有让代码更易读。并非只有我这样想,每当我在社交媒体上分享类似的观点时,我经常会收到几十条「我也是!」的回应,只掺杂着几条「你完全错了」的回应

但是我并不是想在这里完整地讨论 => 箭头函数。关于它,我已经写了很多篇文章来表达观点,包括我书中的以下部分:

无论对 => 的偏好如何,把其仅仅视为是一个更好的 function 的想法,都有些过于简略了。除了一对一的关系,还有很多细微的差异值得讨论。

关于 => 我还是有一些喜爱的,你可能会很惊讶,因为大多数人认为我讨厌箭头函数。

我并不讨厌它,我认为箭头函数有一些显而易见且重要的优点。

只是我并不完全地把它们视为颠覆式的 function,现如今,大多数人都不在意中间派别细微的意见,所以因为我没有站在支持 => 的阵营,我就站在了反对的阵营。但其实不是这样的

我讨厌的是暗示箭头函数普遍更具可读性,或者,客观来说他们在各种情况下都更好的这种行为。

我拒绝这一立场的原因是,在许多情况下,我阅读这些代码都很吃力。所以那种观点只会让作为开发人员的我感到愚蠢和自卑 ——「这段代码并没有非常易读,肯定是我有什么问题,为什么我这么菜?」而且,我并不是唯一一个被这种绝对观点严重煽动的冒名顶替者综合症患者。

最扯淡的是,人们告诉你,「你不了解或不喜欢 => 的唯一原因是你没有充分地学习和使用它们」。行吧,谢谢(你屈尊的)提醒,我知道这是因为我的无知和经验不足了。但其实我内心只想说呵呵。我已经编写并阅读了成千上万个 => 函数。我对它们足够了解,有资格发表意见。

我没站在支持 => 的阵营中,但我承认有些人确实喜欢它们,这是合理的。有些人从使用 => 的语言转到 JS,所以他们能非常自然地感知和阅读。有些人还喜欢它们与数学符号的相似性。

在我看来,有问题的是,某个阵营中的一些人对不同的意见根本无法做到理解或者产生共鸣,就好像提出异议者一定是有什么东西做得不对

好的代码书写体验 != 可读性

我也认为你们在讨论代码可读性时其实不知道自己在说什么。总的来说,当你把大多数关于代码可读性的观点分解的时候,它们其实都是基于个人对书写简洁代码的偏好的看法。

在关于代码可读性的争论中,当我提出反驳时,有些人只是固执己见,拒绝支持别人的观点。另一些人则会用「可读性只是主观的」来搪塞我的反驳。

这种回答之脆弱令人震惊:两秒钟前,他们还在激烈地宣称 => 箭头绝对地、客观地更具有可读性,然后当被追问时,他们承认,「行吧,个人认为它更具有可读性,即使像你这样的无知之人不这么认为。」

你猜怎么着?可读性主观的,但并不完全如此。这是一个非常复杂的话题。也有一些人开始正式研究代码可读性的话题,试图找出哪些部分是客观的,哪些部分是主观的。

我读过很多这样的研究,因此我确信这是一个足够复杂的话题,以至于它没法被简化成 T 恤上的 slogan。如果你想了解详情,我建议你自己去谷歌一下。

虽然我无法完整地回答所有关于可读性的问题,但有一件事我可以肯定的是,代码更多的时候是被阅读而不是被写出来的,所以从「写代码更容易/更快」这个论据出发的论点是站不住脚的。需要考虑的不是你节省了多少写代码的时间,而是读者(未来的你或团队中的其他人)能够多清楚地理解。理想情况下,他们能够在不仔细梳理代码的情况下大致理解代码吗?

任何试图证明写代码容易就有利于代码可读性的说法都是站不住脚的,总的来说,这只是在混淆视听。

因此,我坚决反对 => 总是客观地「更具可读性」。

但我还是不讨厌箭头函数。我只是认为如果要有效地利用它们,我们需要更加自律。

Linter == 准则

您可能(错误地)相信,linter 会告诉您有关代码的客观事实。其实它们可以做到这一点,但这不是其主要目的。

能告诉您代码是否有效的最佳工具是编译器(即 JS 引擎)。而最适合告诉您代码是否「正确」(满足需求)的工具是测试集。

但是最适合告诉您代码是否合适的工具是 linter。根据那些基于观点制订规则的作者的说法,Linter 就是指导你格式化和组织代码的充满主观观点的规则集合,它可以用于避免可能出现的问题。

这就是规则所做的:在你的代码中应用这些观点。

几乎可以肯定的是,这些观点会一次又一次地「冒犯」您。如果您像我们大多数人一样,就会出现「幻想自己做得很好,并且认为您在此代码行上所做的事情是正确的。然后 linter 跳出来,说:『不,不要那样做。』」的场景。

如果有时您的直觉告诉你不要同意 linter 提出的意见,那么您就跟我们其余的人一样了!我们从情感上迷恋自己的观点和能力,并且当某种工具指出我们的错误时,我们就会有点狂躁。

我不会对测试集或 JS 引擎感到生气。这些东西都是关于我的代码的事实。但是当 linter 的观点与我的不同时,我就会非常生气。

我在几周前启用了一个 linter 规则,因为我在重新阅读代码的时候发现有一处让我烦恼的、前后矛盾的地方。但现在,这条 lint 规则每小时会出现两三次,就像 90 年代情景喜剧里典型的老奶奶一样,让我心烦。每一次,我都思考(仅仅是片刻)我是否应该取消这个规则。但最终我让它开着,虽然这令我不爽。

为什么要让我们自己遭受如此痛苦?因为 linter 工具及其观点给我们提供了准则。他们帮助我们更好地与他人协作。

它们最终帮助我们更清晰地表达代码。

我们为什么不让每个开发人员都自己做出决定?因为我们总是倾向于情感依恋。虽然我们写着自己的代码,但面对着不合理的压力和期限,我们很可能会以最不值得信赖的心态进行这些判断。

我们应该听从于帮助我们维护准则的工具。

类似于 TDD 倡导的写业务代码前先写测试代码的原则。当我们仔细分析时,会发现我们最看重的是流程的纪律性和全局效果。在代码无法工作、还找不到原因时,我们就只能胡乱挪一挪代码,来看是否能用。在这种情境下,我们是无法建立这样的过程的。

讲道理,我们还是要承认,当我们制定了合理的指导方针,然后遵守它们的准则时,整体利益会达到最大化。

可配置性为王

如果你有意让自己接受 lint 规则,你(和你的团队,如果有的话)肯定会想要一些发言权 —— 你需要遵守哪些规则。武断和不容置疑的观点是最糟糕的。

还记得 JSLint 么?那里 98% 的规则只是 Crockford 的个人观点,你要么使用这个工具,要么不使用这个工具。他直接在 README 文档中警告你,你会被冒犯,你应该克服它。很有趣,对吧?(有些人可能还在使用 JSLint,但是我认为您应该考虑使用更现代的工具!)

这就是在当代的 linter 工具中,ESLint 为王的原因。其基本思想是,让一切都是可配置的。让开发者和团队从自己的准则和利益出发,自行决定要使用什么样的代码规范。

这不意味着每个开发人员都有自己的规则。规则的目的是使代码符合一个合理的折中方案,即「集中式标准」,这是与团队中大多数开发人员进行最清晰沟通的最佳机会。

但是没有任何规则是 100% 完美的。总会有例外情况。所以,使用内联注释来禁用或覆盖规则这一功能不仅仅是一个小特性,还是一个必要功能。

您不希望开发人员通过配置自己本地的 ESLint 规则,来让提交代码时绕过共识规则。您想要的是开发人员要么遵循已建立的规则(首选!),要么在破例的地方让这个逃离规则的例外一目了然。

理想情况下,在 code review 期间,可以讨论、审查这些特殊标记。也许这是合理的,也许不是。但至少它是显而易见、可以讨论的。

工具的可配置性指的是让工具为我们服务,而不是我们为工具服务。

有些人更喜欢基于约定,而非基于工具,约定的规则是预先确定的,因此没有讨论或辩论。我知道这对一些开发人员和一些团队是有效的,但是我认为这不是一种可以广泛应用、可持续的方法最终,如果一个工具不能灵活地适应不断变化的项目需求、成为开发人员开发过程中重要的一部分,那么它将会变得模糊,并最终被取代。

箭头函数的正当用法

我充分能理解,在这里我使用「正当」这个词会惹怒一些人:「谁有资格说什么是正当的,什么是不正当的?」

记住,我并不是要告诉你什么是正当的。我想让大家接受这样一个观点,即关于 => 箭头函数的各种观点就像它们的语法和用法的所有细微差别一样,最终最正当的是一些观点,不管具体是什么,总归会有一些可应用的观点。

虽然我是 ESLint 的狂热粉丝,但是我对 ESLint 的内置规则无法从各个方面支持 => 箭头函数而感到失望,虽然有规则,但我很失望,它们似乎主要关注于表面的风格细节,例如空白符。

我认为有许多方面会妨碍 => 箭头函数的可读性,这些问题远远超出了现有 ESLint 规则集所能控制的范围。我在推特上问了很多人,似乎很多人对此都有看法。

顶级的 linter 就应该不仅允许您根据自己的喜好配置规则,还可以在缺少某些内容时构造自己的规则。幸运的是,ESLint 完全支持这一点!

因此,我决定开发一个 ESLint 插件来定义一组围绕 => 箭头函数的附加规则:proper-arrows

在解释它之前,我要先指出:它是一组规则,您可以自主决定打开或关闭、配置这些规则。如果你发现哪怕有一个规则的一个细节对您有帮助,使用这个规则/插件都是更好的选择。

我很高兴你对 => 箭头函数有自己的看法。事实上,这才是重点。如果我们都对 => 箭头函数有不同的意见,那么我们应该有工具支持选择和配置这些不同的意见。

这个插件的原理是,对于每个规则,当你打开这个规则时,你会默认打开它的所有报告模式。您可以不打开规则,也可以打开规则,然后根据需要配置它的模式。但我不希望你必须去寻找规则/模式来开启,因为它们的晦涩甚至会阻碍你去考虑它们。所以每个规则都默认开启

唯一的例外是,在默认情况下,所有的规则都忽略了简单的 => 箭头函数,比如 ()=> {}x => x等。如果您想要检查它们,那么您必须在每个规则的基础上使用 { "trivial": true } 选项打开检查。

具体规则

那么具体提供了哪些规则呢?以下是对项目概述的摘录

  • "params":控制 => 箭头函数参数的定义,例如禁止未使用的参数,禁止短/无语义的参数名称等。
  • "name":要求仅在接收到推断名称的位置使用 => 箭头函数(即分配给变量或属性等),以避免匿名函数表达式的不可读/可调试性。
  • "where":限制可以在程序结构中使用 => 箭头函数的位置:禁止在顶级/全局作用域、对象属性、export 语句等地方使用。
  • "return":限制 => 箭头函数的简明返回值类型,例如禁止对象文字简明返回(x => ({ x }))、禁止条件/三元表达式的简明返回(x => x ? y : z)等。
  • "this":要求/禁止 => 箭头函数在 => 箭头函数自身或嵌套 => 箭头函数中使用 this 引用,该规则可以有选择地禁止全局作用域使用带 this=> 箭头函数。

请记住,每个规则都有不同的模式可供配置,所以这些并非全有或全无的。选择你需要的即可。

为了示意 proper-arrows 规则可以检查什么,让我们看看 "return" 规则,特别是它的 "sequence" 模式。这种模式将 => 箭头函数的简洁返回表达式表示为逗号分隔的序列,如下所示:

var myfunc = (x,y) => ( x = 3, y = foo(x + 1), [x,y] );

Sequences 通常用于 => 箭头函数的简洁返回结果中,从而将多个(表达式)语句串在一起,而不需要使用完整的 { .. } 分隔函数体和显式的 return 语句。

有些人可能喜欢这种风格,这没问题!不过还有很多人更倾向于可读性而不是更简洁的代码,他们更喜欢:

var fn2 = (x,y) => { x = 3; y = foo(x + 1); return [x,y]; };

请注意,它仍然是一个 => 箭头函数,其实也并没有多出几个字符。但这样可以更清楚的看到,此函数体中包含三个单独的语句。

更好的做法:

var fn2 = (x,y) => {
   x = 3;
   y = foo(x + 1);
   return [x,y];
};

需要明确的是, proper-arrows 规则不会对琐碎的样式差异进行强制,例如空格/缩进。如果要对这类差异进行统一,可以结合使用其它( ESLint 内置)规则。 proper-arrows 规则专注于有关于 => 箭头函数的更实质性的内容。

简要总结

针对「什么才能造就良好、正确=> 箭头函数的样式」这件事,您和我一定有不不同的意见。这是一件正常不过的事情。

我有两个目标:

  1. 说服您:对这些东西的看法不同是没关系的。
  2. 使您能够使用可配置的工具来制定并推行自己的观点(或团队共识)。

争论基于意见的规则确实没有任何收获。选择您喜欢的,忘记您不喜欢的就够了。

我希望您看一下 proper-arrows,然后看看有没有哪些规则可以为您所用,让您的 => 箭头函数符合您心目中代码的正确形式。

如果这个插件缺少一些有助于定义更多正确箭头的规则,请提出 issue,咱们一起讨论!我们完全有可能添加该规则/模式,尽管我个人并不打算开启该规则!

我不讨厌 => 箭头函数,您也不应该。我只是讨厌无知无纪的争辩。让我们拥抱更智能,可配置性更强的工具,然后转向更重要的主题吧!

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏