跳轉到

3.4 設計原則:SOLID、DRY 與 KISS#

為什麼需要設計原則#

第 3.1、3.2 節談的是「如何組織系統」(模組化、架構)。本節聚焦更「菜鳥」卻同樣重要的問題:寫類別、函式、模組時的具體設計原則。 這些原則是前人從大量實務中沉澱出來的智慧,能幫我們寫出容易修改、不會一改就壞的程式碼。

其中最著名的三組是:SOLID、DRY、KISS。它們都不是嚴格的規律,而是有助於做出更好設計的「指引」。

SOLID:物件導向設計的五個原則#

SOLID 是五個物件導向設計原則的縮寫。我們逐個理解,不看空洞的定義,而是看每個原則解決的問題。

S — 單一職責原則(Single Responsibility Principle, SRP)#

一個類別,應該只有一個改變的理由。

意思是:一個類別應該只承擔一種明確的職責。如果一個類別既要管理資料庫、又要發 email、又要算營收,那它就有三個「改變的理由」——任何一個需求變動都會逼你改它,而且它變得很難理解與測試。

判斷方法:問「這個類別有幾個改變的理由?」多於一個,就該拆分。

O — 開放封閉原則(Open/Closed Principle, OCP)#

軟體實體(類別、模組、函式)應該對「擴展」開放,對「修改」封閉。

意思是:你應該能增加新功能(擴展),而不需要修改既有程式碼(封閉)。透過「多型/介面」來新增行為,而不是一直改原本的 if-else。

例:訂位系統算「折扣」時,與其寫 if (type == "vip") ... elif (type == "生日") ...(以後每加一種折扣就改這個函式),不如定義一個「折扣策略」介面,新增折扣時只需新增一個策略類別,不改原函式。

L — 里氏替換原則(Liskov Substitution Principle, LSP)#

子類型應該能替換其父類型,而且程式照樣正確運作。

意思是:如果一個類別 B 繼承自 A,你在任何使用 A 的地方換成 B,程式都不該出錯。如果子類別違背了父類別的承諾(例如繼承了「鳥」卻不會飛,繼承了「矩形」卻不允許改寬高),就違反了 LSP。

判斷方法:如果「B 是 A 的一種」這個說法在邏輯上不成立(例如「正方形是矩形」在程式裡可能破壞矩形行為),就不該用繼承。

I — 介面隔離原則(Interface Segregation Principle, ISP)#

用戶端不應該被強迫依賴它用不到的介面。

意思是:設計介面時,不要做一個「又肥又全能」的介面,強迫實作者實現一堆用不到的方法。應該把介面拆成多個小而專用的介面,讓用戶端只依賴自己需要的部分。

D — 依賴反轉原則(Dependency Inversion Principle, DIP)#

高層模組不應該依賴低層模組;兩者都應該依賴抽象。

意思是:不要讓高層業務邏輯直接依賴具體的低層實作(例如直接 new 一個資料庫物件)。而是依賴抽象介面(例如「儲存介面」),具體實作(SQL 資料庫、記憶體、雲端)再注入進來。這樣高層邏輯不會因為換一個儲存實作就被迫修改。

例:訂位系統的服務層不該直接依賴「MySQL」這個具體類別,而應依賴「訂位儲存庫」介面,將來要換成 PostgreSQL 或記憶體儲存時,只需新增一個實作,服務層不動。

DRY:Don't Repeat Yourself(不要重複你自己)#

每一份知識,在系統中應該有單一、明確、可信的表示。

DRY 針對的是重複的知識——同樣的邏輯、同樣的規則,在多個地方被複製貼上。DRY 的反面就是 2.4 節的「魔術數字」與到處散布的重複代碼。

為什麼 DRY 重要:重複的程式碼,一旦某處要修改,你得記得「所有」複製的地方都改——漏一處就產生 bug。而「單一真實來源」(Single Source of Truth)能避免這個問題。

例:「會員是 VIP 的折扣規則是 9 折」這條規則,如果散落出現在訂位、付款、報表三個地方,將來改規則時容易漏改。應該把規律收斂到一個地方(例如一個 DiscountPolicy 函式/常數)。

要小心 DRY 的濫用:若兩段程式碼只是「長得像」但語義不同、變化方向不同,硬是合併反而會「過度抽象」,增加耦合。DRY 追求的是知識不重複,而非文字不重複。

KISS:Keep It Simple, Stupid(保持簡單)#

最簡單的解法,往往是最好的解法。

KISS 提醒我們:不要為了「未來可能的需求」或「看起來很酷」而增加不必要的複雜度。它的精神呼應 Anthropic 關於 Agent 設計的核心原則(本書第二章會再遇到):保持簡單,只在你確認需要時才增加複雜度。

為什麼簡單很重要? - 簡單的程式碼容易理解、容易修改 - 簡單的系統bug 少、容易除錯 - 過度設計(Over-engineering)是浪費時間的常見原因

KISS 與「未來擴展」的平衡:KISS 不是拒絕思考未來,而是要求「現在只做需要做的,以最小的足夠複雜度」。當真的需要擴展時,再引入抽象——這就是所謂的 YAGNI(You Aren't Gonna Need It,你其實不需要)。

三組原則的層次#

把三組原則放在一起看,它們解決不同層次的問題:

原則 關注層次 核心一句話
SOLID 物件/類別的設計 讓程式好修改、好擴展、不易壞
DRY 知識的管理 消除重複,維持單一真實來源
KISS 整體的複雜度控制 保持簡單,不過度設計

它們共同的底層追求,正是第 3.1 節的核心——對抗複雜性,保持可維護。

AI 時代的設計原則#

AI 會生成大量程式碼,於是這些原則的重要性反而上升:

  • DRY 讓 AI 生成的重複程式碼被收斂(避免改一處漏一處)
  • SOLID 的介面/抽象,讓 AI 能依「契約」擴展而不破壞既有一致性(呼應 2.6 節的 Spec)
  • KISS 提醒我們,AI 往往會「過度設計」——你只問它要一個簡單函式,它可能給你套上一堆抽象。審查 AI 輸出時,KISS 是一面很好的鏡子。

一個務實建議:讓 AI 生成的程式碼,用這些原則來審查。看到 AI 產生了過度抽象、重複邏輯、違反單一職責的類別,就要像審查人類工程師一樣去修正。

本章小結#

  • SOLID:單一職責、開放封閉、里氏替換、介面隔離、依賴反轉
  • DRY:知識單一真實來源,拒絕重複
  • KISS:保持簡單,不過度設計(YAGNI)
  • 三組原則合力對抗複雜性、保持可維護
  • AI 時代:用這些原則審查 AI 生成程式碼尤其重要(AI 容易過度設計)

想一想#

  1. 找出你寫過的一個「違反單一職責」的類別或函式,把它拆開。
  2. DRY 與「代碼像但語義不同」如何取捨?舉一個「不該 DRY」的例子。
  3. 讓 AI 生成一段程式碼,然後用 SOLID/DRY/KISS 逐條檢查,看看它犯了哪些設計原則的毛病。