iOS 讀書筆記 軟體開發

錯誤處理之斷言 Assertion – 不要亂用我設計的物件

錯誤是很抽象的字,廣義來說所有不在預期內的結果都算是某種”錯誤”。
接下來會分兩三篇文章來分享最近所學到跟錯誤處理相關的議題,話不多說我們開始吧💪

斷言(Assertion) 是一種條件式檢查語法,當現行狀態不合乎條件時會強迫終止程式。

斷言只用在標示一些邏輯上不可能或不應該出現的情形,若上述情形真的出現時,表示有些基本架構已出現問題。 — wiki 斷言

為什麼會需要斷言

當程式碼執行到不該出現的狀態時,且一但進入此狀態就執行不下去的時候。常用在提醒開發者 API 的使用規範、此程式碼缺少必要圖片、初始狀態資料缺少等等。

白話一點就是『這裡有嚴重錯誤,請你現在把程式碼修好』,舉例來說:
– 汽車應該只能汽油,若是加了沙拉油就會讓車子受損,請不要亂加
– 初始值應該永遠都要存在,若不存在物件無法建立
– 必要圖片 (app icon) 一定要包含在資料夾裡
– function 的輸入值規範在某個範圍 (分數 0-100)
– 虛擬類別的建立(Swift API 目前無法宣告虛擬類別)

一段設計良好的程式碼應留下足夠的前後文相關線索,提醒使用者(Code user) 物件或方法的使用條件。但我們都知道沒有人喜歡看說明書 (Document),所以更高明的做法就是直接用程式碼規範條件,並且在使用者犯規(不合乎規範)時提醒他,而斷言(Assertion)這個語法就是為此而生。

Assert

預設僅在 Debug 編譯下才會作用,在 Production 沒有效能影響。

Use this function for internal sanity checks that are active during testing but do not impact performance of shipping code. —Apple Document

接著我們來看一段規範輸入範圍的斷言範例


// This method only works when score 0-100 (bounds) func update(score: Int) {   assert(score >= 0, "score should never be native value")   assert(score <= 100, "score should never greater than 100")    // Prevent crash   let newScore = min(max(0,score), 100)   print(newScore) }

你可以注意到這裡用 assert 來強制規範 score 必須介於 0 – 100 之間。除此之外,在程式碼後段的 min(max(0,score), 100) 則是用來將範圍外的資料修正,來避免在 production 環境下 Crash 。

AssertionFailure

不該執行到這一行,但是萬一 Production 執行到會忽略

func testAsertionFailure() {
    assertionFailure("nope")
    println("ever") // never be executed when debug.
}

guard score >= 0, score <= 100 else {
  assertionFailure("score out of bounds.")
  // If out of bounds in production will be ignored.
  return
}

db.write(score: score)

在上面這個範例裡,當數值超過範圍時不會寫入 db ,並且在 Config = DEBUG 時丟出 exception 。

Precondition

跟 Assert 很像但作用在 Debug 及 Release 。

// This method only works in 0-100 (bounds)
func update(score: Int) {
  precondition(score >= 0, "score should never be native value")
  precondition(score <= 100, "score should never greater than 100")
  
  let newScore = min(max(0, score), 100) // never excute
  print(newScore)
}

同樣的例子用 Precondition 的話 let newScore 這行就永遠不會被執行了,因為 Precondition 無論在 DEBUG 或 Release 都會生效。

PreconditionFailure

跟 FatalError() 一樣

guard let config = loadConfig() else {
  preconditionFailure("Config couldn't be loaded. Verify that Config.json is Valid")
}

// fatal error: Config couldn’t be loaded. Verify that Config.json is valid.: file /Users/John/AmazingApp/Sources/AppDelegate.swift, line 17


// [Require] powered by Sundell
// https://github.com/JohnSundell/Require
let config = loadConfig().require(hint: "Verify that Config.json is Valid")

FatalError

當某些區塊不想別人存取時,可以使用 fatalError 來明示。(常用在抽象類別。)
fatalError 還有另一個特性,它可以無視 return 關鍵字,請看下面範例。

// Abstract class
class Animal {
  func roar() {
    fatalError("Must override this method")
  }
}

class Cat: Animal {
  override func roar() {
    print("Meow!")
  }
}

enum MyEnum {
    case Value1,Value2,Value3
}

func check(someValue: MyEnum) -> String {
    switch someValue {
    case .Value1:
        return "OK"
    case .Value2:
        return "Maybe OK"
    default:
        // 不加 return 也能編譯通過
        fatalError("Should not show!")
    }
}

它表示不得不實作,但是做一個空實做時,並暗示它永遠不該被使用。

小結

Assertion 是很有用的東西,若是使用得當不僅能夠減少文件跟註解,也能讓後續使用的人能夠有明確指引,並且在超出原始設計的預期範圍能夠及時修正。

有沒有感覺更上一層樓了呢?一起持續學習吧 😀

You Might Also Like

No Comments

    發表迴響