第 11 章 設計兩次
設計軟體非常困難,因此關於如何構造模組或系統,最初的想法不太可能就是最佳設計。如果在每個主要設計決策上都考慮多個方案,最終就能獲得好得多的結果:設計兩次。
本章概覽最先想到的設計,為什麼不該直接採用?
假設你正在為圖形介面文字編輯器設計一個類,用來管理檔案中的文字。第一步是定義這個類向編輯器其餘部分提供的介面。與其採用最先想到的方案,不如考慮幾種可能性。一種選擇是行導向的介面,支援插入、修改和刪除整行文字。另一種選擇是以單個字元為單位進行插入和刪除的介面。第三種選擇是字串導向的介面,可以操作任意範圍的字元,包括跨行的範圍。你無需確定每個備選方案的所有功能;在這個階段,勾勒出幾個最重要的方法就足夠了。
嘗試選擇彼此差異很大的方案,這樣你會學到更多。即使你確定只有一種合理的方案,也要考慮第二種設計,不管你認為它有多糟糕。思考該設計的弱點,並與其他設計的特性進行對比,會很有啟發。
粗略設計出備選方案後,列出各自的優缺點。設計介面時,最重要的考慮因素是更高層級的軟體是否易於使用它。在上面的範例中,行導向的介面和字元導向的介面都要求使用文字類的軟體做額外的工作。使用行導向的介面時,更高層級的軟體在操作一行中的部分文字或多行文字時(例如剪下和貼上所選內容),需要自行拆分和合並行。使用字元導向的介面時,修改多個字元需要透過迴圈實作。還有其他值得考慮的因素:
- 某個方案的介面是否比其他方案更簡單?在文字範例中,所有文字介面都相對簡單。
- 一種介面是否比另一種介面更通用?
- 某個介面是否能支援比其他介面更高效的實作?在文字範例中,字元導向的方案可能比其他方案慢得多,因為它需要為每個字元單獨呼叫文字模組。
比較備選設計之後,你就更容易確定最佳設計。最佳選擇可能是其中一個方案,也可能是將多個方案的特性組合起來,形成比任何一個原方案都更好的新設計。
有時所有的備選設計都不夠理想。這時,看看能否提出其他方案,並以已有方案中發現的問題為出發點,探索新設計。如果你在設計文字類時只考慮了行導向和字元導向的方案,可能會發現兩者都不太好用,因為它們都要求更高層級的軟體執行額外的文字操作。這是一個危險訊號:既然有了文字類,它就應該處理所有文字操作。為了消除這些額外的文字操作,文字介面需要更貼合更高層級軟體所執行的操作。這些操作並不總是對應單個字元或單行文字。沿著這個思路,你應該能找到範圍導向的文字 API,消除先前設計中的問題。
譯者圖解用同一操作檢驗三個介面
設計兩次的原則可以應用於系統的不同層級。對於模組,你可以先用這種方式選擇介面,如上所述,再在設計實作時應用一次:對於文字類,可以考慮的實作包括由各行組成的連結串列、固定大小的字元塊或“間隙緩衝區(Gap Buffer)”。實作與介面的設計目標不同:對於實作,最重要的是簡單性和效能。在系統的更高層級上探索多種設計也很有用,例如為使用者介面選擇功能,或將系統分解為主要模組時。無論哪種情況,比較幾個備選方案,都更容易找到最佳方案。
設計兩次不需要花費很多額外的時間。對於類這樣較小的模組,考慮備選設計可能只需一兩個小時。相比實作該類所需的數天或數週,這點時間很少。最初的設計探索很可能帶來明顯更好的設計,其收益足以補償設計兩次所花的時間。對於較大的模組,初期的設計探索需要更多時間,但實作所需的時間也更長,更好的設計帶來的收益也更大。
我注意到,十分聰明的人有時反而很難接受設計兩次的原則。聰明的人在成長過程中會發現,面對任何問題,最先想到的方案就足以取得好成績,無需考慮第二種或第三種可能性。這容易導致不良的工作習慣。然而,隨著年齡增長、職位提升,他們面對的問題會越來越難。最終,每個人都會發現,最初的想法已不夠好。如果想獲得出色的結果,無論多麼聰明,都必須考慮第二種可能性,甚至第三種。大型軟體系統的設計就屬於這種情況:沒有人能只靠第一次嘗試就做好。
不幸的是,我經常看到聰明的人堅持實作最先想到的方案,這使他們無法充分發揮潛力(也讓與他們共事的人感到沮喪)。也許他們下意識地相信“聰明的人第一次就能做好”,所以嘗試多種設計就意味著自己並不聰明。事實並非如此。不是你不聰明,而是問題真的很難!而且,這其實是件好事:解決一個需要認真思考的難題,比解決一個根本無需思考的簡單問題有趣得多。
設計兩次不僅能改善設計,還能提高你的設計能力。構思並比較多種方案,能讓你瞭解哪些因素會讓設計變好或變差。久而久之,你就能更容易地排除不好的設計,找到真正出色的設計。