避免 else 语句会让你成为更优秀的 Go 程序员
避免 else 语句会让你成为更优秀的 Go 程序员
在过去的几年里,我作为一名专业的程序员,经历了快速的成长。从实习生到负责服务超过16万用户、覆盖140多个国家的产品的首席工程师,我积累了丰富的经验。最近,我回顾了这些年用各种语言(包括Haskell、Scala、Go、Python、Java和JavaScript)编写的代码,发现了一个显著的趋势:我几乎不使用else语句。
视线规则
我坚信未来的代码会更注重易读性,而不是执行效率。对此,我十分认同 Donald Knuth 的观点:
「程序最重要的是易读性,而不是执行效率」 - Donald Knuth, 《计算机编程艺术》.
理解代码是一种主观能力,很难明确什么样的代码更通俗易懂。一个公认的规则是视线规则,一个在 Go 社区受欢迎的规则。Mat Ryer 在他的谈话和文章中表达了对该规则的推崇。简单的说,我们应该尽可能缩小代码中的「快乐路径」。Ps:快乐路径代指代码上下文关联的长度。相反,任何错误处理或特殊情况代码都应进一步缩进。
任何遵循这一点的代码都有一个独特的属性:扫描缩进最少的代码就足以理解任何代码段在做什么。扫描缩进更多的代码会显示所有可能发生的特殊情况和错误。这使得它非常容易一眼就能理解。
那么其他语句与此有什么关系呢?
Else 语句是有问题的,因为它们迫使代码向下缩进一级。我们突然不清楚什么代码与「快乐路径」相关,以及什么是特例。
这种不清晰的情况使代码更难扫描,并且影响了可读性。
缺乏上下文
快速有效地扫描代码的能力非常重要。孤立地消化代码的小部分是其中的一个关键部分。我们不希望总是必须阅读每一行代码才能理解代码库的一小部分。
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 中更常见,其中 None 或 undefined 如果没有明确说明,则返回)。
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 。这也很好。