索引 / 001 ZH-TW
A Philosophy of Software Design(軟體設計的哲學) cover

電腦科學

電腦科學

A Philosophy of Software Design(軟體設計的哲學)

John Ousterhout

英文原名: A Philosophy of Software Design

一本探討軟體設計核心問題——管理複雜度——的小書,作者根據講授一門以設計為重點的軟體課程的經驗,主張採用介面簡單的深模組、資訊隱藏,以及審慎的命名與註解。書中也列出一系列警訊,指出設計正變得難以理解與修改。

需要的基礎: 曾用任一語言撰寫並維護中等規模程式的經驗,例如完成過一個較大的課堂專題。

難度等級
入門
學術層次
大學部
software designcomplexitymodularityabstractioncode quality

01 / 導讀

導讀

引用

Ousterhout, J. (2021). A Philosophy of Software Design (2nd ed.). Yaknyam Press.

章節概要

這是一本只有二十二章、不到200頁的小書,圍繞一個問題展開:如何把軟體分解成類別、方法等模組,讓系統的複雜度維持在可以掌控的範圍。本書源自作者在史丹佛大學講授的軟體設計課程,許多例子取自學生專題,例如一個圖形介面的文字編輯器。書末彙整了設計原則,以及正文中陸續提出的「警訊」(red flags),也就是設計出問題的警示徵兆。據作者說明,第二版改動不多:新增第21章,改寫並擴充第6章(從其他章節移入部分內容),並增加幾節,把本書觀點與羅伯特·馬丁的 Clean Code 相互比較。

第1–3章:Introduction(引言);The Nature of Complexity(複雜度的本質);Working Code Isn't Enough(能動的程式碼還不夠)

第2章為複雜度下定義,列出它的症狀(變更放大、認知負荷、未知的未知)與成因(相依性與晦澀),並指出複雜度是一點一滴累積的。第3章對照戰術式與戰略式程式設計,後者是一種投資心態:事先在設計上多花一點時間,換取日後省下的時間;本章也討論該投入多少,包括新創公司的情況。

第4章:Modules Should Be Deep(模組應該要深)

全書的核心比喻:深模組在簡單的介面背後提供強大的功能,淺模組則增加了介面卻沒有隱藏多少東西。本章討論介面與抽象,告誡不要罹患「類別炎」(類別又多又小),並以Java與Unix的輸入輸出為例作比較。

第5章:Information Hiding (and Leakage)(資訊隱藏(與洩漏))

說明資訊隱藏及其失敗形式——資訊洩漏,其中包括時序分解。一組圍繞HTTP伺服器的例子討論類別過多、參數處理,以及回應中的預設值。

第6章:General-Purpose Modules are Deeper(通用模組更深)

作者主張,過度特化可能是軟體複雜度最大的單一來源。本章以編輯器專題中的文字類別為例,示範如何以通用的API(插入與刪除一段範圍)取代綁定特定按鍵的方法;列出幫助找到平衡點的問題;以裝置驅動程式為例,說明如何把特化往上(或往下)推;把通用的復原歷程與特化的動作分開;並主張消除程式碼中的特殊情況。

第7–8章:Different Layer, Different Abstraction(不同層級,不同抽象);Pull Complexity Downwards(把複雜度往下拉)

第7章把傳遞用的方法與變數、裝飾器,以及介面與實作的差異,視為相鄰層級重複同一抽象的徵兆。第8章主張應由模組的實作者而非使用者吸收複雜度,以編輯器的文字類別與設定參數為例,並提醒不要把這個想法推到極端。

第9章:Better Together Or Better Apart?(合在一起還是分開?)

提出合併程式碼的標準(共享資訊、簡化介面、消除重複),舉出編輯器中的例子,並討論方法的拆分與合併。新增的一節反駁 Clean Code 主張函式應該非常短的建議:深度比長度更重要。

第10章:Define Errors Out Of Existence(以定義消除錯誤)

論證例外是複雜度的重要來源,並說明如何減少它們:把API定義得讓錯誤不會發生(以Windows的檔案刪除與Java的substring方法為例)、在底層遮蔽例外、集中處理例外、思考何時直接當掉是可以接受的,以及以設計消除特殊情況。

第11章:Design it Twice(設計兩次)

篇幅很短的一章,建議設計者在定案之前考慮不只一種設計。

第12–16章:Why Write Comments? The Four Excuses(為什麼要寫註解?四個藉口);Comments Should Describe Things that Aren't Obvious from the Code(註解應描述程式碼中不明顯的事);Choosing Names(命名);Write The Comments First(先寫註解);Modifying Existing Code(修改既有程式碼)

全書篇幅最大的一組章節談文件。第12章回應不寫註解的四個常見藉口,並在第二版中駁斥 Clean Code 認為註解是失敗的觀點。第13章說明註解應該補充什麼:低層次的精確、高層次的直覺、介面文件,以及跨模組的設計決策。第14章要求名稱精確、一致,能喚起清楚的意象。第15章主張把先寫註解當作設計工具。第16章把戰略式思維應用到修改既有程式碼與維護註解上。

第17–18章:Consistency(一致性);Code Should be Obvious(程式碼應該一目了然)

第17章舉出一致性的例子、確保一致性的方法,並提醒不要過度追求;第18章列出讓程式碼對讀者更明顯或更不明顯的因素。

第19章:Software Trends(軟體潮流)

依據本書的原則,評估物件導向的繼承、敏捷開發、單元測試、測試驅動開發、設計模式,以及getter與setter。

第20章:Designing for Performance(為效能而設計)

討論如何思考效能、先量測再修改、圍繞關鍵路徑來設計,並提出一個取自作者RAMCloud系統的較長例子。

第21–22章:Decide What Matters(決定什麼是重要的);Conclusion(結語)

新增的第21章把好的設計說成是區分重要與不重要的事物:尋找槓桿點、盡量減少重要的事物、透過顯著位置、重複與核心地位來突顯重要的事物,並避免兩種錯誤——把太多東西當成重要,以及忽略真正重要的東西。全書以簡短的結語作結。

核心概念

第1–3章:複雜度與心態

  • 複雜度:全書要處理的問題;第2章把它的成因歸結為相依性與晦澀。
  • 變更放大、認知負荷、未知的未知:複雜度表現出來的三種症狀。
  • 戰略式與戰術式程式設計:持續投資在設計上,而不只是讓下一個功能能動。

第4–10章:模組設計

  • 深模組:在簡單介面背後隱藏大量功能的模組。
  • 資訊洩漏:同一個設計決策反映在多個模組中,使得一處修改會牽連所有這些模組。
  • 適度通用:介面通用到足以支援多種用途,實作則滿足目前的需求。
  • 把複雜度往下拉:讓實作者而非每個呼叫者處理複雜度。
  • 以定義消除錯誤:把介面設計得讓錯誤情況根本不會出現。

第11–18章:流程、註解與可讀性

  • 設計兩次:在定案前考慮其他設計方案。
  • 作為設計工具的註解:在寫程式碼之前寫註解,讓註解形塑設計,而不是事後描述。
  • 一目了然的程式碼:讀者不需多想就能很快理解的程式碼。

第19–22章:潮流、效能與優先順序

  • 關鍵路徑:在效能關鍵的情況下必須執行的程式碼;書中的RAMCloud例子讓這段程式碼沒有多餘的方法呼叫與特殊情況檢查。
  • 警訊:標示設計問題、有名稱的症狀,彙整於書末。
  • 決定什麼是重要的:辨識設計中少數重要的面向,並圍繞它們組織系統。

評析

優點

  • 一個想法,貫徹始終。 每一章都回到複雜度及其成因,因此本書容易記住,建議也方便在程式碼審查中運用。
  • 經過課堂檢驗的具體例子。 編輯器、HTTP伺服器與復原機制的例子都來自作者的課程,既呈現學生有缺陷的設計,也呈現更好的設計;警訊清單把原則化為可用的檢查表。
  • 勇於表明立場。 本書反駁一些常見建議,最明顯的是第二版中在方法長度與註解問題上與 Clean Code 的分歧,以及對測試驅動開發、getter與setter等潮流的評價。學生因此有了可以拿自身經驗來檢驗的觀點。
  • 篇幅短。 不到200頁,可以和專題課程同時閱讀,不會排擠其他學習。

局限

  • 只談模組層級的設計。 本書關注程式內部的類別、方法與介面,不涉及需求、分散式系統規模的架構、測試實務、團隊或流程,這些由本課程的其他書處理。
  • 立基於一個人的經驗。 作者把本書呈現為從自己的教學與系統研發中提煉出的一套哲學,依據是例子與論證而非實證研究;正如與 Clean Code 的交鋒所示,讀者可能不認同其中某些立場。
  • 對缺乏專題經驗的讀者幫助有限。 例子假定讀者熟悉類別、介面與例外,程式碼多為Java風格;要在為一定規模的程式吃過苦頭之後,這些建議才最有感。
  • 相較第一版改動很少。 作者本人也說,已有第一版的讀者不一定需要買第二版,並把新增內容以免費摘錄公開。

在課程中的定位

在軟體工程這門課的書單中,本書談的是程式碼本身的設計。最接近的搭配是 Thomas 與 Hunt 的 The Pragmatic Programmer:它以簡短主題的形式涵蓋範圍更廣的工作習慣與實務,而 Ousterhout 則深入探討單一問題。兩本一起讀,既有廣度,也能得到一套一致的設計詞彙。

Sommerville 的 Software Engineering 涵蓋從需求到演進的整個生命週期;Software Engineering at Google 討論組織規模下的文化、流程與工具;Bass、Clements 與 Kazman 的 Software Architecture in Practice 在整個系統與品質屬性的層級討論設計決策;Jackson 的 Software Abstractions 介紹以Alloy對設計進行形式化建模。Ousterhout 讓複雜度的代價變得具體,為以上所有內容打好基礎,最好及早閱讀,在進行一個較大的程式專題期間或之前讀。

如何使用本書

本書適合寫過並維護過中等規模程式(例如一個較大的課程專題)的學生,以及在職程式設計師。不要求特定語言,但許多例子使用類似Java的程式碼。

  • 核心: 第1–10章,建立全書通用的詞彙(複雜度、深模組、資訊隱藏、通用介面、錯誤)。
  • 文件: 第12–15章最直接實用,建議在專題第一次程式碼審查之前閱讀。
  • 選讀或稍後再讀: 談潮流與效能的第19–20章,以及作為全書優先順序總結的第21章。

閱讀時打開自己的程式碼。每讀完一章,就對照書末的警訊檢視一個自己寫的模組,並在下一個要寫的介面上試試「設計兩次」。與 Clean Code 的比較很適合當作討論練習:讀一讀馬丁的立場和 Ousterhout 的回應,判斷哪一種比較適用於你的程式碼庫。