程式碼審查中常出現的一種意見是:「這個改成 guard 吧。」既然用 if 也會完全相同地執行,為什麼還要特別修改?看似只是偏好問題,但其實 guard 是 Swift 在語言層級用來推動特定程式碼風格的語法:提早退出,以及靠左對齊的正常路徑。
這是 Swift 基礎系列第 4 篇。在 optional 篇中,我曾短暫介紹 guard let 作為解包工具;這次要深入探討 guard 本身:它與 if 有何不同、編譯器保證了什麼,以及什麼時候應該使用 if 而不是 guard。
如果想重新了解值不存在的狀態,建議先閱讀 Swift optional 的本質與解包判斷標準。
問題場景 — 毀滅金字塔
先看看 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、optional binding 範圍與可讀性目的
- 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)