問題的根源#
長期運作的應用程式往往會逐漸失控。即使一開始有良好的架構,在時程壓力下,多年後可能沒有人真正理解整個系統的結構。開發者只知道最近被 hack 過的地方,新功能被加在他們最熟悉的「hack 點」。
阻礙架構意識的原因:
- 系統過於複雜,需要很長時間才能看到全貌
- 系統複雜到根本沒有全貌可言
- 團隊處於反應式模式(reactive mode),忙於處理緊急問題而忽略全局
架構太重要了,不能只交給少數人負責。團隊中每個人都應該了解架構,並從中受益。當 20 人的團隊只有 3 人深知架構,那 3 個人要花大量精力引導其他 17 人,否則那 17 人只能靠猜測行事。
Telling the Story of the System#
作者經常使用一種稱為 「述說系統的故事」(Telling the Story of the System) 的技巧。至少需要兩個人:
- 一個人問:「這個系統的架構是什麼?」
- 另一個人嘗試用盡可能少的概念(兩三個即可)解釋系統的架構
關鍵原則#
- 假裝對方對系統一無所知
- 用幾句話說明設計的核心部件及其互動方式
- 刻意簡化——這會讓你感覺像在「說謊」,因為省略了許多細節,但這正是重點
簡化描述會迫使你思考:什麼是系統中最重要的事情?這種練習本身就極具價值。
JUnit 範例#
以下是用這個技巧描述 JUnit 架構的過程:
JUnit 有兩個主要的 class:
Test和TestResult。使用者建立測試並執行它們,傳入一個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 變體。
運作方式#
- 準備一疊空白索引卡(或任何可移動的物品)
- 描述系統的人一張一張地將卡片放在桌上
- 透過移動卡片、指向卡片來表達系統中物件的互動
- 重疊卡片表示物件的集合
線上投票系統範例#
「這是即時投票系統的運作方式。這裡是一個 client session」(指向卡片)
「每個 session 有兩個 connection——incoming 和 outgoing」(放下卡片並分別指向)
「啟動時,server 端也會建立一個 session」(在另一側放下卡片)
「當 client 投票時,投票被送到 server session」(用手比劃從 client 到 server 的方向)
「server session 回應確認,然後記錄投票到 vote manager」(指向卡片間的關聯)
Naked CRC 的兩個準則#
- 卡片代表 instance(實例),不是 class
- 重疊卡片表示 collection
這個技巧的力量在於讓系統的部件變得具體可觸摸。透過移動和位置來展示互動方式,讓複雜的場景更容易理解,也更容易記住。
Conversation Scrutiny#
在 legacy code 中,我們很容易只關注「要改什麼」而忽略建立抽象的機會。Conversation Scrutiny(對話審查) 提醒我們注意日常討論中出現的概念。
核心觀察#
當團隊在討論設計時,對話中使用的概念和程式碼中的概念是否一致?如果不一致,需要問為什麼:
- 可能是程式碼尚未適應團隊的理解
- 可能是團隊需要用不同方式理解程式碼
範例#
作者回憶一次與團隊的討論:他們在談論一個 locking policy(鎖定策略) ——如何保證資源按特定順序鎖定和解鎖以避免 deadlock。當一位程式設計師開始 inline 地寫策略邏輯時,作者說:
「等等,我們在談的是一個 locking policy,對吧?為什麼不建立一個叫
LockingPolicy的 class,用方法名稱清楚描述我們要做的事?」
設計永遠不會「結束」#
團隊能犯的最大錯誤之一,就是認為設計在開發的某個時間點就「結束了」。如果設計「結束了」而人們還在持續修改,新程式碼就會出現在不恰當的地方,class 會膨脹,因為沒有人覺得可以舒適地引入新的抽象。這是讓 legacy system 惡化的最可靠方式。
總結#
本章介紹的技巧——Telling the Story of the System、Naked CRC 和 Conversation Scrutiny——都是用來發掘和溝通大型既有系統架構的方法。這些技巧也同樣適用於設計新系統。設計是設計,無論它發生在開發週期的哪個階段。