避免 else 语句会让你成为更优秀的 Go 程序员

Go

在过去的几年我是专业的程序员。 在这期间,我在团队中得到了迅速的提升。 我以一个实习生开始,但现在我是一套产品的首席工程师,这些产品服务的人数多于16万,他们来自于140多个不同的国家。

最近,我回顾了这些年来我编写的所有代码(我仍然可以访问这些代码)。我用各种语言编写过产品代码,包括Haskell、Scala、Go、Python、Java或Javascript。在编写过程中,我注意到一个重要的趋势:我几乎从不使用ELSE语句。

我意识到我厌恶ELSE语句背后有一个明确的理由。我认为它们不应该被使用,而应该被视为一种代码异味。我认为有两个理由: ELSE语句违反了视线规则,而且它们总是缺乏上下文。在展示如何避免使用ELSE语句之前,我将详细解释这两点。

视线规则

我坚信未来的代码会更注重易读性,而不是执行效率。对此,我十分认同 Donald Knuth 的观点:

「程序最重要的是易读性,而不是执行效率」 - Donald Knuth, 《计算机编程艺术》.

问题在于理解代码是一种主观能力:很难明确什么样的代码更通俗易懂。一个公认的规则是视线规则,一个在 Go 社区受欢迎的规则。Mat Ryer 在他的谈话和文章中表达了对该规则的推崇。简单的说,我们应该尽可能缩小代码中的「快乐路径」。
Ps:快乐路径代指代码上下文关联的长度。

Happy path

相反,任何错误处理或特殊情况代码都应进一步缩进。

Special case

任何遵循这一点的代码都有一个独特的属性:扫描缩进最少的代码就足以理解任何代码段在做什么。扫描缩进更多的代码会显示所有可能发生的特殊情况和错误。这使得它非常容易一眼就能理解。

那么其他语句与此有什么关系呢?

Else 语句是有问题的,因为它们迫使代码向下缩进一级。我们突然不清楚什么代码与「快乐路径」相关,以及什么是特例。

Unclear

这种不清晰的情况使代码更难扫描,并且影响了可读性。

缺乏上下文

快速有效地扫描代码的能力非常重要。孤立地消化代码的小部分是其中的一个关键部分。我们不希望总是必须阅读每一行代码才能理解代码库的一小部分。

Else 语句将 if 条件和受其影响的代码隔开,从而使这一点变得更加困难。这最好通过两个例子来解释。首先,你能告诉我当这三行代码运行时会发生什么吗?

if myVariable == nil { 
    return “”
}

希望这种写法是相当明显的。让我们举一个相反的例子:

} else { 
    return “”
}

我们可以看到没有 if 语句,我们无法确定这是什么意思。为什么它会返回一个空字符串?这是一个错误,还是「正常」行为?相反,这段代码依赖于我们记住并阅读了前面的上下文。当语句很小时,这并不重要,但是如果在 if{…} 块中有复杂的逻辑,或者我们正在快速扫描,那么上下文与代码的分离会极大地损害可读性。如果 if/else 语句是嵌套的,或者一个函数中有多个 if/else 语句( 这是哪个 if 语句?)。

如何去掉 else 语句?

现在我们一致认为 else 语句是垃圾。但这并没有什么用。真正的诀窍是如何避开他们。值得庆幸的是,有如下两种简单的方法:

  • 反转 if 条件,尽早返回,
  • 创建辅助函数。

反转条件

这是我遇到的最常见的例子。它也可以有两种形式,一种 else 是隐式的,另一种是显式的。显式版本如下所示:

func doSomething() error {
  if something.OK() {
    err := something.Do()
    if err != nil {
      return err
    }
  } else {
    return nil, errors.New("something isn't ok")
  }
}

隐式类似,但不包含 else 语句本身。相反,else 是通过简单地删除函数的结尾来表示的(这个在 Python 或 JavaScript 中更常见,其中 Noneundefined 如果没有明确说明,则返回)。

function doSomething() {
  if (something.OK()) {
    return something.Do()
  }
}

同样,这并不能让我们了解代码的全部行为。如果没有读取整个函数,则返回值尚不清楚。

通过简单地反转 if 条件,我们可以解决所有这些问题。

function doSomething() {
  if (!something.OK()) {
    // return or throw error
  }
  return something.Do()
}

我们现在可以扫描此函数,清楚地看到缩进错误条件和正常流程,满足视线规则。行为是完全明确的,我们没有上下文分离。这更好。

辅助函数

我们还得到了不会直接导致 return 的 else 语句。这通常是通过一些未正确隔离的特殊情况逻辑实现的。例如

  let charities
  if (country != "") {
    if (tier != "") {
      charities = getCharitiesByCampaignCountryAndTier(campaign, country, tier)
    } else {
      charities = getCharitiesByCampaignAndCountry(campaign, country)
    }
  } else {
    charities = getCharitiesByCampaign(campaign)
  }

  // do something with charities

通过将 charity 获取逻辑写到自己的方法中,可以改善这块的可读性。这将让特殊情况适当处理,并提前返回。通过反转一些 if 语句,可以进一步提高这一点。

例如:

function getCharities(campaign, country, tier) {
  if (country == "") {
    return getCharitiesByCampaign(campaign)
  }

  if (tier == "") {
    return getCharitiesByCampaignAndCountry(campaign, country)
  }

  return getCharitiesByCampaignCountryAndTier(campaign, country, tier)
}

这个辅助函数巧妙的封装了我们需要的所有逻辑,删除了对任何其他语句的需要,并且在保持快乐路径方面做的更好。这更容易浏览,因此可读性更高。

结论

Else 语句的代码味道很奇怪。它们通过强制相等级别的缩进来处理错误和获得满意的路径,从而损害了任何代码的可读性。它们还具有将代码与影响代码的逻辑分离的独特能力。通过提前返回和将逻辑拆分为辅助函数这两种技术,它们很容易避免。因此,它们是不必要的。你可以通过不使用它们来编写更好的代码,成为更好的程序员。

一些注意事项 (以阻止学究)。

  • 在 SQL 中,当 ...ELSE... 不是真的可以避免时。
  • 在 Scala 中,隐式返回(避免使用 return 语句以实现引用透明性)意味着您必须使用它们-您真的没有「提前返回」的能力。
  • 三元运算符很好。
  • 在 Python 中, 三元运算符使用 else 。这也很好。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://dev.to/dglsparsons/write-better-...

译文地址:https://learnku.com/go/t/55494

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!