本章的核心論點是:Model 與 Implementation 必須緊密綁定。如果 domain model 與程式碼之間脫節,model 將淪為無用的文件,而程式碼也將失去概念上的清晰度。Evans 提出 Model-Driven Design 作為解決方案,並透過 PCB 佈線工具的實際案例,展示從程序式腳本到模型驅動設計的轉變。

Model-Driven Design#

Analysis Model 的陷阱#

許多專案採用一種常見但有問題的方法:由分析師建立 analysis model,再由開發者另外建立 design。這兩者之間往往只有鬆散的對應關係,造成嚴重後果:

  • 知識流失:分析階段透過 knowledge crunching 獲得的洞見,在進入實作時大量流失,因為開發者被迫重新建立自己的抽象
  • 模型過時:一旦開始寫程式,analysis model 很快就被拋棄,因為它不考慮實作可行性
  • 洞見斷裂:設計/實作過程中總會出現預料之外的發現,但這些發現無法回饋到 analysis model 中
  • 分析不足:analysis model 會深入某些不相關的主題,卻忽略真正重要的議題

如果 design(或其核心部分)無法對應到 domain model,那麼 model 幾乎沒有價值,軟體的正確性也令人存疑。反之,model 與 design 之間過於複雜的對映關係同樣無法維護。

Model-Driven Design 的核心主張#

Model-Driven Design 拋棄 analysis model 與 design model 的二分法,追求一個同時滿足兩種目的的單一模型

  • 每個 design 中的物件都扮演 model 中描述的概念角色
  • 從 model 中提取 design 所用的術語與職責分配
  • 程式碼成為 model 的表達——改變程式碼就等於改變 model,其影響必須波及整個專案
  • Modeling 與 design 的過程合併為單一的迭代循環

設計軟體系統的某個部分,使其以非常直白的方式反映 domain model。同時也要修正 model,使其能更自然地在軟體中實現——即使在這個過程中,仍然要持續追求對 domain 更深層的理解。

flowchart TD
    subgraph traditional["傳統 Analysis Model"]
        direction LR
        A1["領域知識"] -.-> A2["Analysis Model"]
        A2 -.-> A3["Design Model"]
        A3 -.-> A4["Code"]
    end

    subgraph mdd["Model-Driven Design"]
        direction LR
        B1["領域知識"] <--> B2["單一模型"]
        B2 <--> B3["Code"]
    end

    traditional ~~~ mdd

    style traditional fill:#fff3e0,stroke:#e65100
    style mdd fill:#e8f5e9,stroke:#2e7d32

這個方法對模型的要求更高:它必須同時是良好的分析工具可行的設計基礎。當一個 model 不適合實作時,就必須尋找新的 model;當 model 無法忠實表達 domain 的關鍵概念時,同樣必須重新尋找。

Modeling Paradigms and Tool Support#

要讓 Model-Driven Design 發揮價值,model 與 design 之間的對應必須是精確的、字面上的。這幾乎必然要求使用支援特定 modeling paradigm 的語言和工具。

Object-Oriented Programming#

Object-oriented programming 之所以強大,是因為它本身就基於一種 modeling paradigm:

  • 物件確實存在於記憶體中,擁有與其他物件的關聯
  • 物件被組織成 class,提供可透過訊息呼叫的行為
  • Java 等語言允許直接建立與概念物件模型相類比的物件與關係

OOP 的真正突破不在於用物件來組織程式碼,而在於程式碼表達了 model 的概念

Procedural Languages 的限制#

純粹的 procedural language(如 C)難以支援 Model-Driven Design,因為:

  • 程式只是一系列技術性的資料操作步驟,無法捕捉 domain 的意義
  • 雖然支援複雜資料型別,但這些只是組織化的資料,無法捕捉 domain 的主動面向
  • 功能之間是基於預期的執行路徑連結,而非基於 domain model 中的概念關聯

Evans 提到 Prolog 作為另一個適合 Model-Driven Design 的例子——其 paradigm 是邏輯推理,model 則是一組邏輯規則與事實。重點不在於特定語言,而在於語言是否支援某種 modeling paradigm。

範例:從 Procedural 到 Model-Driven#

Evans 以 PCB(printed circuit board)佈線工具為例,展示兩種截然不同的設計方式。

問題背景#

PCB 上有數以萬計的 net(電氣導體連接),每條 net 都有自己的佈線規則。工程師將多條 net 視為屬於同一個 bus(例如 8、16 或 256 條 net 組成一組),希望能以 bus 為單位指派規則。但佈線工具沒有 bus 的概念,規則必須逐條 net 指派。

Figure 3.2: An explanatory diagram of buses and nets

Mechanistic Design(程序式腳本)#

工程師撰寫腳本來解決這個問題,其做法是:

  1. 依 net name 排序 net list 檔案
  2. 利用命名慣例(名稱字首相同的 net 屬於同一個 bus),逐行解析檔案
  3. 將 bus 規則展開,逐條寫入每條 net 的 rules 檔案

這種做法的問題:

  • 只是檔案操作,沒有明確處理 bus 的概念
  • 換一種檔案格式就必須從頭來過
  • 要加入更豐富的功能或互動性,代價極高
  • 唯一的測試方式是端對端的檔案比對

Model-Driven Design#

將 domain 概念明確地組織成模型,以物件導向語言實作:

Figure 3.3: A class diagram oriented toward efficient assignment of layout rules

核心實作變得幾乎是 trivial 的:

  • AbstractNet 持有 rules,提供 assignRule()assignedRules()
  • Net 繼承 AbstractNet,其 assignedRules() 合併自身規則與所屬 Bus 的規則
  • Bus 同樣繼承 AbstractNet,可以直接指派規則

搭配的 services 與 utilities:

元件職責
Net List Import Service讀取 net list 檔案,為每筆資料建立 Net 實例
Net Rule Export Service將所有 Net 的規則完整展開寫入 rules 檔案
Net Repository依名稱存取 Net
Inferred Bus Factory利用命名慣例從 Net 集合推斷出 Bus,建立實例
Bus Repository依名稱存取 Bus

這種設計的優勢:

  • 概念明確:Bus 作為一個明確的 domain object 存在,而非透過排序和字串比對推斷
  • 可測試:每個元件都有明確定義的介面,可進行 unit test;核心 domain logic 也可獨立測試
  • 可擴展:Model-Driven Design 可以輕鬆加入規則組合的 constraints 或其他增強功能
  • 格式無關:核心邏輯不依賴特定檔案格式

這樣的設計不會一步到位。它需要多次迭代的 refactoring 與 knowledge crunching,才能將 domain 中的重要概念提煉為簡潔而深刻的 model。

Letting the Bones Show:為什麼 Model 對使用者也重要#

Evans 以 Internet Explorer 的「我的最愛」(Favorites)功能為例,說明 user model 與 implementation model 不一致時的問題:

  • 使用者認為 Favorite 是一個網站名稱的清單
  • 實際上 Favorite 是一個包含 URL 的檔案,檔名就是顯示的名稱
  • 當網頁標題包含 Windows 檔名不允許的字元(如 :)時,系統會顯示令人困惑的錯誤訊息,或靜默移除這些字元

解決方式有兩種,但都指向同一個原則——維持單一模型

  1. 暴露真實模型:讓使用者知道 Favorites 就是捷徑檔案,使用者可以利用對檔案系統的既有認知來管理
  2. 改變實作模型:用資料檔案儲存 Favorites,讓它遵守網頁命名規則而非檔案命名規則

當 design 基於一個反映使用者與 domain experts 核心關切的 model 時,這個 design 的「骨架」可以更大程度地暴露給使用者,帶來一致且可預測的行為。

Hands-On Modelers#

Evans 挑戰了軟體開發中常見的「製造業隱喻」——由高技能工程師設計,低技能工人組裝。他指出:軟體開發全部都是設計

Evans 的親身經歷#

Evans 描述了一個他負責協調多個應用團隊並開發 domain model 的專案:

  • 管理層認為 modeler 就應該做 modeling,寫程式是浪費技能,因此禁止他參與實作
  • 他與 domain experts 和各團隊開發主管合作,提煉出一個精良的核心 model
  • 但這個 model 從未被真正落實,原因有二:
    1. 意圖在交接中流失:model 的整體效果對細節非常敏感,而這些細節無法僅靠 UML 圖或一般性討論傳達
    2. 回饋太間接、太慢:model 在特定技術平台上效率極差的問題,隔了好幾個月才傳回給他;等到那時,開發者已經拋棄 model,自行寫出能運作的程式

分離 Modeler 與 Implementation 的危害#

  • 寫程式的人如果不覺得自己對 model 負責,或不知道如何讓 model 為應用服務,那麼 model 就與軟體無關
  • 開發者如果不意識到修改程式碼等於修改 model,其 refactoring 將削弱而非強化 model
  • 脫離實作的 modeler 會失去對實作限制的直覺,產出不切實際的 model
  • 資深設計者的知識與技能無法傳遞給其他開發者

Hands-On Modelers 並不意味著團隊成員不能有專門的角色。問題出在將 modeling 與 implementation 這兩個在 Model-Driven Design 中緊密耦合的任務強行分離。任何寫程式碼的人都必須在某種程度上參與 modeling,任何負責 modeling 的人也必須參與到程式碼中。