三元运算符的辩护

Avatar of Burke Holland
Burke Holland

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

几个月前,我在 Hacker News 上(就像大家一样),偶然发现了一篇(现在已删除)关于不使用if语句的文章。如果您不熟悉这个概念(就像我以前一样),您将获得真正的享受。只需在 Hacker News 上搜索“if 语句”。您将看到建议您可能不需要它们的文章,将它们称为代码异味的文章,甚至还有经典的“被认为是有害的”文章。听着,当人们开始建议使用某个编程概念实际上会伤害某人时,你就知道这个概念是名副其实的了。

如果您觉得这还不够,总有“反If运动”。加入后,您将获得一个漂亮的横幅以及网站上的您的姓名。如果您加入的话。哦,多么甜蜜的讽刺。

我第一次遇到这种奇怪的“if 厌恶”现象时,觉得很有趣,但可能只是互联网上更多的人在生气。您只需搜索一次 Google,就能找到一个对任何事情都生气的人。比如这个讨厌小猫的人。小猫。

一段时间后,我观看了Linus Torvald 的 TED 访谈。在该访谈中,他展示了两张幻灯片。第一张幻灯片包含他认为“品味不佳”的代码。

第二张幻灯片是相同的代码,但按照 Linus 的说法,是“品味良好”的。

我意识到 Linus 是一位有点两极分化的人物,您可能不同意“品味良好”与“品味不佳”的说法。但我认为我们可以普遍认同第二张幻灯片更容易让人接受。它简洁明了,逻辑路径更少,并且不包含if语句。我希望我的代码看起来像那样。它不必是什么天才算法(它永远不会是),但我认为它可以简洁,并记住 Smashing Pumpkins 乐队的 Billy Corgan 关于简洁的说法……

简洁就是神圣。而神是空虚的。就像我一样。

– Billy Corgan,“Zero”

太黑暗了!但这是一张很棒的专辑

除了使您的代码看起来杂乱无章之外,if语句或“分支逻辑”还需要您的大脑同时保存和评估两条不同的路径,以及这些路径上可能发生的所有事情。如果您嵌套if语句,则问题会加剧,因为您正在创建和跟踪决策树,而您的大脑必须像喝醉的猴子一样在整个树上跳来跳去。这类事情是导致代码难以阅读的原因。请记住,您应该在编写代码时考虑到接手您代码的傻瓜,他们将不得不维护它。而那个傻瓜很可能就是您自己。

作为我自己最喜欢的傻瓜,我最近一直在有意识地避免在我的 JavaScript 中编写if语句。我并不总是能成功,但我注意到,至少,它迫使我从完全不同的角度思考解决问题的方法。这让我成为一名更好的开发人员,因为它迫使我参与我大脑的另一部分,否则它会坐在豆袋上吃花生 M&M's,而if语句则完成所有工作。

编写if语句的过程中,我发现了自己对 JavaScript 使用三元运算符和逻辑运算符来编写条件逻辑的方式的喜爱。我现在想向您建议的是,三元运算符的声誉不佳,您可以将其与&&||运算符一起使用来编写一些非常简洁易读的代码。

备受争议的三元运算符

当我刚开始编程时,人们常说:“永远不要使用三元运算符。它们太复杂了。”所以我没有使用它们。从来没有。我从未使用过三元运算符。我甚至从未想过质疑这些人是否正确。

我认为他们错了。

三元运算符只是一行if语句。建议它们在任何形式上都隐式地过于复杂,这仅仅是……不正确。我的意思是,我不是盒子里最愚蠢的甜甜圈,但我完全理解一个简单的三元运算符。当我们说始终避免使用它们时,我们是否只是在有点儿宠坏自己?我认为结构良好的三元运算符每次都胜过if语句。

让我们举一个简单的例子。假设我们有一个应用程序,我们希望测试用户是否已登录。如果他们已登录,则将其发送到他们的个人资料页面。否则,将其发送到主页。以下是执行此操作的标准if语句……

if (isLogggedIn) {
  navigateTo('profile');
}
else {
  navigateTo('unauthorized');
}

这是一个非常简单的操作,却需要六行代码来完成。六行。请记住,每次遍历一行代码时,您都必须记住上面出现的代码以及它如何影响下面的代码。

现在是三元运算符版本……

isLoggedIn ? navigateTo('profile') : navigateTo('unauthorized');

您的大脑只需要评估一行代码,而不是六行。您不必在各行之间移动,记住前一行是什么。

不过,三元运算符的缺点之一是您只能评估一个条件。从前面的示例出发,如果您希望在用户已登录时导航到个人资料页面,但在未登录时不执行任何操作,则此方法无效……

// !! Doesn't Compile !!
logggedIn ? navigateTo('profile')

您将不得不在这里写出一个实际的if语句。或者您会吗?

当您只想评估条件的一侧并且不想使用if语句时,您可以在 JavaScript 中使用一个技巧。您可以通过利用 JavaScript 使用||(或)和&&(和)运算符的方式来实现此目的。

loggedIn && navigateTo('profile');

它是如何工作的!?

我们在这里所做的是询问 JavaScript:“这两件事都成立吗?”如果第一项为假,则 JavaScript 虚拟机没有理由执行第二项。我们已经知道它们两者都不成立,因为其中一项为假。我们利用了这样一个事实,即如果第一项为假,JavaScript 将不会费心评估第二项。这相当于说:“如果第一个条件为真,则执行第二个条件。”

现在,如果我们想反过来呢?如果我们只想在用户登录时导航到个人资料页面呢?您可以在loggedIn变量前面加一个!,但还有另一种方法。

loggedIn || navigateTo('profile');

这句话的意思是:“这两件事中任何一项都成立吗?”如果第一个为假,则它必须评估第二个以确保。但是,如果第一个为真,则它永远不会执行第二个,因为它已经知道其中一个为真;因此整个语句为真。

那么,这比只执行以下操作更好吗?

if (!loggedIn) navigateTo('profile');

不。以那种形式,它不是。但关键在于:一旦您知道可以使用&&||运算符在if语句之外评估相等性,就可以使用它们来极大地简化代码。

这是一个更复杂的示例。假设我们有一个登录函数,我们向其中传递一个用户对象。该对象可能为 null,因此我们需要检查本地存储以查看用户是否在其中保存了会话。如果他们有,并且他们是管理员用户,则将其定向到仪表板。否则,将其发送到一个页面,告诉他们未经授权。以下是作为直接的if语句的外观。

function login(user) {
  if (!user) {
    user = getFromLocalStorage('user');
  }
  if (user) {
    if (user.loggedIn && user.isAdmin) {
      navigateTo('dashboard');
    }
    else {
      navigateTo('unauthorized');
    }
  }
  else {
    navigateTo('unauthorized');
  }
}

哎呦。这很复杂,因为我们正在对user对象进行大量的 null 条件检查。我不希望这篇文章过于稻草人论证,因此让我们简化一下,因为这里有很多冗余代码,我们可能会将其重构为其他函数。

function checkUser(user) {
  if (!user) {
    user = getFromLocalStorage('user');
  }
  return user;
}

function checkAdmin(user) {
  if (user.isLoggedIn && user.isAdmin) {
    navigateTo('dashboard');
  }
  else {
    navigateTo('unauthorized');
  }
}

function login(user) {
  if (checkUser(user)) {
    checkAdmin(user);
  }
  else {
    navigateTo('unauthorized');
  }
}

主要的登录函数更简单,但这实际上是更多代码,并且在您考虑整体而不是仅仅login函数时,不一定是“更简洁”的。

我想建议,如果我们放弃if语句,拥抱三元运算符,并使用逻辑运算符来确定相等性,我们可以在两行代码中完成所有这些操作。

function login(user) {
  user = user || getFromLocalStorage('user');
  user && (user.loggedIn && user.isAdmin) ? navigateTo('dashboard') : navigateTo('unauthorized')
}

就是这样。if语句产生的一切噪音都压缩成两行。如果第二行对您来说有点长且难以阅读,请将其包装起来,以便条件位于它们自己的行上。

function login(user) {
  user = user || getFromLocalStorage("user");
  user && (user.loggedIn && user.isAdmin)
    ? navigateTo("dashboard")
    : navigateTo("unauthorized");
}

如果您担心下一个人可能不知道&&||运算符如何在 JavaScript 中工作,请添加一些注释、一些空白和一棵快乐的树。释放你内心的鲍勃·罗斯。

function login(user) {
  // if the user is null, check local storage to
  // see if there is a saved user object there
  user = user || getFromLocalStorage("user");
  
  // Make sure the user is not null, and is also
  // both logged in and an admin. Otherwise, DENIED. 🌲
  user && (user.loggedIn && user.isAdmin)
    ? navigateTo("dashboard")
    : navigateTo("unauthorized");
}

其他您可以做的事情

趁此机会,以下是一些您可以在 JavaScript 条件语句中使用的其他技巧。

赋值

我最喜欢的技巧之一(我在上面使用过),是用一行代码检查项目是否为 null,然后在为 null 时重新赋值。您可以使用||运算符来实现此目的。

user = user || getFromLocalStorage('user');

您可以像这样一直进行下去……

user = user || getFromLocalStorage('user') || await getFromDatabase('user') || new User();

这也适用于三元运算符……

user = user ? getFromLocalStorage('user') : new User();

多个条件

你可以为三元运算符提供多个条件。例如,如果我们想要记录用户已登录然后进行导航,我们可以在不需要将所有这些抽象到另一个函数的情况下做到这一点。将其用括号括起来并用逗号分隔。

isLoggedIn ? (log('Logged In'), navigateTo('dashboard')) : navigateTo('unauthorized');

这对于你的&&||运算符也适用……

isLoggedIn && (log('Logged In'), navigateTo('dashboard'));

嵌套三元表达式

你可以嵌套你的三元表达式。在他的关于三元运算符的精彩文章中,Eric Elliot 通过以下示例演示了这一点……

const withTernary = ({
  conditionA, conditionB
}) => (
  (!conditionA)
    ? valueC
    : (conditionB)
    ? valueA
    : valueB
);

Eric 在那里做的最有趣的事情是取反第一个条件,这样你就不会以问号和冒号放在一起结束,这使得它更难阅读。我会更进一步,添加一些缩进。我还添加了花括号和显式返回,因为看到一个括号然后立即出现另一个括号会让我的大脑开始预料一个永远不会出现的函数调用。

const withTernary = ({ conditionA, conditionB }) => {
  return (
    (!conditionA)
      ? valueC  
      : (conditionB)
        ? valueA
        : valueB
  )
}

作为一个一般规则,我认为你应该考虑不要嵌套三元表达式或if语句。Hacker News 上的任何上述文章都会让你得出同样的结论。虽然我并不是在这里羞辱你,只是建议你也许(可能)以后会感谢自己没有这样做。


这就是我对被误解的三元运算符和逻辑运算符的看法。我认为它们可以帮助你编写干净、可读的代码,并完全避免使用if语句。现在,如果我们能让 Linus Torvalds 批准所有这些都是“好品味”就好了。我就可以提前退休,安享余生。