喵對發票開發心得

這是一個當兵前無所事事被一個朋友拜託寫的 app

David Lin
David Lin
Jul 21, 2017 · 8 min read

歐洲回來之後,有一段時間閒著沒事做,每天在拍照跟念日文,覺得好久沒有碰 iOS 有點陌生有點想念,就在這個時候我朋友突然跑過來問我要不要幫他寫個 app,我二話不說就答應了。

既然是小專案就要用潮到出水的架構嘛

就以前在開發時使用的是 MVVM 的架構,但隨著專案越來越肥大,越覺得測試越重要,所以我就想,是不是 MVVM 設計上有問題呢?還是我的思考方向有問題?單人開發實在是沒有什麼人可以討論這件事的對與錯,於是我就開始物色新的架構,去看看別人怎麼寫的。不看還好,一看不得了,看到 uber 使用 viper 就覺得很潮,想都沒想就直接敲定這個專案我要用 viper 寫。

於是我就開始搜尋有關這方面的範例專案檔(看現成的最快咩),看來看去普遍都是一般的 viper 架構,並沒有類似 uber 提出的「具有 state tree 的 viper — Riblets」。

https://www.skilled.io/u/swiftsummit/swift-with-a-hundred-engineers

在觀看 uber 講他們開發 mobile app 的影片時,他們說到他們兩邊的 app 用一樣的架構,極端一點甚至連變數命名、邏輯都一樣。而他說到 viper 這個架構時,他們也稍微對 viper 做了些架構上的微調整,包含之前的 state tree,還有一個有趣的就是他們多加了一層 builder 層。一想到這個 builder,就讓我想到我的信箱中好像躺著一封跟 viper 有關係,從 Realm 寄過來的信。

於是我找到這篇很精闢的文章,還順便附帶了測試方法呢:

https://news.realm.io/news/break-the-monoloth-with-b-viper-modules/?utm_source=ios-list&utm_medium=email&utm_content=ios-content

看完這篇後就開始我的 b-viper 之旅了。

使用 b-viper 的感想

我就不多說 b-viper 到底長怎樣了,影片講得很清楚。這邊只談使用 viper 的心得。

層層分明,層層曖昧

viper 代表的是 view, router, presenter, interactor, entity。這邊偷用一下之前一篇簡單講解架構的文章裡面的圖片:

presenter:跟 ui 相關的 business logic,但跟 UIKit 無關。譬如從 view 接收到使用者按個按鈕,去叫 interactor 做事,完成後更新 view。

interactor:跟 data/model 有關的 business logic,譬如操作資料庫,打 api 等等。

router:顧名思義,跟畫面轉場有關。

entity:純資料(plain data),一般來說就是你的 object, class 等等。像是打 api,或者存取資料庫等等,我們會把它視為 service 或者 manager,不屬於這五層中的其中一層。


那為什麼說曖昧呢?

譬如我要打一個 api 好了,我可能會先定義這個回傳資料型態長怎樣,並且做成 struct(這邊視為entity)。然後包一個 class 比如說 makeAPICall 專門來打這個 api(這邊視為service),所以我可以在 interactor 操作這個 service 去拿資料,並且回傳給 presenter。

到這邊沒有什麼問題麻~但如果今天加入 NotificationCenter,那這應該屬於哪一層?譬如喵對發票中的掃描 qrcode 資料來源是從 View 進來,那我應該在哪一層解析傳入的資料?說真的我自己都覺得有點曖昧不清,不知道放在哪一層最為合理。

先說說 NotificationCenter 好了,我認為跟 UIKit 很相關的 notification 我會優先考慮放在 View,因為這些 notification 可能會直接影響 ui layout(比如 keyboardWillShow)。

但如果今天這個 notification 是個資料庫資料更新的 notification,那我會把它放在 presenter 或者 interactor(這邊是情況決定),如果資料庫更新會影響 ui,presenter 接到更新,就去操作 inteactor 拿最新資料來更新 ui。

又或者是聊天程式,interactor 透過某個 socket service 監聽是否有訊息進來,一有訊息進來,這邊我就會使用 weak delegate 通知 presenter 更新 ui(只是構想,還沒有實際演練)。

所以才說……真的每次有一個比較特別且曖昧不清的角色進來時,都要思考要把它擺在哪裡才合理這件事。

測試這件事

在看 b-viper 的第一關就是他使用的一個叫做 dependency injection 的東西,看字面上感覺他是一個很難的東西,但其實了解他後就知道,他就是字面上的那個”意思”。

如果你懂 dependency injection 的意義,那你基本上就可以很容易的寫測試了(前提是你要真的了解囉~)。

Uncle Bob — Clean Architecture

我們來看張圖片吧

仔.細.看.好.啦

有沒有發現這根本就是 viper 去掉 r 剩下來的層次呢?

在 iOS 中測試難寫是因為他們不確定性很高,而且要處理 async 的 function 還滿一個頭兩個大的。所以通常我只會對 entities 跟 service 寫 unit test,unit test 有點像是 behavior test,你給一個 input 測試他的 output。

但 interactor, presenter, view 我就不這樣測了,看到上面紅色的 use cases,其實就是我們的 inteactor,要測試他,我們首先要先 mock entity 跟 presenter。我們可以叫假的 presenter 去叫 interactor 從 entity 拿出指定的東西,而且 entity 又是我們 mock 出來的情況下,我們要看的是 interactor 有沒有依照我們的想法,做出指定的動作,這個稱為 interaction test。

這邊說得很模糊,所以交給大家自己去看啦!這篇算入門又精闢,進去 ctrl+f 找 behavior 就可以看到了。

要測試 presenter 就 mock view 跟 interactor,測試 view 就 mock presenter。

不過我測試還沒寫得很熟練,這邊不負責任的宣示一下 XD

總結

優點

  1. 很潮。
  2. viper 符合 uncle bob 提出的 clean architecture,所以測試上很容易。個人在這兩個月的開發上,覺得他的維護性很高,因為你必須在前期寫 code 時不斷思考該角色放置位置的問題。角色放錯位置整個 code 看起來、寫起來、運作起來都會很怪,後續繼續加 code 時會迫使你不得不調整該角色位置,不然整個架構會炸掉。
  3. 測試上也好寫很多,有測試的 code 總有保障嘛~
  4. 多出來的 builder 可以依據情況不同而建立出不同的 module 以供使用(比如 uber 可以建立出 uberBlack, uberX 等等不同服務,一樣可以叫車,只是車種、車資、車輛位置不同這樣)。
  5. 可以與 MV(X) 混用(不過是不建議)。
  6. 非人為情況下(通常會炸都是你害的),viper 不會有 retain cycle。

缺點

  1. 檔案有夠多,光是寫個 view controller 就可以切到 5 個 protocol、5 個 class(view, router, presenter, interactor, builder),還不算 entity 呢。
  2. 常常迷失方向。
  3. 寫 viper 基本程式碼時會很暴怒,因為很重複,而且 reference 有時 strong 有時 weak 你搞不清楚(但後來寫了 Xcode code snippet 解決)。
  4. 架構嚴謹到你想偷懶都會很慘,測試寫起來會有問題。
  5. 寫測試時會 mock 到暴怒,一個測試可以ㄊㄇ寫到破千行只為了 mock 東西。

好啦說了這麼多,還是要有點實際練習才會知道怎麼寫,除了上面的 b-viper 講者有提供範例外,這邊我也把喵對發票開源出來,歡迎一起討論唷~

https://github.com/yoxisem544/MeowInvoice-iOS

)