PCB 設計工具的故事#

本章以作者開發印刷電路板(PCB)設計工具的親身經歷作為開場。作者對電子硬體一無所知,卻必須在期限內交付軟體。這段經歷生動展示了 Knowledge Crunching 的完整過程。

初期困境#

  • PCB 設計師試圖直接告訴開發者軟體該做什麼,但他們提出的方案大多是「讀入 ASCII 檔案、排序、輸出報表」之類的流程,無法帶來真正的生產力突破
  • 前幾次會議令人沮喪,但報表需求中反覆出現的 Net(線路導體)概念成為突破口——這是 Domain Model 的第一個元素

對話驅動的模型演化#

作者開始用非正式的物件互動圖(object interaction diagrams)與領域專家一起走過使用情境。在反覆對話中:

  • 領域專家不斷糾正術語,例如「components」應為 Component Instances,而 Ref-des 只是特定工具中對 Component Instance 的稱呼
  • 每個 Net 連接的是特定 Component Instance 的特定 Pin,而非整個元件
  • 每個 Net 具有 Topology(拓撲),決定 Net 中各元素的連接方式

Probe Simulation 情境#

為聚焦探索,團隊選定了一個具體功能——Probe Simulation(探針模擬),追蹤訊號傳播以偵測設計問題:

  • 訊號透過 Net 傳送到所有連接的 Pin
  • Component 將訊號從某些 Pin「推送」(push)到其他 Pin
  • 每次訊號經過一個 Net 算一個 hop,超過兩到三個 hop 的路徑可能導致訊號延遲
  • Push-through 的行為由 Component 的類型決定,同一類型的所有 Instance 共用相同的 push 規則

原型的價值#

作者在幾天內用 Java 撰寫了一個極簡原型:

  • 沒有持久層、沒有 UI,只專注在行為邏輯
  • 使用自動化測試框架驅動
  • Java 物件直接反映了開發者與領域專家共同建立的模型

原型的具體性讓領域專家更清楚模型的意義以及它如何對應到運作中的軟體。從此,模型討論變得更加互動——專家可以透過原型的回饋來檢驗自己的想法。

模型中蘊含的知識#

最終模型中包含了與問題相關的 PCB 領域知識,同時:

  • 統一了大量同義詞和描述上的微小差異
  • 排除了數百個工程師知道但與當前問題無直接關聯的事實
  • 讓軟體專家能在幾分鐘內理解軟體的核心內容

有效建模的要素#

作者從 PCB 專案的成功經驗中歸納出五個關鍵要素:

  1. 綁定模型與實作(Binding the model and the implementation)——早期的粗糙原型建立了模型與程式碼之間的關鍵連結,並在後續所有迭代中持續維護
  2. 培養基於模型的語言(Cultivating a language based on the model)——隨著專案推進,團隊成員可以直接使用模型中的術語組成句子,無需翻譯即可被明確理解
  3. 發展知識豐富的模型(Developing a knowledge-rich model)——模型中的物件具有行為並執行規則,不僅是資料結構,而是解決複雜問題的核心
  4. 精煉模型(Distilling the model)——重要的概念被加入,同樣重要的是不必要的概念被移除。當無用概念與有用概念糾纏時,需要找到新的模型來區分本質
  5. 腦力激盪與實驗(Brainstorming and experimenting)——語言加上草圖和腦力激盪的態度,將討論變成「模型的實驗室」,口語表達本身就是模型可行性的快速測試
mindmap
  root((有效建模))
    綁定模型與實作
    培養以模型為基礎的語言
    開發知識豐富的模型
    蒸餾模型
    腦力激盪與實驗

正是腦力激盪的創造力與大量實驗,搭配基於模型的語言,並透過實作的回饋迴路加以約束,才能找到知識豐富的模型並加以精煉。

Knowledge Crunching#

Knowledge Crunching(知識消化)的概念類似於財務分析師「消化數字」——從大量細節中篩選、組合,找出真正重要的底層意義。

知識消化的本質#

  • 不是一個人的獨立作業,而是開發者與領域專家的協作過程,通常由開發者主導
  • 原始素材來自:領域專家的腦中知識、現有系統使用者的經驗、技術團隊過去的相關經驗、專案文件、以及大量的對話
  • 早期版本或原型會將經驗回饋給團隊,改變原有的詮釋

兩種失敗模式#

瀑布式方法的失敗:

  • 業務專家 → 分析師 → 程式設計師,知識單向流動
  • 分析師獨自負責建模,沒有機會從程式設計師身上學習,也無法從早期軟體版本獲得回饋
  • 知識只往一個方向流動,無法累積

缺乏抽象的迭代方法:

  • 開發者請專家描述功能,開發完成後展示結果,再問下一步
  • 如果開發者對領域不感興趣,只學到應用程式「該做什麼」,而非背後的原則
  • 這種方式可以產出堪用的軟體,但永遠無法達到「新功能自然從舊功能推導而出」的境界

即使程式設計師自發地開始抽象和建模,如果缺乏與領域專家的協作,產出的概念往往是膚淺的(naive),軟體會缺乏與領域專家思維方式的深層連結。

知識消化的正向循環#

當團隊成員共同消化模型時:

  • 開發者被迫學習業務的核心原則,而非機械地生產功能
  • 領域專家被迫將所知提煉為本質,並理解軟體專案所需的概念嚴謹度
  • 模型持續改善,成為組織專案中持續湧入資訊的工具
  • 模型聚焦需求分析、與程式設計和設計緊密互動,形成良性循環
flowchart TD
    subgraph W["Waterfall 失敗模式"]
        direction TB
        W_E[領域專家] -->|"單向傳遞"| W_A[分析師]
        W_A -->|"知識流失"| W_P[程式設計師]
    end

    subgraph I["無抽象迭代"]
        direction TB
        I_E[領域專家] -->|"描述功能"| I_D[開發者]
        I_D -->|"展示成果"| I_Demo[Demo]
        I_Demo -->|"表面回饋"| I_E
    end

    subgraph K["Knowledge Crunching"]
        direction TB
        K_D[開發者] <-->|"雙向學習"| K_E[領域專家]
        K_E <-->|"提煉知識"| K_M[領域模型]
        K_M <-->|"模型精煉"| K_D
    end

Continuous Learning#

知識流失的問題#

  • 專案開始時,知識是分散的、破碎的,散佈在許多人和文件中
  • 即使看似不太技術性的領域也可能充滿我們不知道的未知
  • 專案會流失知識:懂得的人離開、團隊重組打散知識、關鍵子系統外包時只交付程式碼而非知識
  • 傳統設計方法下,程式碼和文件無法以可用的形式表達辛苦獲得的知識

持續學習的實踐#

高生產力的團隊有意識地培養知識,實踐持續學習(Continuous Learning):

  • 開發者需要提升技術知識和通用的領域建模技能
  • 同時也需要認真學習目前正在處理的特定領域
  • 自我教育的團隊成員形成穩定核心,專注於最關鍵的開發任務
  • 累積的知識讓這些核心成員成為更有效的 Knowledge Cruncher

作者在 PCB 專案中,Probe Simulation 最終被發現是低優先級功能而被捨棄,相關的模型部分也隨之消失。但早期的工作仍然至關重要——它啟動了 Knowledge Crunching 的過程,建立了共享語言和透過實作的回饋迴路。探索之旅必須有個起點。

Knowledge-Rich Design#

模型中捕捉的知識遠不只是「找出名詞」。業務活動和規則與相關的實體同樣是領域的核心。

隱含概念的提取:Overbooking 範例#

以一個貨物訂艙系統為例。最初的模型很簡單:將 CargoVoyage 關聯起來。

需求文件中有一行:「允許 10% 的超額訂艙(overbooking)」。Overbooking 是航運業的標準做法——因為總會有臨時取消,所以會接受超過船舶實際容量的貨物。

第一版實作將規則寫成一個 guard clause:

public int makeBooking(Cargo cargo, Voyage voyage) {
    double maxBooking = voyage.capacity() * 1.1;
    if ((voyage.bookedCargoSize() + cargo.size()) > maxBooking)
        return -1;
    int confirmation = orderConfirmationSequence.next();
    voyage.addCargo(cargo, confirmation);
    return confirmation;
}

這段程式碼的問題:

  • 業務專家即使在開發者引導下也難以閱讀和驗證規則
  • 技術人員難以將需求文字與程式碼對應起來
  • 重要的業務規則被隱藏在應用方法的 guard clause 中

改進的設計引入了 Overbooking Policy——這是 STRATEGY 設計模式的一種應用,在 DDD 中稱為 Policy

public int makeBooking(Cargo cargo, Voyage voyage) {
    if (!overbookingPolicy.isAllowed(cargo, voyage)) return -1;
    int confirmation = orderConfirmationSequence.next();
    voyage.addCargo(cargo, confirmation);
    return confirmation;
}
public boolean isAllowed(Cargo cargo, Voyage voyage) {
    return (cargo.size() + voyage.bookedCargoSize()) <=
           (voyage.capacity() * 1.1);
}

更明確的設計帶來兩個優勢:

  1. 團隊中的每個人都會理解 Overbooking 是一條獨立且重要的業務規則,而非隱晦的計算
  2. 程式設計師可以向業務專家展示技術產出物(甚至程式碼),讓領域專家也能理解,從而關閉回饋迴路

作者強調,並非每個細節都需要如此精心設計。重點是展示 Domain Model 與對應設計可以用來鞏固和分享知識

Deep Models#

有用的模型很少停留在表面。隨著我們理解領域和應用需求的深入,早期看似重要的膚淺模型元素通常會被捨棄或改變視角。微妙的抽象會浮現——這些抽象在一開始不會想到,但卻直指問題的核心。

航運系統的深層模型#

作者描述了一個貨櫃航運系統專案的轉變:

  • 初始模型:描述貨物、行程路線等,既必要也有用,但領域專家感到不滿——他們看待業務的方式被遺漏了
  • 經過數月的 Knowledge Crunching 後的發現:貨物的實際搬運大多由承包商或公司的營運人員執行。在航運專家眼中,核心是一系列責任的轉移——從託運人到本地承運人,從一個承運人到另一個,最終到收貨人
  • 模型轉變:從「將貨櫃從一個地方移到另一個地方」轉變為「將貨物的責任從一個實體轉移到另一個實體」。法律文件(如提單 bill of lading)和付款流程取代了物流細節成為模型的核心

Knowledge Crunching 是一場探索,你無法預知終點。 模型會持續演化,早期的理解可能被推翻,但這個過程本身就是建立深層理解的關鍵。