架構整潔之道導讀(一)

我是《架構整潔之道》(Clean Architecture) 中文版的技術審校者,在審校的過程當中略有感悟,所以希望通過撰寫導讀的方式分享給大家。

書名的由來

《架構整潔之道》是Clean Architecture的中文譯名。看似簡單地延續了《代碼整潔之道》(Clean Code)的翻譯傳統,但事實上,對於取中文名字這件事,我們還是花了不少氣力的。拿到譯文初稿時,編輯提供了幾個備選的譯名:《架構簡潔之道》,《架構至潔》和《Clean Architecture》,這些名字各有各的考量,在沒有了解這本書的核心思想之前,我也沒有辦法給出恰當的判斷。所以在通讀了原作和譯作之後,我在ThoughtWorks諮詢羣裏發起提案,討論的過程很精彩,最終在骨灰級架構師新哥的建議下,結果大致趨向了整潔架構。

新哥說:“整本書在說依賴治理(管理),也就是如果降低依賴複雜度,和DDD中分離子域分層架構等想法是一致的;如同你整理你的房間,把東西分門別類放好,從這個角度,整齊比簡單更合適,或者清晰也可。”

除此之外,對於《架構至潔》這個候選項,大魔頭的態度是不要至潔,總感覺髒髒的。言下之意,自行體會。而讀MBA的嶽嶽和XR從用戶思維出發,《代碼整潔之道》和《架構整潔之道》可以相互增強記憶,更容易激發用戶的購買行爲。

即便敲定了“整潔架構”,大家對“之道”也有不同的看法。《代碼整潔之道》對應的原標題和副標題分別是Clean Code - A handbook of Agile Software Craftsmanship,而《架構整潔之道》對應的原標題和副標題分別是Clean Architecture - A Craftsman's Guide to Software Structure and Design。我們知道“道”是一種形而上的精神層面,老實講,把Craftsman(手藝人)譯做“道”是有點誇張的。

形而上是精神方面的宏觀範疇,用抽象(理性)思維,形而上者道理,起於學,行於理,止於道,故有形而上者謂之道;形而下是物質方面的微觀範疇,用具體(感性)思維,形而下者器物,起於教,行於法,止於術,故有形而下者謂之器。

道法術器擇其一?其實凡事總有權衡,遵循前人的譯法往往不會太壞。就像鮑勃大叔書中總結的穩定依賴原則,當我們依賴一種譯法次數越多,它就更加穩定,這種穩定先不說能否形成品牌效應,單是SEO就能省去不少功夫,那麼何樂而不爲呢?

鮑勃大叔的文字平鋪直敘、淺顯易懂,尤其喜歡用他自己生活中的經驗做例子。而且這本書是沒有知識斷層的,即便是初級程序員,也能在鮑勃大叔的循循善誘下,完成對軟件架構認知的轉變。因爲他總是從最基礎的知識點切入,自下而上,一步步地搭起架構的形狀。

範式的實質是約束

編程範式是程序員喜聞樂見的話題,就像Vim和Emacs編輯器地位的曠日之爭。它們的沉浮過往儼然就是風雲詭譎的江湖。結構化編程英雄遲暮逐漸淡出程序員的視野,覬覦已久的面向對象編程(OOP)以迅雷之勢稱霸武林,獨居一隅的函數式編程(FP)隱忍多年終於等來了一次機會。2012-2014年,江湖唱衰OOP的聲音不絕於耳,FP就像一名拯救程序員於水火的俠士想要撼動這片天地。硝煙過後,眼前卻不是你死我亡的慘狀,而是你中有我、我中有你的大團圓結局。當Java這位OOP的保守黨融匯了FP的特性lambda表達式,這場範式的衝突之爭也算落下了帷幕。

程序員談編程範式,喜歡黨同伐異,作爲FP的擁躉,我也不例外。可是鮑勃大叔卻娓娓道來,所謂編程範式不過是約束程序的執行,告訴我們什麼不能做而已。

  1. 結構化編程是對程序控制權的直接轉移的規範和限制
  2. 面向對象編程是對程序控制權的間接轉移的規範和限制
  3. 函數式編程是對程序賦值操作的規範和限制

Goto considered harmful

GotoConsideredHarmful

學習C語言編程的第一天,老師就告訴我們不要在程序中使用goto語句,因爲goto會破壞程序的結構化。Dijkstra在論文Go To Statement Considered Harmful中證明了goto語句阻止了將大程序遞歸分解成更小的可證明的單元,這意味着大量使用goto語句的程序是不能被證明的。這裏,不能被證明的語義是不可判定,類似說謊者悖論——“我在說謊”這句話不能被證明和證僞,所以不用goto其實是在保證小的程序單元可判定。可惜的是,Dijkstra並沒有證明程序單元,這項工作被科學方法——測試取代了。在保證程序單元可判定的前提下,測試是一種可以對其可證僞的科學方法。命題“天下烏鴉一般黑”就是可以證僞的,我們不可能枚舉天下所有的烏鴉,等到哪天找到了一隻白烏鴉,我們就可以說這個命題是錯誤的,這就是證僞。Dijkstra說的“測試只能說明bug存在,而不能證明不存在。”是同樣的道理。

測試可以保證,在當前已知情況下,程序單元是正確的。一旦有新的測試用例導致程序單元出錯,那麼我們就可以修正程序,讓程序更加接近真相。這或許就是TDD(測試驅動開發)的妙處所在吧。

去除了goto語句之後,我們發現具備順序,循環和分支判斷能力的計算過程還是圖靈完備的,也就是說goto的有無並不會影響計算能力。那麼goto的在程序中的作用便是弊大於利的。再加上goto的濫用會導致程序結構容易混亂,不利於程序員理解,這更得盡力避免。所以結構化編程限制了對程序直接轉移的控制權。

Pointer considered harmful

PointerConsideredHarmful

人人都知道面向對象編程有三大特徵:封裝,繼承和多態。

封裝是爲了構造抽象屏障(Abstract Barrier),到達隱藏信息的目的。任何編程範式都不會缺少封裝,因爲這是人的需求,是人類簡化問題認知的方式。

繼承是一種函數(過程或者API)複用的方式,以前我們想在多個結構相似的數據上使用同樣的函數,需要通過強制轉換到函數可接收的數據類型(結構體指針)上,這必然存在風險。面向對象的世界裏,我們不再需要手動強制轉換,只要通過顯式地表明繼承關係,編程語言就能在運行時自動做到這點。

多態(polymorphism)是一種將不同的特殊行爲和單個泛化記號相關聯的能力,和多態概念對應的參考實現——運行哪段代碼的決策叫做分派,大部分分派基於類型,也可以基於方法參數的個數及其類型,而分派的具體執行過程則仰仗函數指針。當作爲單個泛化記號的函數被聲明出來,它的具體實現可以多樣化。通過這樣的記號,事實上,我們解耦聲明和實現,而這種解耦的過程恰恰是通過函數指針間接地找到目標函數完成的。所以面向對象編程限制了對程序間接轉移的控制權。

Mutability considered harmful

MutabilityConsideredHarmful

Neal Ford在《函數式編程思想》(Functional Thinking)中提到面向對象編程是通過封裝可變因素控制複雜性(makes code understandable),而函數式編程是通過消除可變因素控制複雜性的。函數式的一個顯著的特點就是不可變性。不可變性意味着更多的內存消耗,更差的性能?其實不盡然。像Scala,Clojure這些基於JVM上的函數式編程語言大量使用了持久化結構(如:Persistent Vector,見腳註1),在不損失效率的前提下,實現了不可變的數據結構。這樣的數據結構在高併發的環境下具有非常巨大的優勢,尤其相對於面向對象編程中爲人所詬病的臨界區和競態條件。

不可變的數據結構是無法重複賦值的,所以函數式編程限制了對程序的賦值操作。

小結

鮑勃大叔一針見血地指出,我們過去50年學到的東西主要是——什麼不應該做。這等於給全書奠定了基調。可以類比,良好的架構也在傳達同樣的道理。

爲什麼從編程範式開始談起?在審閱完整本書之後,我慢慢發現鮑勃大叔其實在傳遞一種設計理念:架構設計裏,自頂向下的設計往往是不靠譜的。就像本書的目錄,從程序的基礎構件,談到組件,最後談到架構,這個過程非常符合系統自組織的特徵。

爲什麼自頂向下的設計往往不靠譜?本書的第4部分“組件構建原則”會有答案,有需要,且聽下回分解。


[1] 函數式編程簡介 於 2018-10-21

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章