概述#

架構債務(Architecture Debt)是技術債務中最昂貴且最難識別的一種形式。與程式碼層級的技術債務不同,架構債務的根因分散在多個檔案及其相互關係之中,難以被單一開發者察覺。本章提出一套系統化流程,利用三類資訊來識別與量化架構債務:

  • 原始碼:用於判定檔案之間的結構性相依(structural dependencies)
  • 版本控制歷史:用於判定程式碼單元的共同演化(co-evolution)關係
  • 議題追蹤資訊:用於判定變更的原因(bug 修復或功能增強)

此分析模型的核心思路是:找出架構中經歷異常高 bug 率和 churn(提交的程式碼行數)的區域,並嘗試將這些症狀與設計缺陷關聯起來。

判斷是否存在架構債務問題#

設計結構矩陣(DSM)#

本書採用 Design Structure Matrix(DSM) 來表示檔案之間的相依關係。DSM 是一種特殊的鄰接矩陣,在工程設計領域已使用數十年:

  • 行與列代表同一組檔案,以相同順序排列
  • 儲存格標註表示相依類型:繼承、呼叫、共同變更等
  • 若系統具有低耦合,DSM 應該是稀疏的(任一檔案僅依賴少數其他檔案)
  • 理想情況下,DSM 應為下三角(檔案僅依賴較低層級的檔案,無循環相依)

Figure 23.1: A DSM of Apache Camel showing structural dependencies

以 Apache Camel 專案為例,Figure 23.1 顯示 11 個檔案的結構性相依(標記為 “dp”、“im”、“ex” 分別代表 dependency、implementation、extension)。此矩陣相當稀疏,意味著這些檔案之間的結構耦合度不高。

疊加演化相依#

然而,當我們將版本控制系統中的歷史共同變更資訊疊加上去,情況就完全不同了。

Figure 23.2: A DSM of Apache Camel overlaying evolutionary dependencies

Figure 23.2 顯示了一幅截然不同的圖景:

  • 儲存格中的數字表示兩個檔案在提交記錄中共同變更的次數
  • 例如 row 8, column 3 標記為 “4”,代表 BeanExpression.javaMethodNotFoundException.java 之間沒有結構關係,但共同變更了 4 次
  • 矩陣變得非常密集,且許多標註出現在對角線上方,代表耦合是雙向的

該專案的架構師證實:幾乎每次變更都既昂貴又複雜,預測新功能完成時間或 bug 修復時程極具挑戰性。這正是高架構債務的典型表現。

發現熱點(Hotspots)#

當你懷疑程式碼庫存在架構債務時——也許 bug 率持續上升、功能開發速度持續下降——你需要找出具體的檔案及其有缺陷的關係。本章將這些對系統維護成本有過大貢獻的元素集合稱為熱點(Hotspots)。

六種常見架構反模式#

為了識別熱點,我們需要尋找導致高耦合和低內聚的反模式(anti-patterns)。以下六種反模式幾乎在每個系統中都會出現:

  • 不穩定介面(Unstable Interface):一個有影響力的檔案(代表重要服務、資源或抽象)與其依賴者頻繁共同變更。該「介面」檔案是其他系統元素使用該服務的入口點,因內部原因或 API 變更而頻繁修改。
  • 模組化違規(Modularity Violation):結構上解耦的模組卻頻繁共同變更。兩個或多個沒有結構相依的檔案,在修訂歷史中頻繁一起改動。
  • 不健康繼承(Unhealthy Inheritance):基底類別依賴其子類別,或客戶端類別同時依賴基底類別和其一個或多個子類別。
  • 循環相依 / 團塊(Cyclic Dependency / Clique):一組檔案緊密連接,形成強連通圖——任意兩個元素之間都存在結構相依路徑。
  • 套件循環(Package Cycle):兩個或多個套件彼此相依,而非形成應有的層級結構。
  • 交叉(Crossing):一個檔案同時擁有大量依賴者(高 fan-in)和大量被依賴者(高 fan-out),且與這些檔案頻繁共同變更。

反模式實例#

Figure 23.3: An example of a clique

Figure 23.3 展示了 Apache Cassandra 中一個 clique(循環相依)的例子:row 8 的 AbstractReplicationStrategy 依賴 file 4 的 WriteResponseHandler 並聚合 file 5 的 TokenMetadata,而 file 4 和 file 5 又反過來依賴 file 8,形成循環。

Figure 23.4: Architecture anti-patterns in Apache Cassandra

Figure 23.4 展示了不健康繼承的例子:SSTableReader(row 14)繼承自 SSTable(row 12),但父類別 SSTable 反過來呼叫子類別 SSTableReader。兩者在修訂歷史中共同提交了 68 次——這種過高的共同變更次數就是一種債務,可透過重構(將部分功能從子類別移回父類別)來消除。

並非熱點中的每個檔案都與其他所有檔案緊密耦合。實際上,一組檔案可能彼此緊密耦合,但與其他檔案解耦。每個這樣的集合都是一個潛在的熱點,也是透過重構進行債務消除的候選對象。

案例研究:量化架構債務#

以 SoftServe 公司的 SS1 系統為例,該系統包含 797 個原始檔案,分析期間涵蓋兩年的修訂歷史和議題記錄。

識別熱點結果#

  • 記錄了 2,756 個議題(其中 1,079 個為 bug),以及 3,262 次提交
  • 識別出三個最有害的反模式叢集,共涉及 291 個檔案(佔全部檔案的 36%)
  • 這三個叢集涵蓋了專案 89% 的缺陷(265 個中的 236 個)

量化債務與投資報酬#

由於分析結果非常具體,架構師能夠精確估算重構所需的工作量:

  1. 重構三個熱點的估計成本:14 人月
  2. 全專案每個檔案的年均 bug 修復次數:0.33
  3. 熱點檔案的年均 bug 修復次數:237.8
  4. 重構後的預估年均 bug 修復次數:96
  5. 估計年度節省:41.35 人月

以 14 人月的投入換取每年超過 41 人月的節省,投資報酬率極為可觀。作者在多個案例中都觀察到類似的回報,一旦架構債務被識別出來並加以償還,專案的功能開發速度和 bug 修復時間都會獲得可量化的改善。

自動化#

整個架構債務分析流程可以完全自動化,並整合到持續整合(CI)工具套件中,實現對架構債務的持續監控。所需的工具鏈包括:

  • 議題追蹤系統提取議題的工具
  • 版本控制系統提取日誌的工具
  • 對程式碼庫進行逆向工程以判定檔案間語法相依的工具
  • 建構 DSM 並遍歷尋找反模式的工具
  • 計算每個熱點相關債務的工具

其中,唯一需要特製的工具是建構和分析 DSM 的部分。專案通常已具備議題追蹤系統和版本歷史,且有大量逆向工程工具可供選擇(包括開源方案)。

債務消除策略#

識別出債務後,具體的重構策略取決於所發現的反模式類型:

  • 循環相依(Clique):移除或反轉某個相依關係,以打破循環
  • 不健康繼承:將功能從子類別移至父類別
  • 模組化違規:將檔案之間未封裝的共享「秘密」封裝為獨立的抽象

並非所有債務都值得償還。關鍵在於量化分析的結果——只有當重構的預期收益顯著超過成本時,才應該投入資源進行償還。本章提出的流程為向專案管理層提出重構商業論證提供了所需的量化數據。

本章小結#

本章提出了一套識別和量化架構債務的系統化流程。該流程整合來自議題追蹤系統、版本控制系統和原始碼本身的資訊,透過 DSM 識別架構反模式並將其分組為熱點,進而量化這些熱點的影響。此監控流程可自動化並整合至 CI 工具套件,為專案管理層提供做出重構決策所需的量化依據。