架構整潔之道導讀(一)
我是《架構整潔之道》( Clean Architecture ) 中文版的技術審校者,在審校的過程當中略有感悟,所以希望通過撰寫導讀的方式分享給大家。
書名的由來
《架構整潔之道》是 Clean Architecture 的中文譯名。看似簡單地延續了《程式碼整潔之道》( Clean Code )的翻譯傳統,但事實上,對於取中文名字這件事,我們還是花了不少氣力的。拿到譯文初稿時,編輯提供了幾個備選的譯名:《架構簡潔之道》,《架構至潔》和《Clean Architecture》,這些名字各有各的考量,在沒有了解這本書的核心思想之前,我也沒有辦法給出恰當的判斷。所以在通讀了原作和譯作之後,我在ThoughtWorks諮詢群裡發起提案,討論的過程很精彩,最終在骨灰級架構師新哥的建議下,結果大致趨向了整潔架構。
新哥說:“整本書在說依賴治理(管理),也就是如果降低依賴複雜度,和DDD中分離子域分層架構等想法是一致的;如同你整理你的房間,把東西分門別類放好,從這個角度,整齊比簡單更合適,或者清晰也可。”
除此之外,對於《架構至潔》這個候選項,大魔頭的態度是不要至潔,總感覺髒髒的。言下之意,自行體會。而讀MBA的嶽嶽和XR(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的擁躉,我也不例外。可是鮑勃大叔卻娓娓道來,所謂程式設計正規化不過是約束程式的執行,告訴我們什麼不能做而已。
- 結構化程式設計是對程式控制權的直接轉移的規範和限制
- 面向物件程式設計是對程式控制權的間接轉移的規範和限制
- 函數語言程式設計是對程式賦值操作的規範和限制
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)是 ofollow,noindex">一種將不同的特殊行為和單個泛化記號相關聯的能力 ,和多型概念對應的參考實現——執行哪段程式碼的決策叫做分派,大部分分派基於型別,也可以基於方法引數的個數及其型別,而分派的具體執行過程則仰仗函式指標。當作為單個泛化記號的函式被宣告出來,它的具體實現可以多樣化。通過這樣的記號,事實上,我們解耦宣告和實現,而這種解耦的過程恰恰是通過函式指標間接地找到目標函式完成的。所以面向物件程式設計限制了對程式間接轉移的控制權。
Mutability considered harmful

MutabilityConsideredHarmful
Neal Ford在《函數語言程式設計思想》( Functional Thinking )中提到面向物件程式設計是通過封裝可變因素控制複雜性(makes code understandable),而函數語言程式設計是通過消除可變因素控制複雜性的。函式式的一個顯著的特點就是不可變性。不可變性意味著更多的記憶體消耗,更差的效能?其實不盡然。像Scala,Clojure這些基於JVM上的函數語言程式設計語言大量使用了持久化結構(如:Persistent Vector,見腳註1),在不損失效率的前提下,實現了不可變的資料結構。這樣的資料結構在高併發的環境下具有非常巨大的優勢,尤其相對於面向物件程式設計中為人所詬病的臨界區和競態條件。
不可變的資料結構是無法重複賦值的,所以函數語言程式設計限制了對程式的賦值操作。
小結
鮑勃大叔一針見血地指出,我們過去50年學到的東西主要是—— 什麼不應該做 。這等於給全書奠定了基調。可以類比,良好的架構也在傳達同樣的道理。
為什麼從程式設計正規化開始談起?在審閱完整本書之後,我慢慢發現鮑勃大叔其實在傳遞一種設計理念:架構設計裡,自頂向下的設計往往是不靠譜的。就像本書的目錄,從程式的基礎構件,談到元件,最後談到架構,這個過程非常符合系統自組織的特徵。
為什麼自頂向下的設計往往不靠譜?本書的第4部分“元件構建原則”會有答案,有需要,且聽下回分解。
[1]函數語言程式設計簡介
於 2018-10-21

架構整潔之道