為什麼要這樣寫?
某日下午,和同事討論了一下我發的 PR 內容。
「看程式碼,我是能理解你想要幹嘛,不過『未來維護的人』看得懂嗎?」這樣說道…
「你這樣設計未來可能會發生…的問題」
每種設計方式都有他的問題,還有可能是相同的問題
我的程式理念,慢慢的往依賴能切分乾淨靠近,但是相對於 structured programming 的直覺使用,依賴反轉過後,尋找實際實作者時,往往會出現難以接受的事實。
例如:iOS 中拿取 bundle identifier
基本上你可以透過 類 system function
拿到。以 structured programming 下,開發者會設計一個 Utils 的物件,包裝這個功能,然後對外宣稱,「如果要拿 bundle identifier,問 Utils 就好了。」
恩,合理!一個 system 的功能找一個包裝好 system 的工具 (Utils) 來取得。
但是,如果是用 object-oriented programming 的角度呢?他的主要的描述反而是:「我這個 manager 想要拿到 bundle identifier,只要能提供給我就行。」「Utils 正好能提供,你問他吧!」
同樣的目的,但是不同的解釋
看起來好像沒什麼,但延伸出了什麼問題呢?
如果不同屬性的變數,會分開成不同的物件去控制。
ex:
1 | class Utils { |
如果有一個功能要同時看系統版本,以及後端服務是否有開啟功能,最直接的寫法會長上面這樣。
參數分別給不同的 manager 管理,看起來也很合理!
如果以 UIViewController
的角度去描述呢?
1 | class Utils { |
「誰能告訴我 buyTicketSerivceEnabled
?」如果條件參數分別是兩個物件提供,那必定要寫一個中介的物件來實踐這件事情。因此就會多一個 Controller
。
想像上 UIViewController 應該會有自己的 ViewControlling,且會有一個自己實作的 Controller 物件,這個物件應該就是「商業邏輯」的一部份。
但是當 UIViewController 變多了呢?就可能會產生多個 Controller,每個 Controller 都包含了一部分「商業邏輯」。
但悲傷的是,商業功能通常會在多個頁面都沾染一些。如果每個 Controller 都是各自實現,那就會造成「code repeat yourself」。為了避免這樣的事情,又會把功能抽出來,進化成一個物件。
1 | class Utils { |
這裡多了一個 ProjectBuyTicketManager
,並且多了一個 func
叫做 buyTicketSerivceEnabledAndOSSupported
,因為他比 buyTicketSerivceEnabled
還考慮了 OS
。如果名稱沒有區別,那未來的維護者可能會誤會用底層的 BuyTicketFunction
就好了。
增加新的頁面,收納同群的商業邏輯
因此若有多個 UIViewController
跟 Buy Ticket 有關,
-「如果商業邏輯相同」則可以共用 ProjectBuyTicketManager
-「如果略有不同、大部分相同」,則可以整理 ProjectBuyTicketManager
內的邏輯,共用相同的區塊
-「如果商業邏輯不同」… 大哥,這樣應該就是完全不同的程式碼了吧。
這就是 UI 可以先考量自身的使用情境,用單一 protocol
下的 function
的描述,然後再詢問一個商業邏輯的實作,獲得真正的功能。
為了測試、為了維護
商業功能不會永遠不變
變化是在所難免,即使多一個小小的邏輯,也有可能需要調整資料結構。但重點是,調整的當下,你有多少的信心之前的功能還是對的?
理想上可以單單針對 UI 物件作單元測試 (Unit Test),Mock 商業邏輯層,來達到驗證頁面正確。
因此:
- 單單調整
UIViewController
物件的內容,接口UIViewControlling
沒有變化,那基本上可以確保原來的功能還是會正確的。 - 當商業邏輯層 (
ProjectBuyTicketManager
) 內容有變化時,而最終 UI 上發現有誤時,
就可以先初步排除 UI 層內容的問題,而可以先從「商業邏輯層」和「商業邏輯實作 UIViewControlling」的這兩部分下手。
這裡只是先描述切分外層依賴的概念,範例裡的「商業邏輯層」用了許多 shared instance,會大大影響程式的可測性。但是一樣可以用 protocol
做相依性的切離,這裡先不贅述了。
測試不能保證不會出現未知的錯誤,但是能強化已知項目的穩健度
PS. 最近設計的 iOS Whoscall SDK 慢慢完成了。這樣的結構、想法,在最後做功能微調和完成 TODO
上有不少的幫助。有空會再把這些心得記錄下來!