Liyao Chen
Jan 8, 2018 · 5 min read

你是否也曾因為一在發生的 UI 錯誤煩惱? 當 QA 回報某個頁面錯誤,[Bug] 精選頁面 Load more 未顯示 Loading indicator,為什麼要說又呢?

如果你也有這樣的症頭,請繼續看下去。

為什麼要說又呢
為什麼要說又呢
為什麼要說又呢

雖然我們依循 Model-View-Controller(MVC) 的架構開發,但對於複雜的 UI 狀態改變還是會出錯。

哪裡處了問題?

Class KKFeaturedViewController: UIViewController {
var cards: [Card]
var isLoading: Bool
var isReloading: Bool
var hasMore: Bool
var loaded: Bool
// ...
}

大家都寫得差不多,建立多個 flag 來判斷不同的狀態,這樣的程式碼哪裡有問題? 讓我們回到 QA 回報的問題 [Bug] 精選頁面 Load more 未顯示 Loading indicator ,可以從上面的變數中推測當 Load more 時,會是什麼狀態?

  1. cards.count > 0
  2. isLoading == true
  3. isReloading == false
  4. hasMore == false || true
  5. loaded == true

由下列程式碼中很清楚的光是 cards.counthasMore 就定義了四種狀態。更不用說在此架構下若要滴水不漏,你需要處理 2 的 4 次方,也就是 16 種狀態。

Class KKFeaturedViewController: UIViewController {
// ...

func fetchCardAPI() {
// ...
isLoading = true
if hasMore {
if cards.count == 0 {
// First load without content (reload)
}
else {
// Loading more with content (load more)
}
}
else {
if cards.count == 0 {
// Empty content
}
else {
// Content complete
}
}
}
}

不存在狀態 if (loaded == false && cards.count > 0) { // 不該存在的狀態 }

16 個狀態組合中若不小心出現了不該出現的狀態,Bug 就產生了。同時而且這樣的程式碼不容易分辨哪些狀態有處理過,哪些沒有,像是複雜的電路板到處接來接去,出錯時不容易找到問題出在哪,當然也就不容易修正。

複雜的程式碼流程就像亂接的電路板
複雜的程式碼流程就像亂接的電路板
複雜的程式碼流程就像亂接的電路板

我想有限狀態機 可以幫我解決這個問題。有限狀態機的概念是 在有線個數的狀態裡,狀態之間轉移的數學模型。 主要的元素有 狀態, 動作(事件) 來延伸出 狀態A + 事件 => 狀態B 。最常見的應用是交通燈(紅綠燈),也就是 綠燈時 + 60秒 => 黃燈 以此類推。

回過頭來整理真正會出現的狀態,原本 16 種狀態只有成 8 種會出現,一半以上的狀態都可以忽略 (難怪這麼多 Bug )

參考How to fix a bad user interface一文中並實作其 UI Stack 的概念。

UI Stack
UI Stack
UI Stack

狀態實作

在我們情境下需要考慮到較多的狀態,所以就從原本的 5 種延伸成下列 8 種(請你視使用情境而自行調整成需要的狀態)。

將 ViewController 的權責從 處理狀態轉換 + 過濾不必要的狀態 + 各個子UI管理 被簡化成 根據狀態顯示UI,並且集中管理。

enum UIStack {
// Blank State
case initial // 初始狀態尚未開始載入資料
// Loading State
case initialLoading // 初始載入,可與 Reload 共用
case partialWithLoading // 載入部分資料,但又觸發載入下一頁
// Partial State
case partial //載入部分資料
case partialWithError //載入部分資料,但載入新資料過程有誤
// Error State
case error // 錯誤
// Ideal State
case perfect // 資料完整載入
case empty // 打過 API 但是沒有資料 (empty inbox 的概念)
}

Class ListViewController: UIViewController {
internal var state: UIStack = .initial {
didSet {
// * switch cases ... * //
}
}
}

總結

設定狀態顯示UI 的邏輯分離後會讓事情簡單很多,防止自己粗心大意。

開發加速

開發過程中可隨時模擬狀態,就連部分載入但有錯誤的情境都可輕易重現。 舉例來說,想要重現 contentEmpty 這個狀態,在以前我可能要去修改 API Clint 去偽裝這隻 API 回應給我 0 個資料,而現在只要直接改 state 即可。

容易除錯

當 UI 出錯時就可以分成兩種錯誤,一種是狀態錯了,另一種是對應的 UI 錯了。

程式碼集中

跟狀態相關的程式碼會集中在一個地方,統一實作。

防呆

狀態一但被定義,Swift 的 Switch case 會強迫你要實作所有 cases,不讓我們有偷懶的機會。

Reference

Liyao Chen

Written by

Swift iOS developer. TDD adventurer. https://gliyao.com

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade