學習 Swift 錯誤處理時,很容易覺得工具太多:throws 和 do-catch、try 後面的問號與驚嘆號,還有 Result 型別。有些 API 會拋出錯誤,有些 API 會回傳 Result,有些程式碼則直接用 try? 把錯誤吞掉。到底哪一種才是標準?
其實這些工具不是競爭關係,而是各司其職。基本做法是 throws;try 的變形代表「你有多在意錯誤」的光譜;Result 則是在需要把錯誤當成值攜帶時的補充工具。Swift 基礎系列第 5 篇,這篇文章會畫出這張地圖。
輸入驗證中先關閉失敗路徑的結構,與前一篇的 Swift guard 與提早結束的選擇準則 相連。
基本功 — 錯誤也是型別,拋出錯誤也是契約
Swift 錯誤處理從兩種宣告開始。將錯誤定義為遵守 Error 協定的型別,能夠產生錯誤的函式則在簽章中加上 throws。
enum PaymentError: Error {
case insufficientBalance(needed: Int)
case cardExpired
case network(underlying: Error)
}
func pay(amount: Int) throws -> Receipt {
guard balance >= amount else {
throw PaymentError.insufficientBalance(needed: amount - balance)
}
// ...
}
錯誤型別常使用 enum 並非偶然。就像 Optional 篇所看到的,enum 是用來表示「各種情況」的工具,而失敗原因正是各種情況。透過關聯值,還能攜帶不足金額等情境資訊。
更重要的是,throws 存在於簽章中。函式可能失敗這項資訊會註冊到型別系統,因此呼叫端必須使用 try,漏寫 try 就是編譯錯誤。不同於無法得知例外會在哪裡發生的語言,Swift 程式碼會用 try 標示所有可能失敗的位置。就像 Optional 將「沒有值」提升為型別一樣,throws 將「可能失敗」提升到簽章中。第 1 篇的安全優先原則在這裡也再次出現。
接收端的基本形式是 do-catch。
do {
let receipt = try pay(amount: 50_000)
show(receipt)
} catch PaymentError.insufficientBalance(let needed) {
showTopUp(needed: needed)
} catch {
showError(error) // 其餘全部, error 自動提供變數
}
catch 是模式比對。它可以只捕捉特定 case、取出關聯值,再用最後一個 catch 接住其餘情況,結構上類似 switch。這是配合錯誤為 enum 的設計。
try 的三種面貌 — 面對錯誤的關注程度光譜
try 有三種寫法,各自宣告「要如何面對錯誤」。
try — 我會處理,或將它傳遞出去。 這是基本形式。可以用 do-catch 捕捉,或將自己的函式也宣告為 throws,讓錯誤向上傳遞。錯誤傳遞會自動進行,不需要額外的明確程式碼,這是 Swift 錯誤處理隱藏的優點。中間層函式只要加上 throws,就能免費充當管線;處理只需在靠近 UI 的外層進行一次。
try? — 失敗也沒關係,只要沒有值即可。 它會把錯誤轉成 Optional:成功時得到值,失敗時得到 nil,錯誤資訊則會被捨棄。適合讀取快取這類「不行就算了」的情境。危險的是習慣性使用 try?。在需要失敗原因的地方使用 try?,會讓除錯線索悄悄消失。只有當你能對「沒有人會在意這次失敗的原因嗎」回答「是」時,才適合使用。
try! — 失敗代表程式設計師的錯誤。 失敗時會立即當機。和強制解包 ! 完全相同的邏輯,只能用在「如果失敗,就表示程式碼錯了」的地方,例如載入 App bundle 內建資源。Optional 篇建立的準則在這裡完全適用:如果 nil(這裡是錯誤)是正常情境,就絕對禁止使用。
Result — 將錯誤當成值攜帶
如果 throws 是基本做法,那麼什麼時候使用 Result?Result 是承載成功或失敗的 enum。
enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}
它與 throws 的關鍵差異在於時間與位置。throws 是強制在呼叫當下處理(或傳遞)的控制流程;Result 則是普通值,可以儲存、放進陣列,稍後再處理。因此,適合使用 Result 的情況大致有三種。
第一,基於 completion handler 的非同步 API。就像在閉包篇看到的,completion handler 會在函式回傳後執行,因此無法用 throws 傳遞錯誤。completion: (Result<Data, NetworkError>) -> Void 曾是這種情況的標準做法。第二,需要彙整結果時。若要執行 10 個工作並統計 7 個成功、3 個失敗,錯誤就必須是值。第三,希望明確指定失敗型別時。Result 的 Failure 是具體型別,因此從簽章就能看出會收到哪種錯誤。
不過,仍需掌握發展方向。async/await 成為標準後,非同步錯誤傳遞已由 async throws 負責,Result 的第一種用途正在新程式碼中減少。第三種用途也正由 Swift 6 的 typed throws(throws(PaymentError)、SE-0413)吸收。因此,目前的實務準則可以整理為:預設使用 throws;Result 是在特殊情況下,必須將錯誤以值的形式儲存、彙整或傳遞時使用的工具。
順帶一提,兩者之間的轉換只需一行。用 Result { try pay(amount: 100) } 包裝,再用 try result.get() 拆開。既然能在邊界自由轉換,就沒有堅持只用其中一種的理由。
設計感 — 好的錯誤會考慮接收端
接下來談一點語法以外的內容。錯誤處理程式碼的品質,大多取決於拋出端的設計。
將錯誤拆分成呼叫端可以採取不同處理方式的單位。 細分 case 的標準是:「接收端會用不同方式處理這兩者嗎?」餘額不足與卡片過期需要不同的使用者提示,因此應該是不同 case;TCP 逾時與 DNS 失敗若會由 App 以相同方式重試,就應該合併為 network。將處理方式相同的錯誤拆成十種,只會增加 catch。
要拋出錯誤,還是回傳 Optional,判斷標準也相同。 如果「不存在」是字典查詢這類可預期的日常結果,就使用 Optional;如果代表發生問題且需要知道原因,就使用 throws。當失敗原因只有一個而且很明顯時,很多情況下 Optional 就足夠了。
與錯誤型別一起設計要顯示給使用者的訊息。 遵守 LocalizedError 後,可以讓錯誤本身攜帶顯示用訊息;這部分已在另一篇文章中詳細說明。
直接執行確認的結果
我在 Apple Swift 6.3.3 中執行了將字串轉換為整數的 throwing 函式。用 try? 接收失敗時只剩下 nil;用 Result 擷取成功結果後,稍後可以透過 get() 再接回 throwing 流程。
error=try?-nil:true,result:42
正因如此,只要失敗原因可能改變日誌、重試或使用者提示其中任何一項,我就不使用 try?。相反地,在快取查詢這類將失敗與不存在視為相同的邊界,轉成 nil 更能表達意圖。只有在不需要立即處理結果,或需要彙整多個工作結果時,才選擇 Result。
總結
- Swift 錯誤處理的骨架是 Error 協定(主要是 enum)+throws 簽章+do-catch 模式比對。失敗可能性會註冊到型別系統,所有失敗位置都會用 try 標示。
- try 的三種變形是對待錯誤的態度宣告:try 表示處理或傳遞,try? 表示不在意原因,try! 表示失敗就是程式錯誤。
- 錯誤傳遞是自動的。中間層只要加上 throws,處理在外層進行一次即可。
- Result 是在需要將錯誤以值的形式儲存、彙整時使用的工具;非同步傳遞正由 async throws 接替,型別明確化用途則正由 typed throws 接替。
- 設計的核心,是將錯誤 case 拆成「呼叫端會採取不同處理方式的單位」。
至此,Swift 基礎系列的控制流程三部曲(Optional、guard、錯誤處理)完成了。下一篇會探討另一種基礎:Swift 的 String 為什麼比其他語言特別難用,並從「韓文有幾個字」為什麼不是簡單問題開始。
延伸閱讀
- Swift Bridge 模式:拆分抽象化,避免子類別爆炸(範例完整整理)
- [Swift 基礎 #3] Swift 屬性完整整理:儲存、計算、lazy、didSet 的 4 種選擇準則
- [Swift 基礎 #6] Swift 字串為什麼不能變成 text[0]?Grapheme Cluster(字素叢集)完整整理
來源與驗證
- The Swift Programming Language: Error HandlingSwift.org · 官方文件 · 查核 2026年8月26日依據: Error、throw、throws、do-catch 與 try、try?、try! 的處理方式
- Swift ResultApple Developer Documentation · 官方文件 · 查核 2026年8月26日依據: Result 的 success、failure 表示法與 throwing 運算式之間的轉換 API

![[Swift 基礎 #5] throws、try、Result 的選擇準則 封面圖](/assets/images/posts/79164702-137a-40ab-b9c5-28127ec6c2df/1.jpg)