軟體系統做得越大,最讓人頭痛的往往不是某個單獨的錯誤,而是「只想改一點東西,卻不知道還會影響哪裡」。一個欄位名稱變更,可能牽動前端、後端、資料驗證與報表;新接手的開發者得先理解大量隱藏規則,才敢動程式。
「軟體設計哲學」技能就是用來處理這類情況。它把複雜度、深模組、資訊隱藏和策略式程式設計整理成可套用的檢查框架,讓你在設計 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:不要只按「執行時間順序」切模組
有些系統會直接按執行流程拆分模組:
- Read。
- Parse。
- Validate。
- Process。
- 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年查閱
