軟體開發適合 BottomUp 或 TopDown?
在進行軟體開發的系統設計,有兩種迥然不同的思維模式,一種是由下到上的 BottomUp,也就像是蓋房子一樣,由地基規劃打底開始,逐步往上;另一種是由上至下的 TopDown,由需求主導,逐步向底層延伸。甚至,微軟曾經為了不得罪兩邊的支持者,提出了折衷的 MiddleOut 從中間往兩邊延展的模式,但落得兩邊都不討好的下場,所以這個含糊其辭的異教論我就不在此討論了。
十年前當 XML base 的 WebService 初試啼聲,彷若帶來一道曙光,是 IT 界整合的創世救星的年代,那時候各種系統整合的名詞蜂擁而出,開始有 SOA(Service-Oriented Architecture:服務導向架構)、 BPM(Business Process Management)或是 ESB(Enterprise Service Bus:企業服務匯流)⋯⋯等令人眼花繚亂的名詞,當年我負責協助金融業導入相關技術產品與專案,就遇到究竟該 TopDown or BottomUp 僵持不下的爭論。
上述專案導入由於與利益掛鉤,所以廠商都會想盡辦法勸客戶以 Infrastructure(基礎建設)的角度去採購與施工,本來客戶只是需要一條柏油路讓資料可以從 A 送達到 B ,但是廠商會以宏觀的觀點告訴客戶直接投資一個四通八達的捷運系統,未來資料要從 ABCDE⋯到⋯XYZ 二十六個英文字母都不是問題。而且為了要應未來『不可預期的延展性』,所以各種可搭配的彈性或高效能(承載量)產品就會順便引進,本來百萬的小案子就被拱成千萬之譜,然後廠商會感謝客戶 CIO 的慧眼獨具,這筆一次性投資之後在未來幾年(倒底是幾年?)內成本平攤後會比選擇每次都蓋一條小馬路來的划算。於是乎,著眼於這樣的利益導向,BottomUp 幾乎是既定的策略。
但是 BottomUp 真的能『完全』滿足每個專案所要的需求嗎?
很快每個專案就會發現,本來打算花一百萬開條馬路直接到我家門口又快又方便,現在花了一千萬搞了個捷運系統,結果為了因應大家共同使用的需求,所以他們把捷運站蓋在離我家有點遠的幾個重大十字路口,從捷運站下車之後我還得自己走一段路回家⋯⋯,更別說為了遵照捷運路線規則,中間我還多出換車和轉線的麻煩(這時候原導入廠商就會回頭現身提出各種收費的客制化服務,又是一筆商機),對每個專案的負責人而言,公司這筆投資有點隔靴搔癢啊。
當然若不趕時間且平時用量夠大的話,還是會感謝這個節運系統的存在,他畢竟省下了自己開馬路的成本,而且提供了可預期的一定水準與服務品質(每次招標來的廠商幫你蓋條馬路的品質風險是很難預估的),只需要每次多花點小錢解決離開捷運站後轉乘其他交通工具的開銷,處理最後一哩的問題,但這卻不是 Performance Critical Mission 適合的解決方案。
相信當你時間趕不及的時候,還是會選擇直接跳上計程車直達目的吧?
基本上,BottomUp 的設計是嘗試以中立的立場,不偏袒任何應用的方式(離各種相關應用平均距離相同),儘量提供可以被重複使用的服務端點(捷運站)。如果你家的系統就那麼寥寥沒幾種,甚至主力系統就一種,搭建這樣的基礎建設反而是拖累自己。
講完系統整合面,用 BottomUp 做單一軟體或系統的開發也常會遇到些障礙。
身為非 UI 的工程師,當拿到 UI MockUp 和需求描述,過去最常做的事情就是先從最底層的資料庫設計開始著手,接下來才開始往上堆疊應用程式開發。而且為了彰顯自詡為“經驗豐富的資深工程師”這樣的角色,所以我們會在底層琢磨許久,預測未來效能的瓶頸,提供可以滿足各種 NFR 的高超設計:多執行緒、平行作業、記憶體快取、自動資料同步還原、資料切割壓縮封存、負載平衡與高可用性⋯⋯,什麼鬼都有。
過早的最佳化,不如不要最佳化。
事實上,這時候我們已經迷失在實作的細節之中,而罔顧了真正的商業需求。
當打造了完一個偉大框架的萬能底層之後,我們告訴商務應用層開發人員:我們底層提供了這個 API 的第三個參數可以讓你決定是否啓用記憶體快取機制,如果這邊你需要提高讀取效能就帶入 true。若你想要重新同步記憶體快取的內容和資料庫的資料,你呼叫另一個 API 就可以做到。如果你收到系統例外,打上面這個 API 就會自動切換到另一個備援節點⋯⋯。
是的,上面這個情境代表我們已經開始玷污了商務應用層的開發,藉由暴露過多底層特性而導致商務層不再維持抽象,而是緊緊綁死底層提供的各項超能力。而這樣打破抽象原則的開發通常是一種 BottomUp 思維不經意犯下的錯誤,而且極為普遍,因為大部份的開發還是停留在底層完工後才有 API 可以呼叫,才能編譯這樣的思考模式,所以上層應用開發人員很自然會對底層所有的設計概括承受。
沒多久前,公司同事才因為這種責任分層問題找我討論。他的觀點:我只是負責提供 service 的人,我就盡量把所有能參數化的東西都公開透明化給上層,應用開發人員自己去組合出他要的,這樣出了問題他也容易自己 debug。
就除錯而言,封裝和抽象的確會造成無法第一時間就直接找到 root cause(問題根源)的阻礙,也容易造成線上系統出問題會讓一整條開發人員起床聯合問診的情況⋯。但,我還是認為這是必要之惡,也是該付的代價。
那位同事的做法,等同是把責任轉嫁給 service 的使用者,並且要求對方必須了解底層的特性,並俱備跟自己不相上下的使用知識,這無疑是增加對方腦力的負擔(人家還得同時搞懂商業需求的邏輯呢)。
透過抽象和封裝把責任切割好,底層的人若擔心半夜被叫起床尿尿,就會自覺要把更多內部就該處理好的事情做好,例如該有的監控稽核 tracing log⋯,而不是把責任往上拋。
講了這麼多關於 BottomUp 對系統開發造成的弊端,那 TopDown 呢?
其實 TopDown 是現在我認為更為適合的系統開發方式,而且可以搭配另外兩套實施論:DDD(Domain-Driven Development:領域驅動開發)和 TDD(Test-Driven Development:測試驅動開發)。
一切從商務需求出發,搭配 DDD 可以在應用邏輯層開發時,就使用符合商務語意的命名原則來設計物件和方法,並且以簡單明瞭又直觀的方式撰寫邏輯,易於閱讀與維護。但是欠缺底層不就如空中閣樓那般,實務上如何編譯?這是剛好可以順水推舟地導入 TDD 的測試先行概念,利用尚未真正實作而會失敗的 Test Case 來彌補缺口。
而且由於不綁底層實作(沒東西可綁),所以 Unit-Tesing(單元測試) 導入常遇到的許多阻礙就迎刃而解,也順其自然地達到去耦合的特性。
由於我們先放棄了底層實作,意味著我們不去思考效能瓶頸與最佳化等議題,只單純以商務邏輯的描述下去開發,透過 TDD 來要求功能結果的正確性。所以之後我們一定會回頭面對必須重構來達到效能速度⋯⋯等 NFR 的需求。
大部份事先琢磨底層的人其實都想避開未來重構這個麻煩,所以總是期望未卜先知預期將來的一切狀況做好事前設計與準備,但這本來就是過度理想的幻想。反而不願意重構這樣的念頭成了死不認錯、不肯大改的心理抗拒因素。
透過 TopDown 導入 TDD,為的就是迎接不可避免的重構。如果當初 TDD 做得扎實,現在就可以義無反顧地為重構放手一搏,重新建構符合商務需求卻又俱有最佳效能的底層。
單純 TopDown 讓專案失敗的機率其實很高,因為它雖然確保東西符合功能需求,但是一旦經歷壓力測試就會爆發各種 NFR 的疑慮,偏偏沒有 TDD 的狀況下修改底盤牽扯疊覆其上的模組範圍極廣,因此風險也極高,幾乎是上線日前最無法妥協的事,也代表時程延誤,專案經理要殺頭。
所以走 TopDown 不只是容易搭配 TDD,也一定要搭配 TDD!這樣才能作出容易理解、易於維護且修改風險低,可以健康成長系統。
留言
張貼留言