問題的根源#

長期運作的應用程式往往會逐漸失控。即使一開始有良好的架構,在時程壓力下,多年後可能沒有人真正理解整個系統的結構。開發者只知道最近被 hack 過的地方,新功能被加在他們最熟悉的「hack 點」。

阻礙架構意識的原因:

  • 系統過於複雜,需要很長時間才能看到全貌
  • 系統複雜到根本沒有全貌可言
  • 團隊處於反應式模式(reactive mode),忙於處理緊急問題而忽略全局

架構太重要了,不能只交給少數人負責。團隊中每個人都應該了解架構,並從中受益。當 20 人的團隊只有 3 人深知架構,那 3 個人要花大量精力引導其他 17 人,否則那 17 人只能靠猜測行事。


Telling the Story of the System#

作者經常使用一種稱為 「述說系統的故事」(Telling the Story of the System) 的技巧。至少需要兩個人:

  1. 一個人問:「這個系統的架構是什麼?」
  2. 另一個人嘗試用盡可能少的概念(兩三個即可)解釋系統的架構

關鍵原則#

  • 假裝對方對系統一無所知
  • 用幾句話說明設計的核心部件及其互動方式
  • 刻意簡化——這會讓你感覺像在「說謊」,因為省略了許多細節,但這正是重點

簡化描述會迫使你思考:什麼是系統中最重要的事情?這種練習本身就極具價值。

JUnit 範例#

以下是用這個技巧描述 JUnit 架構的過程:

JUnit 有兩個主要的 class:TestTestResult。使用者建立測試並執行它們,傳入一個 TestResult。當測試失敗時,它告訴 TestResult。人們可以向 TestResult 查詢所有發生的失敗。

這個描述的簡化之處:

  • JUnit 中有許多其他 class
  • 使用者不直接建立 test 物件——透過 reflection 從 test case class 建立
  • Test 其實是一個 interface,測試寫在 TestCase 的子類別中
  • 人們通常不直接向 TestResult 查詢失敗——TestResult 使用 listener 模式通知

故事如何引導設計#

當你打算新增功能時,可以思考該變更如何影響系統的「故事」。如果某個改法會讓「故事」變得更複雜或不自然,那可能不是最好的方向。如果改法讓故事更簡潔、更真實,那就更符合架構。

經常在團隊中述說系統的故事,用不同方式描述。隨著你考慮系統的變更,你會發現有些改動更符合故事的走向——它們讓簡短的故事更不像謊言。


Naked CRC#

CRC 代表 Class, Responsibility, and Collaborations。傳統 CRC 在索引卡上寫下類別名稱、職責和協作者。Naked CRC 則是不在卡片上寫任何東西的 CRC 變體。

運作方式#

  1. 準備一疊空白索引卡(或任何可移動的物品)
  2. 描述系統的人一張一張地將卡片放在桌上
  3. 透過移動卡片、指向卡片來表達系統中物件的互動
  4. 重疊卡片表示物件的集合

線上投票系統範例#

「這是即時投票系統的運作方式。這裡是一個 client session」(指向卡片)

「每個 session 有兩個 connection——incoming 和 outgoing」(放下卡片並分別指向)

「啟動時,server 端也會建立一個 session」(在另一側放下卡片)

「當 client 投票時,投票被送到 server session」(用手比劃從 client 到 server 的方向)

「server session 回應確認,然後記錄投票到 vote manager」(指向卡片間的關聯)

Naked CRC 的兩個準則#

  1. 卡片代表 instance(實例),不是 class
  2. 重疊卡片表示 collection

這個技巧的力量在於讓系統的部件變得具體可觸摸。透過移動和位置來展示互動方式,讓複雜的場景更容易理解,也更容易記住。


Conversation Scrutiny#

在 legacy code 中,我們很容易只關注「要改什麼」而忽略建立抽象的機會。Conversation Scrutiny(對話審查) 提醒我們注意日常討論中出現的概念。

核心觀察#

當團隊在討論設計時,對話中使用的概念和程式碼中的概念是否一致?如果不一致,需要問為什麼:

  • 可能是程式碼尚未適應團隊的理解
  • 可能是團隊需要用不同方式理解程式碼

範例#

作者回憶一次與團隊的討論:他們在談論一個 locking policy(鎖定策略) ——如何保證資源按特定順序鎖定和解鎖以避免 deadlock。當一位程式設計師開始 inline 地寫策略邏輯時,作者說:

「等等,我們在談的是一個 locking policy,對吧?為什麼不建立一個叫 LockingPolicy 的 class,用方法名稱清楚描述我們要做的事?」

設計永遠不會「結束」#

團隊能犯的最大錯誤之一,就是認為設計在開發的某個時間點就「結束了」。如果設計「結束了」而人們還在持續修改,新程式碼就會出現在不恰當的地方,class 會膨脹,因為沒有人覺得可以舒適地引入新的抽象。這是讓 legacy system 惡化的最可靠方式。


總結#

本章介紹的技巧——Telling the Story of the SystemNaked CRCConversation Scrutiny——都是用來發掘和溝通大型既有系統架構的方法。這些技巧也同樣適用於設計新系統。設計是設計,無論它發生在開發週期的哪個階段。