software-design-philosophy

software-design-philosophy

熱門

透過深層模組、資訊隱藏與策略性程式設計來管理軟體複雜度。當使用者提及「模組設計」、「API 太複雜」、「淺層類別」、「複雜度預算」、「策略 vs 戰術」、「深層模組」、「資訊洩漏」、「轉送方法」、「這段程式碼過度設計」或「簡化這個設計」時使用。也適用於審查介面簡潔度、評估抽象是否值得、決定是否該寫註解,或選擇通用 vs 專用方法。涵蓋深層與淺層模組、複雜度紅旗,以及作為設計文件的註解。關於程式碼品質,請參閱 clean-code。關於架構邊界,請參閱 clean-architecture。

1698星標
171分支
更新於 2026/7/16
SKILL.md
唯讀
名稱
software-design-philosophy
描述

透過深層模組、資訊隱藏與策略性程式設計來管理軟體複雜度。當使用者提及「模組設計」、「API 太複雜」、「淺層類別」、「複雜度預算」、「策略 vs 戰術」、「深層模組」、「資訊洩漏」、「轉送方法」、「這段程式碼過度設計」或「簡化這個設計」時使用。也適用於審查介面簡潔度、評估抽象是否值得、決定是否該寫註解,或選擇通用 vs 專用方法。涵蓋深層與淺層模組、複雜度紅旗,以及作為設計文件的註解。關於程式碼品質,請參閱 clean-code。關於架構邊界,請參閱 clean-architecture。

軟體設計哲學框架

一個實用框架,用於管理軟體工程的基本挑戰:複雜度。在設計模組、審查 API、重構程式碼或提供架構決策建議時,應用這些原則。

核心原則

撰寫軟體的最大限制,是我們理解自己所建立系統的能力。 複雜度是敵人:它讓系統難以理解、難以修改,並成為錯誤的來源。評估每個設計決策時,問自己「這會增加還是減少系統的整體複雜度?」——目標不是零複雜度,而是最小化不必要的複雜度,並將必要的複雜度集中到可管理的地方。

評分

目標:10/10。 在審查或建立設計時,根據滿足多少個快速診斷列來評分(每列約 1.25 分),然後對照區間進行合理性檢查:

  • 9-10 — 深層模組,介面遠比實作簡單;沒有資訊洩漏(實作可以改變而不影響呼叫者);介面註解捕捉設計意圖;設計改進是常態。八項診斷全部通過。
  • 6-8 — 大致深層,但有一兩個洩漏、淺層類別或未記錄的抽象。5-6 項診斷通過。
  • 3-5 — 類別炎或時間分解、反覆洩漏、僅重述程式碼的註解。2-4 項診斷通過。
  • ≤2 — 戰術龍捲風程式碼:淺層模組、普遍洩漏、未記錄設計意圖。0-1 項診斷通過。

務必說明當前分數、未通過的診斷列,以及每項需要哪些具體改變才能達到 10/10。

軟體設計框架

管理複雜度並產出易於理解和修改的系統的六項原則:

1. 複雜度及其成因

核心概念: 複雜度是指系統結構中任何使其難以理解和修改的部分。它表現為三種症狀——變更放大、認知負荷和未知的未知——並有兩個成因:依賴關係和模糊性。

關鍵見解:

  • 變更放大:一個簡單的變更需要修改多處
  • 認知負荷:開發者必須記住太多資訊才能進行變更
  • 未知的未知:不明顯需要改變什麼或哪些資訊相關——這是最糟的症狀
  • 複雜度是漸進的——它由數百個小決策累積而成(「千刀萬剮」),因此每個決策都很重要

程式碼應用:

情境 模式 範例
變更放大 集中共享知識 提取顏色常數,而不是在 20 個檔案中硬編碼 #ff0000
認知負荷 減少開發者需知道的資訊 open(path) 而非要求緩衝區大小、編碼、鎖定模式
未知的未知 讓依賴關係明確 型別系統和介面揭示變更影響的範圍
模糊性 精確命名 numBytesReceived 而非 n;retryDelayMs 而非 delay

請參閱 references/complexity-symptoms.md 當你需要指出程式碼庫有哪種症狀後再修復——每種症狀的辨識測試、依賴關係分類(語法/語意/時間/隱藏)、C = Σ(cp·tp) 成本公式,以及一個 10 列的紅旗表格。

2. 深層 vs 淺層模組

核心概念: 最好的模組是深層的:強大的功能背後是簡單的介面。淺層模組的介面相對於其提供的功能來說過於複雜——它們增加複雜度而非隱藏它。

為什麼有效: 介面是模組對系統其他部分施加的成本;實作則是效益。因此,一個比你自己重新實作還難學的方法就是淨負值——深度,而非程式碼行數,決定了一個模組是否值得存在。

關鍵見解:

  • 深度 = 提供的功能 / 施加的介面複雜度(Unix 檔案 I/O 是深層的;薄薄的 Java I/O 包裝是淺層的)
  • 「類別炎」:建立過多小型淺層類別的疾病——每個介面都增加認知負荷
  • 小方法本身並非好事;深度比大小更重要
  • 最好的抽象將顯著的複雜度隱藏在幾個簡單概念之後

程式碼應用:

情境 模式 範例
深層模組 將複雜度隱藏在簡單 API 後 file.read(path) 隱藏磁碟區塊、快取、緩衝、編碼
類別炎治療 合併相關的淺層類別 RequestParser + RequestValidator + RequestProcessor → 一個 RequestHandler
介面簡潔 更少的參數、更少的方法 config.get(key) 搭配合理的預設值,而非 15 個建構子參數

請參閱 references/deep-modules.md 當判斷一個抽象是否值得——深度比的前後程式碼、類別炎治療的詳細步驟,以及案例研究(Unix I/O、GC、TCP/IP)。

3. 資訊隱藏與洩漏

核心概念: 每個模組應封裝其他模組不需要的知識。資訊洩漏——一個設計決策反映在多個模組中——是軟體設計中最重要的紅旗之一。

為什麼有效: 存在於一個模組中的決策可以在那裡改變,其他地方不變;同樣的決策洩漏到 N 個模組,會將一次編輯變成 N 次編輯,而且編譯器不會提醒你。隱藏正是將變更放大轉回局部變更的方法。

關鍵見解:

  • 時間分解導致洩漏:按何時發生來拆分程式碼會強制跨模組共享知識——應按知識來組織
  • 透過資料格式、協定或共享假設的後門洩漏是最微妙的形式
  • 裝飾者經常洩漏——它們暴露被裝飾的介面
  • 如果兩個模組共享知識,要麼合併它們,要麼建立一個封裝該知識的新模組

程式碼應用:

情境 模式 範例
格式洩漏 集中序列化 一個模組擁有 JSON 編碼/解碼,而非到處 json.dumps
時間分解 按知識而非時間組織 將「讀取設定」和「套用設定」合併為一個設定模組
協定洩漏 抽象傳輸細節 MessageBus.send(event) 隱藏 HTTP vs. gRPC vs. 佇列

請參閱 references/information-hiding.md 當一個變更迫使你同步編輯兩個模組時——四種洩漏形式(介面、後門、時間、裝飾者)附程式碼、五種減少策略、HTTP 處理案例研究,以及一個偵測表格。

4. 通用 vs 專用模組

核心概念: 設計「某種程度上通用」的模組:介面足夠通用以支援多種用途,實作則處理當前需求。問:「能涵蓋我所有當前需求的最簡單介面是什麼?」

為什麼有效: 違反直覺的是,通用介面通常更簡單——特殊情況方法會隨著需求增長而倍增,而一個通用方法可以吸收它們。陷阱是另一方向:當前需求不需要的通用性是投機複雜度,為可能永遠不會到來的使用案例付出代價。

關鍵見解:

  • 「某種程度上通用」是太特定和太通用之間的最佳點
  • 將複雜度向下推:底層模組應處理困難情況,讓上層保持簡單
  • 設定參數通常代表未能做出決定——每個參數都是推給呼叫者的複雜度
  • 有疑問時,先實作更簡單、更通用的方法

程式碼應用:

情境 模式 範例
API 通用性 為概念而非單一使用案例設計 text.insert(position, string) 而非 text.addBulletPoint()
減少設定 自動決定行為 自動偵測檔案編碼,而非 encoding 參數
避免過度專用化 一個通用方法勝過多個特定方法 store(key, value, options) 而非 storeUser()、storeProduct()、storeOrder()

請參閱 references/general-vs-special.md 當選擇介面應多通用時——「所有當前需求的最簡單介面」測試、設定參數反模式,以及將複雜度向下推的詳細步驟。

5. 作為設計文件的註解

核心概念: 註解應描述程式碼中不明顯的內容:設計意圖、抽象理由、不變量和假設。「好的程式碼會自我文件化」是一個迷思,僅對低層實作細節成立。

為什麼有效: 程式碼只能記錄做了什麼——永遠無法說明為什麼選擇這種方法而非其他,或它默默假設了什麼。這個理由是系統中最易流失的資訊:它只存在於作者腦中,一旦作者離開就消失,因此註解是捕捉它的唯一機會。

關鍵見解:

  • 四種類型:介面註解(最重要——它們定義抽象)、資料結構成員註解、實作註解、跨模組註解
  • 先寫註解(註解驅動設計)以在寫程式碼前釐清思路
  • 不要重複程式碼已說明的內容;將註解放在它們描述的程式碼旁邊,並一起更新
  • 如果註解很難寫,設計可能太複雜

程式碼應用:

情境 模式 範例
介面註解 描述抽象,而非實作 「回傳最接近位置的 widget,若無則回傳 null」
資料結構註解 解釋不變量 「列表按優先級降序排序;平手時按插入順序」
實作註解 解釋為什麼,而非做什麼 「// 二元搜尋:列表始終排序,可容納 10 萬+ 項目」
跨模組註解 連結相關決策 「// 此逾時必須與 RetryPolicy.java 中的重試間隔一致」

請參閱 references/comments-as-design.md 當撰寫或審查註解且不確定應包含什麼時——四種註解類型附範例、註解驅動設計程序,以及對自我文件化程式碼迷思的反駁。

6. 策略 vs 戰術程式設計

核心概念: 戰術程式設計快速讓功能運作,並隨著每個捷徑累積複雜度。策略程式設計投入 10-20% 的額外努力在良好設計上,將每個變更視為改善結構的機會。

為什麼有效: 戰術速度是借來的:每個捷徑讓未來變更更困難,而策略投資會複利——策略性設計的系統在幾個月內就會變得更易於開發。

關鍵見解:

  • 戰術龍捲風:快速交付但留下破壞的開發者——短期受讚揚,長期具破壞性
  • 你的主要工作是做出一個恰好能運作的偉大設計,而不是一個恰好有設計的運作程式碼
  • 新創公司最需要策略程式設計——早期捷徑會隨著團隊成長複合成癱瘓性債務
  • 每個變更都是投資機會:讓程式碼變得更好一點;重構是每個功能的一部分,而非特殊事件

程式碼應用:

情境 模式 範例
戰術陷阱 抵制快速骯髒的修復 不要為「就這一個特殊情況」添加布林參數
策略投資 在功能開發期間改善結構 在新增功能時重構尷尬的模組介面
設計審查 評估結構,而非僅正確性 問「這會讓系統更簡單嗎?」而不只是「它能運作嗎?」

請參閱 references/strategic-programming.md 當決定一個變更應投入多少設計努力,或為其辯護時——10-20% 投資數學、戰術龍捲風模式,以及為什麼新創公司最需要策略程式設計。

常見錯誤

錯誤 為什麼失敗 修正
建立過多小型類別 類別炎增加介面而無深度;每個邊界都是認知負擔 將相關的淺層類別合併為更深層的模組
按時間順序拆分模組 「讀取、處理、寫入」強制跨模組共享知識 將共享知識的程式碼分組到一個模組
在介面中暴露實作 呼叫者依賴內部實作;變更會傳播 圍繞抽象設計介面;隱藏格式和協定
將註解視為可選 設計意圖和假設遺失;新人猜錯 先寫介面註解;與程式碼一起維護
萬物皆設定參數 參數推給呼叫者是你拒絕做出的決定(見 §4) 自動決定行為;提供合理的預設值
快速骯髒的戰術修復 捷徑累積直到系統無法運作 投入 10-20% 額外努力;將每個變更視為設計機會
轉送方法 僅轉送引數的方法增加介面但無功能 將轉送合併到呼叫者或被呼叫者
為特定使用案例設計 專用介面累積特殊情況 問:涵蓋所有當前需求的最簡單介面?

快速診斷

問題 如果否 行動
你能用一句話描述每個模組嗎? 模組做太多或缺乏目的 拆分為連貫、可描述的職責
介面是否比實作簡單? 模組淺層——複雜度向外洩漏 隱藏更多;將淺層類別合併為更深層的
你能在不影響呼叫者的情況下改變實作嗎? 資訊正在跨邊界洩漏 將洩漏的知識封裝在一個模組中
介面註解是否描述抽象? 設計意圖遺失;模組將被誤用 記錄模組承諾什麼,而非如何運作
設計討論是否納入程式碼審查? 審查捕捉錯誤但不捕捉複雜度增長 將「這是否減少複雜度?」加入審查標準
每個模組是否隱藏一個重要的設計決策? 模組圍繞程式碼而非資訊組織 重新組織,使每個模組擁有特定知識
新人能否在不閱讀實作的情況下理解模組邊界? 抽象未記錄或洩漏 改善介面註解;簡化介面
你是否花費 10-20% 的時間在設計改善上? 債務隨著每個功能累積 在每個 PR 中包含設計改善

延伸閱讀

如需包含詳細範例的完整方法論:

關於作者

John Ousterhout 是史丹佛大學電腦科學系的 Bosack Lerner 教授,也是 Tcl 腳本語言和 Tk 工具包的創作者。他從史丹佛 CS 190 課程中發展出《軟體設計哲學》,將數十年的系統建構經驗提煉為適用於各種語言和規模的原則。