架構設計的基本準則是非常重要的,它們指導著我們如何構建可靠、可維護、可測試的系統。下面是這些準則的轉換表達方式:
簡單即美(KISS):KISS原則的核心思想是保持簡單。在設計系統之前,首先要正確理解系統需求,然后才進行設計。要避免過度設計,除非有人能承擔復雜性的成本。這里的“簡單”強調易實施性和易理解性。接口應該自然地表達語義,讓人一看方法名就能理解其功能。
模塊化(Modularity):模塊化強調的是將系統分解成互相獨立的模塊。從架構設計的角度來看,模塊的接口比實現更為重要。我們應該專注于模塊而不是框架,因為框架是易變的,而模塊是更加穩定和可復用的。設計模塊時,應忽略框架的存在,專注于模塊的接口設計,并確保接口足夠通用。
可測試性(Testable):設計應該以可測試性為第一目標。可測試性通常意味著低耦合,因為低耦合的模塊更容易進行單元測試。模塊測試的第一步是創建環境模擬,即模擬模塊所依賴的其他模塊。測試能夠幫助我們發現架構調整的潛在問題,并且在代碼重構時尤其重要。
正交分解(Orthogonal Decomposition):正交分解是指對系統進行獨立且相互無關的分解過程。這個原則強調的是乘法而不是加法,即組合而不是繼承。通過組合相互獨立、沒有相關性的模塊,可以構建出我們所需的業務場景,而不是通過繼承疊加能力來改造模塊。
核心系統的傷害值
正交分解首先涉及確定核心系統和周邊子系統。核心系統是業務的最小功能集,而周邊子系統則通過逐步增加新功能來擴展系統的功能。對核心系統的變更必須謹慎對待。如果某個新功能在早期未被規劃,后來又被確定為核心功能,我們必須認真評估其對現有架構的影響。周邊功能方面,我們關注的是如何降低添加新功能對核心系統的影響。無論情況如何,系統都會因功能增加而變得復雜。為了減少新功能的負面影響,相關代碼應盡可能地內聚,即使不寫入獨立的模塊中,也要放在獨立的文件中。這些代碼被視為周邊系統的功能實現代碼,而不是核心系統的一部分。我們關注的是周邊功能對核心系統的影響。為了添加某個功能,核心系統需要添加相關代碼。根據經驗,核心系統為新功能添加的代碼量越少,該功能與核心系統的耦合度就越低。是否可能添加功能而不修改核心系統的代碼?這是可能的,但需要核心系統提供插件機制。
我們將在后續討論這個話題,現在暫且擱置。讓我們把話題轉回到架構設計質量的評估上。雖然我們已經討論了一些架構設計的基本準則,但尚未涉及質量評估的方法。質量評估可以是定性的或定量的。定性評估方法有一定的數據支持,但可能有些主觀。例如,“從某個角度來看,我感覺這個更好”。定量評估方法更理想,但目前我個人尚未聽說過任何用于確定架構設計優劣的定量評估方法。今天我會介紹一些我個人想出的判斷公式。這些公式都是經驗性的,并沒有經過嚴格的數學證明。假設一個架構設計方案將系統分成了n個模塊,表示為:[M1, M2, ..., Mn]。其中M1是核心系統,其他模塊是周邊子系統。為簡化起見,假設周邊子系統之間是正交的,相互沒有耦合。
模塊的耦合度測量
我們第二個關注的問題是每個模塊自身的質量,包括模塊接口的質量和模塊實現的質量。首先,我們來看模塊接口的質量,這是模塊級別最重要的部分。模塊接口的質量取決于以下兩個方面:
接口與業務的匹配性:接口應盡可能自然地反映業務需求。然而,從機器判斷的角度來看,這一點是無法計算的,完全取決于個人主觀判斷。我們將在下一講“少談框架,多談業務”中繼續探討這個話題。
接口的外部依賴:即模塊接口對外部環境的耦合程度。下面我們將介紹模塊的“耦合度測量公式”,它同時適用于模塊實現和模塊接口的耦合度測量。
假設我們的模塊實現(或模塊接口)依賴了模塊A,那么我們的模塊實現(或模塊里的“符號”是指被引用的類型,包括typedef(類型別名)、class或struct,以及被引用的全局變量、全局函數或成員函數。
接下來,我們看模塊實現(或模塊接口)的所有外部依賴,即該模塊的總耦合度公式為其中,耦合度A表示該模塊與依賴模塊A的耦合程度,如前文所述。而不成熟度系數A則表示依賴模塊A的不成熟度程度。若依賴模塊A完全成熟,不再發生變化,則為0;若發生非常劇烈的變動,規格甚至無法確定,則為1。
通過該耦合度測量公式,我們鼓勵依賴外部成熟模塊。理論上,完全成熟的模塊可能僅限于語言內置的數據類型(如int、string等)。其他模塊則多多少少會受到一些變化的影響,因此我們應盡量減少外部依賴。
需要注意的是,將模塊接口引用的類型A改為object或interface{}類型并不能降低耦合度。換句話說,如果某參數為interface,那么這個interface的耦合度取決于實際使用時存在的各種可能類型,都會計算在依賴中。
關于耦合度測量公式,需要強調的是,它是一種經驗公式,僅代表某種價值主張。在實際應用中,計算得到的具體耦合度值并沒有物理意義,只能用于比較兩個相同功能的系統(或模塊)的架構設計方案。對于兩個功能完全不同的系統(或模塊)A、B,其計算結果不能用于評判彼此的好壞。
首先,我們討論了架構設計的基本準則,它們為我們提供了一個方向。雖然這些準則不能明確指出何為好與不好,但它們指明了我們設計架構的方向。接著,我們開始對架構的優劣進行定性甚至定量的分析。考慮到核心系統的重要性,我們引入了一個傷害值來評估其純潔度。
最后,我們針對模塊自身的接口或實現,給出了耦合度測量公式。通過這些公式,我們明確了我們的架構設計的價值主張。然而,需要意識到的是,這些并不是全部。判斷模塊間的耦合度是復雜的。我們的公式在某種程度上只考慮了靜態依賴關系,而沒有考慮動態依賴。舉例來說,考慮兩個網絡模塊A和B,一個顯而易見的耦合度判斷是:A調用B的網絡接口數量越多,說明它們之間的依賴越大;而A調用B的網絡接口的次數越多,也意味著它們之間的依賴越大。