Blackbing Playground

射後不理設計模式

誒~其實這是我亂取的名字

AJAX Loading Loading ajax-loader (2).gif.gif)

大概有好一段時間,AJAX 的互動方式都會增加一個 Loading 的狀態顯示,在 Response 尚未回傳回來時,提醒使用者資料還在傳輸當中,當 Response 結束之後再顯示結果。這個 Loading 的圖示,大概就被當成網站很潮的 AJAX application 代表,像是 ajaxload.info 還有自動產生 Loading 圖示的網站。優點是讓使用者知道資料正在傳輸。但缺點是要等 server 端回傳回來資料才能修改 UI 的狀態讓使用者知道動作已經完成。但其實在某些情況時,使用者根本不需要知道這些狀態,只要系統能夠確保事情是完成的就好 (而且其實看久覺得蠻煩的,尤其是看到他不斷的在 Loading,或者是看到他閃了一下就不見,都會讓人覺得系統有問題)。

perform actions optimistically

Secrets to Lightning Fast Mobile Design 這篇投影片分享了 Instagram 如何讓 Mobile App 更快速的祕訣,其中有一項我覺得很簡單而且可以大幅改善效能的技巧:「perform actions optimistically」。

舉例來說,當使用者按下「Like」時,就會出現「Liked」的狀態,但其實資料還在傳輸。
Screen Shot 2013-11-11 at 下午4.47.08.png

當使用者留言時,就會先呈現留言完成的狀態,但其實資料還在傳輸。
Screen Shot 2013-11-11 at 下午4.48.29.png

所以這種方式其實是將 UI 的 feedback 直接給使用者,讓使用者「感覺」很快。

再來看一個例子:http://localtodos.com/ 這是一個 todo list 的應用程式,所有的資料都存在 client 端的 localStorage裡頭。由於不需要跟 server 端溝通,因此在做 新增/修改/刪除 時,沒有網路傳輸的延遲,當然就不需要做 loading 狀態的顯示,任何操作都可以直接反應給使用者。

那麼如果資料是存在 server 端,又要達到一樣的效果,該怎麼辦呢?其實只要將儲存的動作,改成「射後不理」就可以了。也就是說,使用者的操作並不理會 server 端的回應,只要保證「 server 端的資料與使用者的動作保持一致」即可。

射後不理(fire and forget)

基本上這種設計模式就是以呈現 UI 為主,系統在做什麼事情使用者不管,反正 UI 呈現怎樣,就應該要幫我處理掉。可以想成 UI 跟 backend 是不同步的,彼此只依靠 UI 來同步所該做的事情。

聽起來有點抽象,舉個例子來說,若今天有個資料可以讓使用者自由排序,當使用者進行拖拉的動作時,要將使用者排序的動作儲存起來,如果每次拖拉完成的動作都需要等待 server 端的回應,那就會造成 UI 上的不同步,進而影響到使用者操作時的困擾。這時就會需要射後不理這樣的設計模式來輔助使用者進行操作。

實作方式

聽起來不難,不過實作過程其實遇到很多很麻煩的事情,因此分享一些實作上的心得,為了簡化問題,我用 Like/Unlike 來舉例。需求很簡單:按下Like 按鈕之後切換 Like/Unlike 的狀態。BUT,如果連按兩次?三次?十次? UI 可以即時顯示,但要如何正確的跟 server 端溝通?如果在資料傳輸的過程中,使用者又改變了狀態?那怎麼辦?

  1. 狀態流程圖

    重點其實是狀態的改變,把狀態列出來畫成「狀態流程圖」就會比較好理解。
    Like-Unlike (1).png.png)

    • unknown: 未知狀態
    • fetch: 從 server 取得 Liked 資料
    • mismatch: 使用者按下「Like/Unlike」而且跟fetch的值不一樣
    • flying: 資料正在傳輸中

    所以如果資料傳輸回來時,發現使用者的狀態改變的話,又會再觸發 mismatch 跟 server 端 request。

  2. 發 request 的時機

    如果是 like/unlike 時就發送 request ,很快就會遇到一個問題,當使用者連點兩下時,就會同時發出 Like 和 Unlike,若狀態是根據 request callback 回來判斷的話,遇到「後發先至」的情況就會讓 UI 造成問題。因此「射後不理」的設計模式其實是可以避免這種情況發生的。

    發 request 的時機其實就是在使用者改變狀態時,且跟 fetch 回來的值不同時,為了避免使用者頻繁更新狀態而跟 server 端重複發 request,可以設一個 timeout 來檢查是否觸發 mismatch。

  3. server 端的回應確認 UI 是否保持一致

    最後,當 request callback 回來時,檢查使用者是否又很善變的改變了狀態,如果改變了,再重新做一次 mismatch,並且發 request。如此就可以循環整個流程。

有限狀態機

由於實作部分很難拆解出來,而且不同的 scenario 都會有不同的流程或解法,就不把程式碼貼出來獻醜了。我想表達的重點是「有限狀態機」,其實將流程圖畫出來之後就會發現很適合用「有限狀態機」來表達流程與狀態,而且也可以避免在流程中任意改變狀態(如果你是自己處理事件的觸發),除了幫助自己理解流程之外,程式碼也會比較好維護。

關於有限狀態機我也還在摸索階段,這是第一次嘗試應用在專案裡頭,有興趣歡迎再討論看看有哪些適合的案例。

Reference: