概述#
Modifiability(可修改性)關注的是對系統進行變更的成本與風險。幾乎每個軟體系統在其生命週期中都會經歷變更,而修改的成本通常遠高於初始開發。因此,理解如何為變更做好架構層級的準備至關重要。
要分析可修改性,需要回答四個核心問題:
- What can change?(什麼可以改變?):功能、平台、品質屬性、系統環境、容量、整合對象等。修改可發生在實作、編譯、建置、組態設定或執行期間。
- Who makes the change?(誰來修改?):開發者、終端使用者、系統管理員,甚至是系統本身(在具備學習與自適應能力的系統中)。
- What is the likelihood of the change?(變更可能性有多高?):無法預測所有變更,但可以估算可能性以評估哪些值得投入架構準備。
- What is the cost of the change?(變更成本是多少?):涉及兩類成本:引入可修改性機制的成本,以及使用該機制進行修改的成本。
對於 N 次類似修改,簡化的決策公式為:
N * 不使用機制的修改成本 <= 建立機制的成本 + (N * 使用機制的修改成本)但 N 是預測值。如果實際變更次數少於預期,昂貴的機制可能不划算。此外還需考慮時間因素與機會成本。然而,若不引入架構機制而只是不斷堆疊變更,通常會累積大量技術債(technical debt)。
可修改性的常見風味#
變更的普遍性催生了多種特定名稱:
- Scalability(可擴展性):容納更多負載的能力。包含 horizontal scalability(水平擴展,scaling out)與 vertical scalability(垂直擴展,scaling up)。在雲端環境中,水平擴展稱為 elasticity。
- Variability(可變性):系統及其支援產出物以預先規劃的方式產生一組變體的能力。在 software product line(軟體產品線)中尤為重要,透過共享工程資產大幅降低產品線整體成本。
- Portability(可移植性):將軟體從一個平台遷移到另一個平台的難易程度。透過最小化平台相依性、隔離相依性到明確位置、以及在虛擬機器(如 JVM)上執行來達成。
- Location independence(位置獨立性):分散式軟體互動時,一方或雙方的位置在執行期前未知或在執行期間變更。設計位置獨立性意味著位置變更對系統其餘部分的影響最小。
General Scenario#
Table 8.1 描述了 modifiability 的通用情境:
| 情境部分 | 說明 |
|---|---|
| Source | 終端使用者、開發者、系統管理員、產品線擁有者、系統本身 |
| Stimulus | 新增/刪除/修改功能;變更品質屬性、容量、平台或技術;新增產品線成員;變更服務位置 |
| Artifact | 程式碼、資料、介面、元件、資源、測試案例、組態、文件 |
| Environment | 執行期、編譯期、建置期、初始化期、設計期 |
| Response | 進行修改、測試修改、部署修改、自我修改 |
| Response Measure | 受影響產出物的數量/大小/複雜度、工作量、時間、金額、對其他功能或品質屬性的影響、引入的新缺陷、系統自適應所需時間 |

Figure 8.1: Sample concrete modifiability scenario
上圖呈現一個具體情境範例:開發者希望在設計階段變更使用者介面,目標是在 3 小時內完成變更與測試,且不產生副作用。
Tactics for Modifiability#
可修改性策略的目標是控制進行變更的複雜度、時間與成本。

Figure 8.2: Goal of modifiability tactics
要理解這些策略,必須先掌握軟體設計中最基本的複雜度度量:
- Coupling(耦合):兩個模組之間修改傳播的機率。高耦合是可修改性的敵人。
- Cohesion(內聚):模組內部職責的關聯程度。高內聚代表變更情境影響同一模組內多個不同職責的機率低。
- Size(大小):在其他條件相同的情況下,較大的模組更難修改且更容易有缺陷。
- Binding time(綁定時間):變更發生在軟體生命週期的哪個階段。架構若經過適當準備,能讓變更在較晚階段以較低成本進行。
策略的效果可歸結為影響這四個參數:縮小模組大小、提高內聚、降低耦合、延遲綁定時間。

Figure 8.3: Modifiability tactics
Increase Cohesion(提高內聚)#
透過重新分配職責來降低單一變更影響多個模組的可能性:
- Split Module(拆分模組):若被修改的模組包含不具內聚性的職責,修改成本通常很高。將模組重構為數個更具內聚性的子模組,可降低未來變更的平均成本。拆分不應只是機械地將程式碼對半分,而應產出各自具有內聚性的子模組。
- Redistribute Responsibilities(重新分配職責):若相似的職責 A、A’、A’’ 散落在多個不同模組中,應將它們集中。可透過假設可能的變更情境來識別應搬移的職責——若情境持續只影響模組的某個部分,其他部分可能應被搬移;若情境需要修改多個模組,則被影響的職責應歸併到新模組。
Reduce Coupling(降低耦合)#
這些策略與 Chapter 7 的整合性策略有大量重疊,因為降低獨立元件間的相依性(integrability)與降低模組間的耦合(modifiability)本質相似:
- Encapsulate(封裝):引入明確介面,隔離功能,確保外部僅透過介面存取。詳見 Chapter 7。
- Use an Intermediary(使用中介):在高度耦合的模組之間放置中介。若 A 直接呼叫具體功能 C,可引入抽象 B 來調和 A 與 C 之間的關係。詳見 Chapter 7。
- Abstract Common Services(抽象共同服務):對相似服務提供通用抽象介面。常用於實現跨作業系統或硬體的可移植性。詳見 Chapter 7。
- Restrict Dependencies(限制相依性):系統性地限制模組之間的互動或相依關係。實務上透過可見性限制與授權機制實現。典型應用包括 layered architecture(僅允許使用較低層)與 wrapper(外部只能看到並依賴 wrapper)。
Defer Binding(延遲綁定)#
由於人力工作幾乎總是比電腦處理更昂貴且容易出錯,讓電腦盡可能處理變更通常會降低成本。設計具有內建彈性的產出物,運用該彈性通常比手動撰寫特定變更便宜。
一般而言,在生命週期中越晚綁定值越好。但建立晚期綁定機制的成本通常較高,這是一個經典的權衡。我們希望盡可能晚綁定,前提是其機制符合成本效益。
編譯期/建置期綁定策略:
- Component Replacement(元件替換):例如在建置腳本或 makefile 中替換元件
- Compile-time Parameterization(編譯期參數化)
- Aspects(面向切面)
部署期/啟動期/初始化期綁定策略:
- Configuration-time Binding(組態期綁定)
- Resource Files(資源檔案)
執行期綁定策略:
- Discovery(發現):詳見 Chapter 7
- Interpret Parameters(解譯參數)
- Shared Repositories(共享儲存庫)
- Polymorphism(多型)
建立可修改性機制與使用該機制進行修改可能涉及不同的利害關係人——通常由開發者提供機制,由管理員或安裝者在後續生命週期階段使用。安裝機制使他人可以在不修改程式碼的情況下變更系統,有時稱為 externalizing the change。
Tactics-Based Questionnaire#
Table 8.2 提供了基於策略的可修改性問卷:
| 策略群組 | 關鍵問題 |
|---|---|
| Increase Cohesion | 是否透過拆分模組來提高內聚性?是否透過重新分配職責使同一目的的職責歸併? |
| Reduce Coupling | 是否一致地封裝功能並引入明確介面?是否使用中介防止模組過度耦合?是否系統性地限制模組間相依?是否在提供相似服務時抽象共同服務? |
| Defer Binding | 系統是否定期延遲重要功能的綁定以便在生命週期後期替換?例如:是否有 plug-in、add-on、resource file 或 configuration file 可擴展系統功能? |
Patterns#
Client-Server Pattern#
Client-Server 模式由伺服器同時為多個分散式客戶端提供服務。互動流程包含兩階段:
- Discovery:由客戶端發起,使用 discovery service 定位伺服器;伺服器以協定回應
- Interaction:客戶端發送請求,伺服器處理並回應
關鍵考量:
- 若伺服器對客戶端是 stateless,每個請求被獨立處理
- 若伺服器 maintains state,請求需識別客戶端,且需處理工作階段結束與超時機制
優點:連線動態建立、伺服器無需事先知道客戶端(低耦合)、客戶端間無耦合、容易擴展、客戶端與伺服器可獨立演進、共同服務可共享、使用者互動隔離於客戶端
權衡:網路通訊可能因壅塞導致效能不穩、透過共享網路通訊時需特別處理安全性(尤其是機密性與完整性)
Plug-in (Microkernel) Pattern#
Plug-in 模式有兩類元素:提供核心功能的元素,以及透過固定介面擴展核心功能的專用變體(plug-in)。兩者通常在建置期或更晚綁定。
應用範例:
- 精簡的作業系統(microkernel)提供底層機制,plug-in 提供實際 OS 功能(如裝置驅動、任務管理)
- 核心產品提供使用者服務,plug-in 提供可移植性(如 OS 相容性)、額外功能或與外部系統的整合
優點:提供受控的擴展機制、不同團隊或組織可開發 plug-in、plug-in 可獨立演進
權衡:因 plug-in 可由不同組織開發,更容易引入安全漏洞與隱私威脅
Layers Pattern#
Layers 模式將系統劃分為層,每層是提供內聚服務集合的模組分組。層之間的 allowed-to-use 關係必須是單向的——上層可使用下層的公開設施,但反向使用不被允許。
- Layer bridging:上層直接使用非相鄰下層的情況,通常不建議但有時必要
- 層完全分割軟體,每個分割區透過公開介面暴露
優點:下層變更不影響上層(只要介面不變)、下層可跨應用重用(如 OS、網路通訊軟體)、團隊需理解的介面數量減少
權衡:若分層設計不當,可能無法提供高層所需的底層抽象、層間呼叫可能造成效能損耗、過多的 layer bridging 會削弱可移植性與可修改性目標
Publish-Subscribe Pattern#
Publish-Subscribe 模式中,元件主要透過非同步訊息通訊。發布者不知道訂閱者,訂閱者只知道訊息類型。系統依賴 implicit invocation——發布訊息的元件不直接呼叫其他元件。
三類元素:
- Publisher component:發送(發布)訊息
- Subscriber component:訂閱並接收訊息
- Event bus:管理訂閱與訊息分派,屬於執行期基礎設施
優點:
- 發布者與訂閱者獨立且鬆散耦合,新增或變更訂閱者只需註冊事件
- 透過變更訊息的事件或主題即可輕鬆改變系統行為,可能啟用或停用功能
- 事件可輕鬆記錄,支援 record-and-playback 以重現錯誤
權衡:
- 某些實作可能影響效能(latency)
- 元件可能無法確定收到已發布訊息所需的時間;系統效能與資源管理更難推理
- 可能影響同步系統的確定性(determinism),方法呼叫順序可能因實作而異
- 可能影響可測試性(testability),event bus 的微小變更可對系統行為產生廣泛影響
- 某些實作限制安全性(integrity)的彈性實現——由於發布者不知訂閱者身份,端到端加密受限
Publish-Subscribe 模式雖然對可修改性極為有利,但在可測試性、效能確定性與安全性方面存在顯著權衡。採用此模式時應仔細評估這些面向。
小結#
Modifiability 的核心是透過提高內聚、降低耦合、延遲綁定三大策略群組來控制變更的複雜度與成本。耦合與內聚是自 1960 年代以來最基本的軟體設計複雜度度量。架構師應在預期變更的成本效益分析基礎上,選擇適當的策略與模式,在「前期投入」與「未來修改成本」之間取得平衡。