Browsing Category

讀書筆記

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

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

讀書筆記

讀書筆記:A dependency injection kind of guy

內容來自 SwiftBySundell 的廣播節目第20期

這次的特別來賓是 Radek Pietruszewski

software writer at Nozbe and creator of SwiftyUserDefaults

他說自己是 software writer 而不是什麼厲害的架構是,因為他不認為自己真的弄了什麼厲害的架構,而只是認為自己寫的程式碼是讓人讀的,所以稱自己為 writer 。

以下條列幾個重點

  • 從 Requirement 到 Solution 的能力就是 Senior developer 該具備的能力
  • 每個人都應該去看看 Functional programming 跟 ReactNative ,即便你不去真正使用它,你也會從中得到很多不同靈感,在未來面對其他問題時會很有幫助
  • 尤其強調 ReatNative 是未來 update state and UI changed function(input, state)
  • 依賴注入(Dependency Injection)不過是把一個物件傳給另一個物件,不是什麼多困難的事情
  • 在軟體開發必須為未來考慮,因為隨著時間過去 spec 會改變,設計會改變,必須為未來準備
  • 用 Default 取代 Singleton ,用 Factory 輔助
  • 像是現在流行的 Micro service 一樣,我們可以用 Micro features 來因應複雜的需求,像是拆分成 framework (但推薦 static framework, 因為 dynamic framework 會有 app launch time 的問題)

Obj-C to Swfit 的建議

他沒有太多這樣的經驗,但不建議重寫,因為重寫總是會有很多風險而且花的時間通常超過你的預期。最終可能會變成從已知的 bug 換成未知的 bug 而已。

建議從 Testing 跟新功能開始寫 Swift,熟悉語法同時練習哪些元件可以拆分出來。

DI and singleton

依賴注入(Dependency Injection)不過是把一個物件傳給另一個物件,不是什麼多困難的事情,只不過是養成良好的習慣。

如果只是一個人做的小 project 是沒差, singleton 不會是什麼問題。

但如果你要做的是產品,一年以上的產品,你必須要跟很多人合作。也因為時間拉長的關係隨著時間過去 spec 會改變,設計會改變,你原本的假設會被破壞,原本你認為只會有一個 instance 的假設不存在了,這時 singleton 已經散佈在 100 多個地方,你就會超級難改。

在這樣的情境下寫出容易修改,容易維護的程式碼,就是維持高品質程式碼的關鍵。

如果你有好一點的架構,就不需要花一個月只為了一個假設被改變了。

我們雖然不能預知未來,但能夠為它做準備。

Sundell 則是說自己常用 Default argument 避免使用 Singleton (詳見 avoding singleton),另一個他常用的手法是用 Factory pattern 可以一行建立 instance 來避免使用 Singleton。