代码审查中经常会出现这样的意见:“这个改成 guard 吧。”既然用 if 也能完全一样地运行,为什么还要改?这看起来像是偏好问题,但实际上 guard 是 Swift 在语言层面用来推动特定代码风格的语法:提前退出,以及左对齐的正常路径。
这是 Swift 基础系列的第 4 篇。在可选值篇中,我曾简要介绍过 guard let 这一解包工具。这一次我们深入了解 guard 本身:它与 if 有什么不同、编译器能保证什么,以及什么时候应该使用 if 而不是 guard。
如果想重新了解值不存在的状态,建议先阅读 Swift 可选值的本质与解包判断标准。
问题场景 — 毁灭金字塔
先来看 guard 出现之前的代码。假设这是一个注册用户的函数,需要检查很多内容:输入是否为 nil、格式是否正确,以及是否同意条款。
func signUp(email: String?, password: String?, agreed: Bool) {
if let email = email {
if isValidEmail(email) {
if let password = password {
if password.count >= 8 {
if agreed {
// 真正想做的事
createAccount(email, password)
} else {
showError("需要同意条款")
}
} else {
showError("密码太短")
}
} else {
showError("请输入密码")
}
} else {
showError("不是有效的电子邮件格式")
}
} else {
showError("请输入电子邮件")
}
}
缩进有五层。这种形状被称为毁灭金字塔,不仅外观难看,还会带来实际的阅读成本。函数的核心工作(createAccount)被埋在最深处,而每个检查的失败处理(else)在视觉上都离对应条件很远。要回答“密码太短时会怎样?”,必须一边滚动,一边用眼睛匹配括号。
guard 的解法 — 先处理失败,让核心逻辑位于平地
用 guard 重写同一个函数后,会变成这样。
func signUp(email: String?, password: String?, agreed: Bool) {
guard let email, isValidEmail(email) else {
return showError("请检查电子邮件")
}
guard let password, password.count >= 8 else {
return showError("请检查密码 (8 个字符以上)")
}
guard agreed else {
return showError("需要同意条款")
}
createAccount(email, password)
}
结构反转了。每个条件与对应的失败处理被放在一起,通过所有检查的核心逻辑则没有额外缩进,平坦地位于函数底部。读者的视线只需从上到下流动一次。“前置条件,然后是核心逻辑”这一函数的逻辑结构,现在与代码的视觉结构一致了。
这种风格的优势通常被称为让正常路径左对齐。正常流程始终位于 0 级缩进,只有异常情况才会进入代码块。即使函数变长,也会保持“沿着最左边阅读就能看到正常场景”的一致性。
与 if 的真正区别 — 编译器强制的两项保证
接下来可能会有人问:“if 不是也能提前退出吗?”没错,可以写成 if email == nil { return }。但 guard 不只是反转后的 if,它还提供了两项由编译器强制保证的特性。
**第一,else 代码块必须离开作用域。**在 guard 的 else 中,必须使用 return、throw、continue、break 或 fatalError 离开当前作用域,否则就会编译错误。用 if 编写检查时,可能只检查条件却忘记 return;而 guard 会在编译时捕获这个错误。“如果执行通过了这个位置,条件就为真”不再是约定,而是保证。
**第二,解包后的值会在 guard 以下的整个范围内继续有效。**if let 的绑定只在 if 代码块内有效,而 guard let 的绑定可以在 guard 语句之后的整个剩余作用域中使用。通过检查的值可以一直带到函数末尾,因此不会产生“单独解包、单独使用”的别扭感。
这两项保证结合起来后,guard 也会承担文档的作用。函数开头的一组 guard 用来声明“这个函数的前置条件列表”。仅靠函数签名无法表达的契约,可以从函数体的第一行读到。
该使用 if 而不是 guard 的场景
那么,是否应该把所有 if 都改成 guard?不是。区分标准很明确:guard 用于“不是这样就无法继续”的前置条件,if 用于“这种情况这样做,另一种情况那样做”的分支。
// if适合使用 if 的场景 — 两条路径都是正常流程
if user.isPremium {
showPremiumBadge()
} else {
showUpgradeButton()
}
不是 premium 并不是失败。如果两个分支都会继续执行,属于正常场景,就应该使用 if。用 guard 编写反而会传达“不是 premium 就是不正常”的错误信号。
相反,如果检查失败就会结束函数,即使只有一个检查项,也适合使用 guard。语法选择本身就是意义传达。读者看到 guard 时会期待“这是前置条件”,看到 if 时会期待“这是分支”;可读性的本质就是符合这些预期。
还要指出一种反模式:在 guard else 代码块中填入复杂逻辑。如果开始在 else 中尝试恢复、修改状态或执行长时间处理,就破坏了 guard“失败后快速结束”的约定。当 else 超过三行时,最好把它视为重新审视设计的信号。如果失败处理复杂到这种程度,那它就不是前置条件,而是独立的分支,或者应该抛出错误交给调用方处理。
循环与异步代码中的 guard
guard 也可以在函数之外使用。在循环中,它会与 continue 搭配,用来表达“跳过这一项”。
for item in items {
guard item.isValid else { continue }
guard let url = item.downloadURL else { continue }
process(url)
}
过滤条件较多时,guard 能让 for 的主体保持平坦。当然,如果条件很简单,for item in items where item.isValid或 compactMap 可能更加简洁。一个 where 子句就能完成时使用 where;如果混合了解包且条件分为多个阶段,guard 会更方便。
在异步代码中,与闭包部分常见的 [weak self] 组合,实际上已经成为惯用写法。
fetchData { [weak self] data in
guard let self else { return }
self.update(with: data)
}
“如果 self 已经被释放,就什么都不做”这一前置条件在第一行处理,剩余代码则在 self 已获得的平地上继续执行。这正是 guard 的提前退出理念与内存管理相遇的地方。
通过直接运行确认的结果
在 Apple Swift 6.3.3 中,我创建了一个接收 String? 输入的函数,在 guard 的 else 中拒绝 nil;如果有值,则在下一行使用绑定后的字符串。
guard=rejected,accepted:devpaw
这段输出也符合我在代码审查中推荐 guard 的标准。如果输入失败就结束当前路径,只有通过检查的值会在主体中继续使用,那么就应该用 guard。如果真和假两边都是正常的业务流程,就不要仅仅因为看起来更平坦而改成 guard,应当保留 if。
总结
- guard 是在语言层面支持提前退出的语法,它把毁灭金字塔翻转为“列出前置条件 + 平坦的核心逻辑”结构。
- 它与 if 的区别在于编译器保证:else 必须离开作用域,绑定会在后续整个作用域中保持有效。
- 选择标准:失败后无法继续的前置条件用 guard,两边都是正常流程的分支用 if。语法选择本身就是意义传达。
- else 代码块变长,说明 guard 可能被错误使用。如果失败处理很复杂,就应该用分支或抛出错误来解决。
最后一句提到了“抛出错误”。下一篇正是这个主题:throws、do-catch 和 try 的三种变体,以及 Result,完整梳理 Swift 错误处理的地图。
延伸阅读
- Swift 装饰器模式:无需继承即可层层叠加功能的方法(示例与实战完整总结)
- [Swift 基础 #5] Swift 错误处理完整地图:throws、try?、try!、Result 什么时候该用哪个?
- [Swift 基础 #6] Swift 字符串为什么不能使用 text[0]?Grapheme Cluster(字素簇)完整总结
来源与验证
- The Swift Programming Language: Control FlowSwift.org · 官方文档 · 核查 2026年8月26日依据: guard 的 early exit、可选绑定范围与可读性目标
- The Swift Programming Language: StatementsSwift.org · 标准或规范 · 核查 2026年8月26日依据: guard else 必须通过 return、break、continue、throw 或 Never 转移控制权的语法规则

![[Swift 基础 #4] 正确使用 Swift guard:用提前退出压平毁灭金字塔 封面图](/assets/images/posts/eb18b3e1-ed72-4816-890a-2865806e1441/1.jpg)