關於 Swift Error 的分類

Swift 錯誤類型的種類

Simple domain error

簡單的,顯而易見的錯誤。這種錯誤的最大特點是我們不需要關心原因,只需要知道錯誤發生,並且想要進行處理。用來表示這種錯誤發生的方法一般就是返回一個 nil 值。在 Swift 中,這類錯誤最常見的情況就是將某個字符串轉換爲整數,或者在字典嘗試用某個不存在的 key 獲取元素:

// Simple Domain Error 的例子

let num = Int("hello world") // nil

let element = dic["key_not_exist"] // nil

在使用層面 (或者說應用邏輯) 上,這類錯誤一般用 if let 的可選值綁定或者是 guard let 提前進行返回處理即可,不需要再在語言層面上進行額外處理。

Recoverable error

正如其名,這類錯誤應該是被容許,並且是可以恢復的。可恢復錯誤的發生是正常的程序路徑之一,而作爲開發者,我們應當去檢出這類錯誤發生的情況,並進一步對它們進行處理,讓它們恢復到我們期望的程序路徑上。

這類錯誤在 Objective-C 的時代通常用 NSError 類型來表示,而在 Swift 裏則是 throwError 的組合。一般我們需要檢查錯誤的類型,並作出合理的響應。而選擇忽視這類錯誤往往是不明智的,因爲它們是用戶正常使用過程中可能會出現的情況,我們應該嘗試對其恢復,或者至少向用戶給出合理的提示,讓他們知道發生了什麼。像是網絡請求超時,或者寫入文件時磁盤空間不足:

// 網絡請求
let url = URL(string: "https://www.example.com/")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
    if let error = error {
        // 提示用戶
        self.showErrorAlert("Error: \(error.localizedDescription)")
    }
    let data = data!
    // ...
}

// 寫入文件
func write(data: Data, to url: URL) {
    do {
        try data.write(to: url)
    } catch let error as NSError {
        if error.code == NSFileWriteOutOfSpaceError {
            // 嘗試通過釋放空間自動恢復
            removeUnusedFiles()
            write(data: data, to: url)
        } else {
            // 其他錯誤,提示用戶
            showErrorAlert("Error: \(error.localizedDescription)")
        }
    } catch {
        showErrorAlert("Error: \(error.localizedDescription)")
    }
}

Universal error

這類錯誤理論上可以恢復,但是由於語言本身的特性所決定,我們難以得知這類錯誤的來源,所以一般來說也不會去處理這種錯誤。這類錯誤包括類似下面這些情形:

// 內存不足
[Int](repeating: 100, count: .max)

// 調用棧溢出
func foo() { foo() }
foo()

我們可以通過設計一些手段來對這些錯誤進行處理,比如:檢測當前的內存佔用並在超過一定值後警告,或者監視棧 frame 數進行限制等。但是一般來說這是不必要的,也不可能涵蓋全部的錯誤情況。更多情況下,這是由於代碼觸碰到了設備的物理限制和邊界情況所造成的,一般我們也不去進行處理(除非是人爲造成的 bug)。

**在 Swift 中,各種被使用 fatalError 進行強制終止的錯誤一般都可以歸類到 Universal error。
**

Logic failure

邏輯錯誤是程序員的失誤所造成的錯誤,它們應該在開發時通過代碼進行修正並完全避免,而不是等到運行時再進行恢復和處理。

常見的 Logic failure 包括有:

// 強制解包一個 `nil` 可選值
var name: String? = nil
name!

// 數組越界訪問
let arr = [1,2,3]
let num = arr[3]

// 計算溢出
var a = Int.max
a += 1

// 強制 try 但是出現錯誤
try! JSONDecoder().decode(Foo.self, from: Data())

這類錯誤在實現中觸發的一般是 assert 或者 precondition

斷言的作用範圍和錯誤轉換

fatalError 不同,assert 只在進行編譯優化的 -O 配置下是不觸發的,而如果更進一步,將編譯優化選項配置爲 -Ounchecked 的話,precondition 也將不觸發。此時,各方法中的 precondition 將被跳過,因此我們可以得到最快的運行速度。但是相對地代碼的安全性也將降低,因爲對於越界訪問或者計算溢出等錯誤,我們得到的將是不確定的行爲。

函數 fatalError precondition assert
-Onone 觸發 觸發 觸發
-O 觸發 觸發
-Ounchecked 觸發

對於 Universal error 一般使用 fatalError,而對於 Logic failure 一般使用 assert 或者 precondition。遵守這個規則會有助於我們在編碼時對錯誤進行界定。而有時候我們也希望能儘可能多地在開發的時候捕獲 Logic failure,而在產品發佈後儘量減少 crash 比例。這種情況下,相比於直接將 Logic failure 轉換爲可恢復的錯誤,我們最好是使用 assert 在內部進行檢查,來讓程序在開發時崩潰。

本文轉載自喵神的 Blog: https://onevcat.com/2017/10/swift-error-category/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章