Swift Tips 009 - Using the guard statement in many different scopes

每天了解一点不一样的 Swift 小知识

代码截图

小笔记

今天代码截图里想表达什么

对于今天的代码而言,John Sundell 向我们展示了 guard 关键字的不同使用场景,在代码中,他利用 guard 完成了跳循环(continue),退循环(break),退函数(return),抛异常(throw),杀进程(exit(1))的操作。

这里虽然 guard !xxx 的写法也解决了问题,但个人感觉在这里 if-else 的写法会更贴近人类的的直觉,读起来也会更通顺。

关于这点,我们已经无法考证 Joho Sundell 写下这段代码的原始想法,不过这里我们只需知道 guard 还有这些用法就好。

guard 是什么

看完了 John Sundell 的酷炫用法后,我们还是回到 guard 关键字上,它是 Swift 2 之后提出的新特性,它使用场景主要有三个

  1. 突出代码主逻辑,避免过多的 {} 嵌套
  2. 通过 gurad 关键字进行可选值解包
  3. 对不期望的情况早做检查,使代码的可读性和维护性提升。

例如下面的代码是用传统的 if-else 方式编写的

func createPersonNoGuard() -> Person? {
if let age = age, let name = name, name.characters.count > 0 && age.characters.count > 0 {
if let ageFormatted = Int(age) {
return Person(name: name, age: ageFormatted)
} else {
return nil
}
} else {
return nil
}
}

而这段代码是利用 guard 改造后的代码。

func createPerson() throws -> Person {
guard let age = age, let name = name, name.characters.count > 0 && age.characters.count > 0 else {
throw InputError.InputMissing
}
guard let ageFormatted = Int(age) else {
throw InputError.AgeIncorrect
}
return Person(name: name, age: ageFormatted)
}

关于 guard 的一些最佳实践欢迎阅读以下 2 篇文章

关于管理逻辑流分叉的探讨

上一节提到了 guard 能够帮助开发者突出代码主逻辑,避免过多的 {} 嵌套,我们不妨举个例子,下面这段代码是我们实际开发中可能遇到的场景:

func foo1(_ a:Bool, _ b:Bool, _ c:Bool, _ d:Bool) {
if(a&&b){
if c {
if d {
// do something
}
}
}
// do something
}

不知道你看到这样的层级嵌套时,有什么想法,我确实已经头大了,所以在没有 guard 之前,有人尝试用这种方式来提升代码的可读性:

func foo2(_ a:Bool, _ b:Bool, _ c:Bool, _ d:Bool) {
while (true) {
if !(a && b) {
break
}
if !c {
break
}
if !d {
break
}
// do something
break
}
// do something
}

这里我们将嵌套的层级变得更加扁平化,从逻辑梳理的角度上来看是变得更加易读了,但是 while(true) 的写法总是有点别扭,不过不要捉急,Swift 有嵌套函数,我们还可以优化:

func foo3(_ a:Bool, _ b:Bool, _ c:Bool, _ d:Bool) {
func should(){
if !(a && b){
return
}
if !c {
return
}
// do something
}
should()
// do something
}

即使平级的 if else 和嵌套的 if else 能改善一些体验,但都不如 guard 优雅,不是么?

这里需要多说两句的是,在一些语言中,例如 Ruby,有 unless 语句,本质上是 if 的相反情况(reverse if),即作用域内的代码只有在传递进来的条件被判断为 false 的时候执行。

Swift 中的 guard,虽然有一些类似,但是它们是不同的东西。guard 不是通常意义上的分支语义。它特别强调,在某些期望的条件不满足时,提前退出。

虽然在一些情况下,你可以将 guard 强行掰弯,当做 reverse if 来使用,但是,这可能会背离 guard 的初衷!使用 if..else 语句或者考虑将代码分割成多个函数。