A Philosophy of Software Design:真正好的模組,應該把複雜藏在裡面

軟體設計哲學(A Philosophy of Software Design)技能是一套用來檢查與改善程式設計複雜度的工作方法,源自 John Ousterhout 的軟體設計觀點。它協助開發者或 AI 檢視模組介面是否夠簡單、設計知識有沒有外洩、程式是否過度拆分,以及每次修改是否讓系統更容易維護。重點不是增加更多抽象,而是把必要的複雜度放在對的地方。

軟體系統做得越大,最讓人頭痛的往往不是某個單獨的錯誤,而是「只想改一點東西,卻不知道還會影響哪裡」。一個欄位名稱變更,可能牽動前端、後端、資料驗證與報表;新接手的開發者得先理解大量隱藏規則,才敢動程式。

「軟體設計哲學」技能就是用來處理這類情況。它把複雜度、深模組、資訊隱藏和策略式程式設計整理成可套用的檢查框架,讓你在設計 API、審查程式碼或要求 AI 修改專案時,不只問「功能有沒有做出來」,也問「下一次修改會不會更難」。

這篇會詳細拆解技能的原則、適用情境與檢查方式,再用實際的程式設計情境示範。A Philosophy of Software Design 是這套方法的英文名稱與理論來源,本文重點是如何使用技能改善程式設計,不是介紹書籍版本或購買建議。

目錄 隱藏

軟體設計哲學技能主要解決哪些問題?

它特別適合處理「程式可以執行,但維護起來很辛苦」的問題。從技能的角度,最先檢查的不是程式碼有幾行,而是修改成本:一個小需求需要改幾個地方?使用一個模組前,得先知道多少內部規則?程式作者離開後,其他人能不能安全修改?

例如一個課程網站的報名、會員和通知功能,各自保存同一套學員狀態判斷。哪天要增加一種狀態,就得同時修改多處。這不只是重複程式碼的問題,而是同一項設計知識散落在不同模組。技能會引導你找出知識應該由哪個模組負責,再檢查能否用更簡單的介面提供服務。

什麼時候適合使用這個技能?

  • API 設計:函式參數太多、呼叫順序複雜,或每次使用都要查很多特殊條件。
  • 程式碼審查:想知道某次修改是否增加了模組依賴,而不只看程式會不會執行。
  • 重構既有系統:多個類別經常一起改,或出現只負責轉傳資料的薄薄一層。
  • 使用 AI 寫程式:AI 已經完成功能,卻可能為每個例外多加旗標、檔案和設定。
  • 模組責任不清:團隊對「這項規則應放在哪裡」沒有共識,導致同一知識被複製。

如果要處理的是變數命名、重複語法或單一函式的可讀性,可以搭配 Clean Code 的程式碼品質觀點;如果要判斷大型系統的依賴方向,可延伸到 Clean Architecture 的架構邊界。軟體設計哲學技能則聚焦:每個模組有沒有真正幫其他模組減少理解負擔。

這個技能的六大檢查原則

核心原則 設計時該問的問題
Complexity(複雜度) 一次小修改,為什麼需要理解或更動這麼多地方?
Deep Module(深模組) 介面能不能更簡單,卻提供更完整的能力?
Information Hiding(資訊隱藏) 重要的資料格式或處理規則是否散落在多個模組?
適度通用的模組 能不能用一個簡單介面滿足目前的多種需求?
Comments as Design(註解與設計) 註解有沒有交代承諾、假設和設計理由?
Strategic Programming(策略式程式設計) 這次做完功能,是否順手降低未來的維護成本?

下面從複雜度的三個症狀開始,依序拆解這六項原則,並說明哪些情況值得調整設計。

複雜度有三個最常見的症狀

1. Change Amplification:一個小改動,牽動很多地方

例如一個資料格式改名,結果前端、後端、匯入程式、驗證邏輯、報表都要一起改。如果某個知識散落在很多地方,修改成本就會被放大。

2. Cognitive Load:要記住太多東西,才能完成一件事

一個 API 如果有十幾個參數、特殊順序、隱藏限制與例外情況,呼叫者每次使用都得重新理解。使用前得先記住這些規則,本身就是理解成本。

3. Unknown Unknowns:你根本不知道還有什麼沒看到

你知道某個功能要改,卻不知道還有另一個模組依賴相同假設;程式可以編譯、測試也過了,但幾天後才發現另一條路徑壞掉。

Unknown Unknowns 之所以麻煩,是因為開發者甚至不知道自己該搜尋什麼。

複雜度的兩個根源:Dependency 和 Obscurity

複雜度通常來自兩件事。

Dependency(依賴)代表一個元件不能獨立理解或修改;Obscurity(晦澀)則代表重要資訊沒有清楚呈現。

依賴本身不一定是壞事。任何大型系統都會有依賴。

更重要的是:依賴是否清楚?是否集中?修改時能不能知道會影響誰?

當依賴和晦澀一起出現時,系統就很容易進入「大家不敢改」的狀態。

複雜度很少一次爆炸,通常是慢慢累積

軟體設計變差,往往不是某一天有人決定「今天故意把系統做得難以維護」。比較常見的是很多小決定疊在一起:

  • 這次先多加一個布林旗標(boolean flag)。
  • 這個特殊情況先複製一段程式碼。
  • 這個設定先讓呼叫者自己傳。
  • 這個格式先在兩邊各寫一份。

每一個選擇單獨看都不嚴重,但半年後會變成大量認知負荷。可以把它想成 Complexity Budget(複雜度預算):每次新增介面、例外與依賴,都在消耗未來修改系統的能力。

Deep Module:好的模組,介面應該比內部簡單很多

Ousterhout 最知名的概念之一,是 Deep Module(深模組)。

一個深模組提供很多能力,但對外只暴露少量、穩定、容易理解的介面。

可以把它想成:

模組價值 ≈ 提供的功能 ÷ 介面帶來的理解成本

這是方便理解的概念比喻,不是可直接計算的工程指標。如果一個類別只有十幾行程式碼,卻要求呼叫者理解許多方法、狀態和特殊順序,它可能很「小」,卻不夠深。

Shallow Module:看起來模組化,實際上只是增加邊界

Shallow Module(淺模組)最典型的問題是:介面很複雜,但內部並沒有隱藏多少東西。

例如一個方法只把所有參數原封不動傳給下一層,自己沒有做任何抽象或判斷。

這種 Pass-through Method(轉傳方法)會增加一個閱讀與維護邊界,卻沒有減少任何複雜度。

所以「一個類別只做一件事」不能被機械理解成「每件小事都建立一個類別」。

模組切得太碎,會形成 Classitis:類別很多、檔案很多、介面很多,但開發者要理解的東西反而更多。

模組不是越小越好,要看它有沒有把複雜藏起來

小函式、小類別有時很好,但大小不是目標。

更值得問的是:

  • 呼叫者需要知道多少細節?
  • 如果內部實作改變,呼叫者要不要跟著改?
  • 這個模組有沒有隱藏一個重要決策?

如果答案都是「沒有」,那這個抽象可能只是在把程式碼切碎。

如果你想先從命名、函式、可讀性等程式碼層面整理,可以搭配Clean Code:好程式碼不是能跑就好,而是讓下一個人看得懂、改得動一起看;這篇談的層次更偏向模組與複雜度分配。

Information Hiding:模組要藏的是「知識」

Information Hiding(資訊隱藏)不是單純把變數設成 private。

資訊隱藏真正要處理的是設計知識。

例如一個資料格式、通訊協定、快取策略、排序規則,如果只有一個模組知道,就可以在那裡修改。

如果同一個知識散落在五個模組,這個設計決策就已經 Leakage(洩漏)。

資訊洩漏的訊號:兩個模組總是要一起改

如果你發現:

  • 每次改 A 都一定要改 B。
  • 兩邊都知道同一個資料格式。
  • 兩邊都重複判斷同一個規則。
  • 某個協定細節被很多模組直接使用。

這通常代表 Knowledge Boundary(知識邊界)沒有設計好。

解法可能是合併模組,也可能建立新的模組,讓那個共享知識只存在一個地方。

Temporal Decomposition:不要只按「執行時間順序」切模組

有些系統會直接按執行流程拆分模組:

  1. Read。
  2. Parse。
  3. Validate。
  4. Process。
  5. Write。

表面看起來很整齊,但如果 Read、Parse、Validate 都需要理解同一個資料格式,知識還是被拆散了。

Ousterhout 提醒,模組更應該依「共同知識」來組織,而不只是依時間順序。

把同一個設計決策集中管理,通常比把流程每一步都拆成獨立類別更能降低修改成本。

Push Complexity Downward:把難題放到比較低層解掉

如果某個複雜決策可以由模組自己判斷,就不要一直要求呼叫者設定。

例如某個方法每次都要求呼叫者指定緩衝區大小、編碼、重試次數和鎖定模式,就把太多實作決策推給外部使用者。

有時候更好的設計是:

  • 模組自己選合理預設值。
  • 自動偵測可以偵測的資訊。
  • 只有確實需要由外部控制的少數選項才暴露。

每一個 Configuration Option(設定選項)都會讓呼叫者多一個決策。

如果所有事情都「保留彈性」,使用者最後就得替模組做完所有設計工作。

General-Purpose Module:通用不等於為未來所有可能性預留

Ousterhout 提倡 Somewhat General-Purpose(適度通用)的模組。

這個概念介於兩個極端之間:

  • 太特殊:每個使用情境都有自己的方法。
  • 太通用:為還不存在的需求設計大量抽象。

更實用的問題是:

什麼是可以涵蓋目前所有需求的最簡單介面?

不是為十年後想像中的功能預先做 Framework,也不是每遇到新情況就增加一個特殊方法。

過度設計和策略式設計不是同一件事

很多團隊聽到「要先設計」,就擔心 Over-engineering(過度設計)。

這個擔心是合理的。

策略式設計不要求你預測所有未來需求,而是願意多花一點時間,把目前已知的複雜度整理好。

例如:

  • 一個介面是否可以少兩個參數?
  • 兩個總是一起改的類別是不是該合併?
  • 同一種資料格式的處理規則能不能集中?
  • 新增功能時,是否順便把原本奇怪的邊界修掉?

這些做法的目的,是在每次修改時避免複雜度繼續累積,不是建造一個沒人需要的大架構。

Tactical Programming:今天省下的時間,可能只是向未來借

Tactical Programming(戰術式程式設計)追求「先讓功能跑起來」。

短期看很快,但如果每一個需求都用 special case、boolean flag、copy-paste 解掉,速度其實是在借未來的。

下一個人會花更多時間理解,下一個功能也會更難加入。

Ousterhout 用 Tactical Tornado 形容一種工程師:看起來產出很快,但身後留下一堆結構問題。

更危險的往往不是一次大錯,而是每天很多個「先這樣就好」。

Strategic Programming:每次改程式,也順手改善設計

Strategic Programming(策略式程式設計)不是要求每次功能開發都大重構。

它比較像固定投入一小部分時間處理設計。

例如新功能碰到一個很難用的 API,就不要再加一層 workaround,而是順手把介面改得更清楚。

這種投入在一開始可能多花一些時間,但會讓之後的修改愈來愈容易。

Comments:好的註解要保存程式碼本身看不到的資訊

「好程式碼不需要註解」只對一部分低階細節成立。

程式碼可以告訴你做了什麼,但通常無法完整說明:

  • 為什麼選這個做法。
  • 哪些條件一定成立。
  • 哪個設計決策不能隨便改。
  • 為什麼不用另一種看起來更簡單的方法。

這些都是 Design Intent(設計意圖)。

如果不寫下來,作者離開之後,新的人只能靠猜。

Interface Comment 應該描述「承諾」,不是重述程式碼

一個好的介面註解,應該告訴呼叫者:

  • 這個模組抽象上提供什麼。
  • 輸入有哪些限制。
  • 輸出代表什麼。
  • 錯誤情況怎麼處理。
  • 有沒有重要副作用。

至於「for loop 從 0 跑到 n」這種程式碼已經說得很清楚的東西,不需要再翻譯一次。

如果註解很難寫,設計可能本身就太難解釋

Comment-driven Design(註解驅動設計)有一個很實用的做法:先寫介面註解,再寫實作。

如果你連「這個模組到底承諾什麼」都很難講清楚,通常代表抽象還沒想清楚,不只是註解寫法的問題。

這時候先調整介面,比直接開始寫程式碼更省時間。

Red Flags:看到這些訊號,就該檢查設計

訊號 可能問題
一個小需求要改很多檔案 Change Amplification
呼叫一個方法前要知道很多背景 Cognitive Load 過高
兩個模組永遠一起改 Information Leakage
很多類別只有一兩個轉傳方法 Shallow Module/Classitis
新增功能一直加 boolean flag 介面正在累積 special cases
註解只把程式碼翻成中文或英文 沒有保存 Design Intent
沒人敢改某區塊 Unknown Unknowns 過多

Deep Module 和 Clean Architecture 有什麼關係?

兩者關注層級不同,但方向一致。

Clean Architecture 比較強調 Dependency Direction(依賴方向)與商業規則邊界;A Philosophy of Software Design 更關注每個模組是否真的把複雜度封裝好。

即使架構分層看起來正確,只要每一層都暴露太多細節,系統仍然難以維護。

如果想繼續看架構邊界,可以延伸閱讀Clean Architecture:為什麼商業邏輯不該被框架與資料庫綁死。

不要為了消滅所有 Dependency,製造更多 Abstraction

複雜度管理很容易走到另一個極端:每看到一個依賴,就再加上一層介面、工廠、轉接器或包裝類別。

但每一個抽象本身也是介面,也需要學習與維護。

所以抽象不是免費的。

只有當它真的隱藏重要知識、降低呼叫者負擔、或讓未來修改更集中時,才有價值。

一張表判斷一個模組夠不夠「深」

檢查問題 好的訊號
一句話說得出這個模組負責什麼嗎? 責任清楚
介面比內部實作簡單很多嗎? 複雜度被封裝
內部實作改變時,呼叫者不用跟著改嗎? 資訊隱藏有效
模組有沒有藏住重要設計決策? 抽象有價值
呼叫者需要記很多特殊規則嗎? 越少越好
兩個模組是不是經常一起改? 若是,要檢查 Leakage

用技能評估模組:八題設計檢查法

這套技能提供八項快速檢查問題,幫你把「程式很難維護」變成可討論的具體項目。每題可以先記錄通過或未通過,再說明證據與改善方向。技能內部也提供以十分為滿分的簡易評分方式;這是整理討論的檢查工具,不是經過驗證的軟體品質指標,也不能單靠分數保證系統可靠。

檢查問題 若不符合,可以怎麼改進
模組的責任能用一句話說清楚嗎? 重新界定責任,避免沒有明確用途的模組
對外介面比內部實作簡單嗎? 隱藏更多細節,合併缺乏價值的淺模組
改變內部實作時,呼叫端可以不受影響嗎? 集中管理已外洩的設計知識
介面註解有清楚說明抽象與承諾嗎? 描述輸入、輸出、限制與設計意圖
程式碼審查會討論設計品質嗎? 把「是否增加複雜度」納入審查問題
每個模組有隱藏重要設計決策嗎? 讓模組依共有的設計知識分工
新人不用看完實作也能理解模組邊界嗎? 改善介面、命名與必要的說明
修改功能時,有持續改善設計嗎? 在合理範圍內安排結構改善與測試

檢查時應要求 AI 指出具體檔案、介面或依賴關係,再解釋為什麼沒有通過。沒有看到程式碼就直接給高分或低分,都沒有足夠依據。若沒有實際執行測試,也不能把「設計看起來不錯」當成「功能已驗證」。

實際應用:怎麼讓 AI 用這套技能審查程式?

假設你的專案有三個類別,分別負責讀取報名資料、檢查學員狀態,以及發送通知。每個類別都要知道「完成報名」有哪些狀態,修改規則時往往要一起改。這是一個假設情境,用來說明技能如何判斷,不代表任何實際網站已經有這個問題。

技能不會要求你一律把所有類別合併。它會先確認:三個類別是否共享同一項狀態定義?哪個模組應該擁有這項知識?能否對其他功能提供簡單又穩定的查詢介面?這樣重構的目標才是減少外部依賴,而不只是讓檔案數量變少。

可以把下面的任務要求提供給支援相關技能、且能讀取專案的 AI 工具:

請使用 A Philosophy of Software Design 的軟體設計檢查方法,先閱讀目前的相關模組,不要直接修改程式。依複雜度、深模組、資訊隱藏、適度通用、介面註解與策略式設計六項原則分析。請用八題設計檢查表列出:實際證據、通過或未通過的理由,以及最值得優先改善的三個問題。最後提出最小範圍的重構建議、可能影響與需要執行的測試,等我確認後再修改。

這種要求比單純輸入「幫我把程式碼最佳化」更明確。它讓 AI 必須先提出可檢查的依據,再安排改動與測試,也避免為了讓程式碼看起來漂亮,就一次重寫整個專案。

使用這個技能時,還有哪些限制?

首先,技能是一套設計檢查方法,不會自動產生讀取程式、修改網站或部署伺服器的權限。它能否分析真實專案,取決於 AI 是否確實讀到了程式碼,以及是否具備相應工具。AI 回覆「已按照技能分析」,也不代表真的取得完整檔案或執行測試。

其次,模組設計並不是唯一要考慮的工程問題。效能、安全、資料正確性、相容性與交付期限,都可能影響你應該怎麼選擇。為了隱藏複雜度而增加一個無法觀察的黑盒子,也未必是好的設計。

最後,不要為了提高檢查分數而盲目重構。應先確認目前最麻煩的修改成本,再從可以回復、可以測試的小範圍開始。沒有足夠證據的地方,先標記為待確認,而不是補出看似精確的判斷。

常見問題

這個技能和一般程式碼檢查有何不同?

一般程式碼檢查可能著重錯誤、命名與格式;軟體設計哲學技能更關注模組之間的知識分配、介面成本,以及下一次修改會不會更難。

不會寫程式,也能請 AI 使用這個技能嗎?

可以請 AI 解釋模組設計、整理問題與提出改善方向。不過,真正涉及程式修改時,仍要提供可讀取的專案並確認測試結果;重要系統最好由熟悉技術的人協助驗收。

這個技能會自動修改網站或程式嗎?

不會。技能本身提供分析規則,實際能不能讀取、修改或發布程式,還是取決於所在工具的權限與設定。重要操作應先審查方案,再決定是否執行。

如何確認 AI 真的依照這套技能分析?

要求它提供具體的程式位置、八題檢查結果、失敗原因與可驗證的改善建議。如果沒有讀到完整程式碼或缺少測試,就應清楚標示限制,不能只靠一句「檢查完成」當作證據。

類別越小,設計就越好嗎?

不一定。小類別如果只增加介面與跳轉,卻沒有隱藏重要複雜度,反而可能讓整體更難理解。

Deep Module 是不是代表把很多功能塞進一個大類別?

不是。重點是「介面簡單、能力強」,不是單純讓檔案變大。模組仍然要有清楚的抽象與責任。

Information Hiding 和 private 有什麼不同?

private 是語言層級的存取控制;Information Hiding 更關注設計知識是否只存在一個地方。資料可以是 private,設計假設仍然可能洩漏到其他模組。

通用設計會不會變成過度設計?

會,所以 Ousterhout 強調的是適度通用。先找能涵蓋目前所有需求的最簡單介面,不為假想需求提前加入大量抽象。

好程式碼到底需不需要註解?

需要保存程式碼本身無法清楚表達的資訊,例如設計意圖、限制、假設與介面承諾。重述程式碼的註解則價值很低。

策略式程式設計會不會拖慢開發?

短期通常會多花一些設計時間,但目的是避免後續每個需求都因複雜度增加而愈做愈慢。

好的軟體設計,是把複雜度放對地方

大型系統一定會有複雜度。

差別在於這些複雜度是散落在每個呼叫者腦中,還是集中在少數設計良好的模組裡。

Deep Module、Information Hiding、適度通用、策略式程式設計,最後都在做同一件事:讓下一次修改需要理解的東西更少、影響範圍更清楚。

每次新增功能時,都試著讓系統更容易理解,而不是留下新的特殊規則。這樣系統即使成長,也比較容易維護。

參考資料

John Ousterhout|A Philosophy Of Software Design|2026年查閱
Stanford University|CS 190 Course Information|2022
John Ousterhout|Publications|2026年查閱

元氣站長
元氣站長

經營網路事業,賺取持續收入!
網路賺錢修煉,成就理想人生!