我們將從一個極為簡單的起點出發,持續精煉系統的設計;並且移除任何未被證明有用的彈性

XP 的設計策略是:永遠保持「能跑通當前測試套件的最簡設計」。

這句話沒那麼難懂。簡單有什麼錯?測試套件有什麼錯?

四項價值如何推向這個策略#

  • 溝通——複雜的設計比簡單的設計更難溝通。因此我們該打造一套「產出最簡設計」的設計策略;同時,它也該產出具溝通力的設計——設計元素要能向讀者傳達系統的重要面向。
  • 簡單——我們該有一套產出簡單設計的策略,而且策略本身也該簡單。這不代表它容易執行——好設計從來就不容易——但策略的表述應該簡單。
  • 回饋——作者說在開始實踐 XP 之前,他設計時最大的困擾是不知道自己是對是錯;而且設計得越久,這問題越嚴重。簡單設計靠「很快就做完」解決了這件事:下一步就是把它寫成程式碼,看看程式碼長什麼樣。
  • 勇氣——還有什麼比「設計一點點就停下來、並確信時候到了你就能按需加上更多」更需要勇氣?

依循這些價值,我們必須:

  1. 打造一套產出簡單設計的設計策略
  2. 快速找到驗證其品質的方法
  3. 把學到的東西回饋進設計
  4. 把整個流程的循環時間壓到最短

原則如何推向這個策略#

  • 小額初始投資——在設計上做出「能開始回本」之前的最小投資
  • 假設簡單——假設「我們能想像可行的最簡設計」真的可行。這給了我們時間,萬一最簡設計行不通時可以做一次徹底的功夫;而在那之前,我們不必扛著額外複雜度的成本
  • 增量式變更——設計策略靠逐步變更運作。我們一次設計一點點;永遠不會有「系統已經設計完了」的時刻——它永遠可能改變,雖然系統有些部分會安靜一陣子
  • 輕裝上路——設計策略不該產出任何「多餘」的設計。足夠應付當前目的(品質工作的需要)即可,不要更多

所幸多數人都能戒掉這種「向未來借麻煩」(作者的祖母如此稱呼)的習慣。不幸的是,你越聰明,越難戒掉。

什麼時候該加更多設計#

換個角度問:什麼時候才加更多設計? 常見的答案是「為明天設計」。

圖 8:如果變更成本隨時間劇烈上升

這個策略只在「從現在到以後什麼都不變」時才成立。 如果你確切知道會發生什麼、也確切知道怎麼解決它,那麼一般而言「現在需要的現在加、以後需要的以後加」確實比較好。

這個策略的問題是不確定性

  • 有時候明天永遠不會來——你預先設計的那個功能被客戶從盤子上拿走了
  • 有時候你在現在與以後之間學到了更好的做法

無論哪一種,你都得在兩者間選擇:付出移除多餘設計的成本,或是持續付出「扛著一個沒有當前效益的複雜設計」的成本

作者說他永遠不想賭「變化不會發生」,更不想賭「自己不會學到東西」。因此我們得換一張圖:今天設計今天的問題,明天設計明天的問題。

圖 9:如果變更成本隨時間維持便宜

XP 的設計流程#

  1. 從一個測試開始,這樣我們才知道何時完成。光是為了寫測試,我們就得做一定量的設計:有哪些物件?它們有哪些可見的方法?
  2. 設計並實作剛剛好足以讓那個測試通過的東西——你必須設計出足夠的實作,讓這個測試以及所有先前的測試都能通過
  3. 重複
  4. 只要看到讓設計更簡單的機會,就動手

這個策略看起來簡單得荒謬。它確實簡單,但並不荒謬——它有能力創造出大型而精密的系統。

然而它並不容易沒有什麼比「在緊迫的期限下工作、卻仍然抽出時間邊做邊清理」更難的了。

用這種風格設計,你第一次遇到某個東西時會用非常簡單的方式實作它;第二次用到它時,你才把它變得更通用。

「透過重構做設計」實際上怎麼運作#

真正執行起來,這套設計策略會讓人覺得奇怪。

我們拿起第一個測試案例,說:「如果我們要做的只是實作這個測試案例,那我們只需要一個物件、兩個方法。」我們實作那個物件與兩個方法。完成了。我們的整套設計就是一個物件——維持了大約一分鐘。

接著我們拿起下一個測試案例。我們可以硬幹出一個解法,也可以把既有的一個物件重組成兩個物件;那樣一來,實作這個測試案例就變成替換掉其中一個物件。於是我們先重組、跑第一個測試案例確認沒壞、再實作下一個測試案例

這樣做個一兩天,系統就大到足以讓我們想像兩支隊伍同時在上面工作而不必老是擔心互踩;於是我們讓兩對同時實作測試案例,並定期(每隔幾小時)整合彼此的變更。再過一兩天,系統就能支撐整個團隊以這種風格開發。

當「垢」悄悄追上來#

團隊會時不時感覺到髒東西正從背後爬上來——也許他們量到估算持續偏差,也許他們一想到得改系統某個部位就胃部糾結。

無論如何,此時會有人喊:「暫停。」團隊聚在一起花上一天,用 CRC 卡、草圖與重構的組合,把系統整體重組一遍。

大重構要拆成小步#

不是所有重構都能在幾分鐘內完成。如果你發現自己建了一個糾纏的大型繼承階層,可能得花上一個月的集中努力才解得開——但你沒有一個月的集中努力可以花,你得交付這次迭代的故事。

你會在做某個測試案例做到一半時,看見一個朝大目標再邁一步的機會。就走那一步:這裡搬一個方法,那裡搬一個變數。最終,那個大重構會只剩下一件小工作,然後你幾分鐘內就能把它做完。

延伸案例:保險合約系統的平行類別階層

作者曾在一套保險合約管理系統上,一步一步地推進大規模重構。

原本的階層長這樣——這個設計違反了「一次且僅此一次」規則。

圖 10:Contract 與 Product 有平行的子類別

於是他們開始朝著另一個設計移動:

圖 11:Contract 引用一個 Function,但沒有子類別

在作者待在這套系統上的那一年裡,他們朝目標設計走了許多小步——把 Contract 子類別中的職責推進 Function 或 Product 子類別。

到他合約結束時,他們仍然沒有消滅掉 Contract 子類別,但那些子類別已經比一開始瘦得多,而且明顯正在退場的路上。

而在這整段期間,他們持續把新功能送上正式環境。

在 XP 的觀點裡,設計不是畫一堆圖、然後實作系統去符合那些圖——那是「把車頭對準方向」。

〈學開車〉指向另一種設計風格:先把車發動,然後往這邊修一點、往那邊修一點、再往這邊修一點。

什麼叫「最簡」#

既然最佳設計的定義是「能跑通所有測試案例的最簡設計」,那這個定義的效力就取決於:我們說的「最簡」是什麼意思?

幾個錯誤的答案:

  • 類別最少?——會導致物件大到無法有效運作
  • 方法最少?——會導致巨大的方法與重複
  • 程式碼行數最少?——會導致為壓縮而壓縮,並失去溝通力

作者所謂的「最簡」,是四項限制,依優先序排列

  1. 系統(程式碼與測試合起來)必須溝通你想溝通的一切
  2. 系統必須不含重複的程式碼(第 1 與第 2 點合起來,構成「一次且僅此一次」規則)
  3. 系統應有盡可能少的類別
  4. 系統應有盡可能少的方法

系統設計的目的,首先是溝通程式設計師的意圖,其次是提供一個讓系統邏輯棲身的地方。上述限制提供了滿足這兩項要求的框架。

  • 如果你把設計視為溝通媒介,那你就會為每個重要概念設一個物件或方法,並讓類別與方法的命名彼此呼應。
  • 在「必須溝通」的約束下,你接著必須找到辦法消除系統中所有重複的邏輯。作者說這是設計中對他而言最難的部分——你先得找到重複,然後還得找到消除它的辦法。消除重複自然會引導你創造出大量小物件與小方法,因為否則重複無可避免。
  • 但你不會為了好玩而創造新物件或新方法。如果你發現一個類別什麼也沒做、什麼也沒溝通,或一個方法什麼也沒做、什麼也沒溝通,就刪掉它。

另一種看待這個流程的方式是擦除:你有一套能跑通測試案例的系統,把所有沒有目的的東西刪掉——不管是溝通目的還是計算目的。剩下來的,就是能動的最簡設計。

這樣為什麼會更便宜#

降低軟體長期成本的傳統策略,是降低返工的機率與成本XP 完全反其道而行——它不減少返工的頻率,反而沉醉於返工。(沒有重構的一天,就像沒有陽光的一天。)這怎麼可能更便宜?

關鍵是:風險就是錢,正如時間就是錢。

  • 若你今天放入一個設計特性、明天就用到它,你贏了,因為今天放入比較便宜。但〈軟體開發的經濟學〉指出這個評估並不完整:只要不確定性夠大,「等待」這個選擇權的價值就高到讓你等待比較划算。
  • 設計不是免費的。 你今天放進越多設計,就越增加系統的開銷:更多東西要測、更多東西要理解、更多東西要解釋。所以你每天不只付你花掉那筆錢的利息,還多付一點「設計稅」。
  • 而真正的殺手是風險。 你不能只評估「明天發生的某件事」的成本,還得評估它發生的機率。作者說他和任何人一樣愛猜對,但當他開始留意時發現:他猜對的次數遠不如自己以為的多——他一年前創造的那個絕妙設計,往往幾乎沒有一項猜對;在完工之前,設計的每一寸他都得返工,有時還得兩三次。

把這些加起來:

  • 今天做一個設計決策的成本 = 做決策的成本 + 這筆錢的利息 + 它為系統增加的慣性
  • 今天做一個設計決策的效益 = 這個決策未來被有效使用的期望值

如果今天決策的成本高、它正確的機率低、明天知道更好做法的機率高、而明天放入這個設計的成本低——那結論就是:如果今天不需要,就永遠不要在今天做設計決策。

這正是 XP 的結論:「一天的難處一天當就夠了。

有幾個因素能讓上述評估失效:

  • 如果明天做這個變更的成本高出非常多,那我們就該今天決定,賭一把自己是對的
  • 如果設計的慣性夠低(例如你們是真的、真的非常聰明的人),那即時設計的好處就比較少
  • 如果你真的、真的很會猜,那你大可今天就把一切都設計好

但對我們其餘人而言,作者看不到別的結論:今天的設計今天做,明天的設計明天做。

圖在設計中的角色#

那些漂亮的設計圖與分析圖怎麼辦?有些人確實用圖思考設計比用程式碼更順。視覺導向的人要如何為設計做出貢獻?

首先,用明確的圖來設計軟體沒有任何錯,而且圖形化途徑有很多優點:

  • 畫圖的困難能給你關於設計健康度的微妙線索——如果你發現無法把圖中元素減少到可管理的程度、如果有明顯的不對稱、如果線比方塊多得多,這些都是壞設計的線索,而它們在圖形表示中才變得顯而易見。
  • 速度——在你把一個設計寫成程式碼的時間裡,你能用圖比較三個設計。

用原則來導航:

  • 小額初始投資——一次只畫幾張圖
  • 為贏而打——別出於恐懼而使用圖(例如因為想推遲「承認自己不知道設計該長怎樣」的那一天)
  • 快速回饋——迅速查明我們的圖有沒有打中目標
  • 順著人的直覺——鼓勵那些用圖思考最順的人畫圖
  • 擁抱變化+輕裝上路——圖對程式碼產生效果之後就不要保存,因為它們代表的決策明天大概就會改變

例如圖可以畫在白板上。「希望能把這塊白板存下來」是一個確鑿的徵兆,表示這個設計還沒有被溝通出去——無論是對團隊,還是對系統。

作者澄清:如果有某種原始碼最適合用圖來表達,那你當然該用圖去表達、編輯與維護它。 那些能讓你指定系統完整行為的 CASE 工具很好——它們可以把自己做的事叫做「程式碼生成」,但在作者看來那分明就是一種程式語言。他反對的不是圖,而是試圖讓同一份資訊的多種形式保持同步。

如果你用的是文字型程式語言,依循這條建議,你畫圖的時間絕不會超過 10 到 15 分鐘——然後你就知道自己想向系統問什麼問題了。得到答案後,你可以再畫幾張圖,直到碰上下一個需要具體答案的問題。

同樣的建議適用於 CRC 卡等其他非程式碼的設計標記法:做個幾分鐘,足以照亮一個問題,然後轉向系統,以降低你一直在自欺的風險。

系統架構#

前面作者一直沒用「架構」這個字。但架構在 XP 專案裡,和在任何軟體專案裡一樣重要

架構的一部分被系統隱喻捕捉住了:如果你有一個好的隱喻,團隊裡每個人都能說出系統整體大致如何運作。

下一步是看故事如何變成物件。規劃遊戲的規則說:第一次迭代的結果,必須是整個系統一具能運作的骨架。但你同時又得做「能動的最簡方案」——這兩者怎麼調和?

做完這個練習,你就有了你的架構。它可能不是你原本預期的架構——但那樣的話,你就學到了一些東西。

那如果你找不到一組故事能迫使你建出「你知道、你絕對知道自己將會需要」的架構呢?

你有兩個選擇:基於臆測把整個架構先擺上去,或是現在只擺上足以滿足當前需求的架構,並相信自己以後能再加

作者的選擇是:擺上我現在需要的架構,並相信自己以後有能力改變它。