程式碼明明能正常執行,為什麼打開檔案要修改一個功能時,卻得東修西補?
只改了一個按鈕顏色,付款邏輯卻爆掉了。這種經驗大家多少都有過吧。
能用兩個詞精準解釋這個原因的概念,就是耦合度與內聚力。
耦合度越低越好,內聚力越高越好。好的設計,有八成都是從這句話開始的。
本文會結合我的經驗,說明這兩個概念究竟是什麼、為什麼被稱為軟體設計原則的根基,以及如何應用到實際程式碼中。
先把重點整理出來。
- 耦合度:模組彼此糾纏的程度 → 越低越好
- 內聚力:單一模組中的程式碼專注於一件事的程度 → 越高越好
- 這兩者是 SOLID、設計模式等知名原則的基礎。
- 目標只有一個:「容易修改,也容易替換的程式碼」。
耦合度與內聚力,到底是什麼?
先用一句話整理這兩個概念:
耦合度指模組與模組之間的關係,內聚力則指單一模組內部的連結程度。
因為方向完全相反,很容易混淆;我是這樣記的。
耦合度是「與外部的距離」,內聚力是「內部的凝聚」。
耦合度高時,修改 A 會連帶影響 B、C,像骨牌一樣。
內聚力低時,付款邏輯、寄信和記錄日誌混在同一個類別裡。光看名稱,根本不知道這個類別在做什麼。
好的設計會把兩者往相反方向推:對外鬆散,對內緊密。
為什麼這是設計原則的「根基」?
耦合度與內聚力最早在 1970 年代的結構化設計理論中被整理出來,一般認為是 Larry Constantine 提出的。
將近半個世紀後仍然適用,是因為它們描述的是不受特定語言或潮流影響的本質。
拆解我們熟悉的各種知名原則,最後都會回到這裡。
| 原則・模式 | 歸根究柢想表達的事 |
|---|---|
| 單一職責原則(SRP) | 提高內聚力 |
| 相依性反轉原則(DIP) | 降低耦合度 |
| 介面隔離原則(ISP) | 切斷不必要的耦合 |
| 大多數設計模式 | 低耦合 + 高內聚 |
看到了吧?名稱雖然不同,根基只有一個。
學習新的原則或模式時,我會先問:「這是要降低耦合度,還是提高內聚力?」這樣就理解一半了。
如何降低耦合度?用程式碼來看。
光說可能太抽象,來看一個短例子。
下面是高耦合的程式碼。Order 類別直接知道特定的付款業者。
class Order {
let pay = KakaoPay() // 和 Kakao Pay 緊密耦合
func checkout() {
pay.send() // 要換成其他付款方式,就得拆掉這裡
}
}
把付款業者換成 Toss 的瞬間,就得打開 Order 修改。這就是高耦合的典型例子。
這次用協定把兩者隔開。
class Order {
let pay: Payment // '只依賴「付款」這個契約
init(pay: Payment) { self.pay = pay }
func checkout() { pay.send() } // 換成什麼都沒關係
}
現在不論傳入哪家付款業者,Order 都不必在意。只要替換實作即可。
我在實務上這樣修改後,即使收到新增付款業者的需求,也幾乎不用碰既有程式碼。這就是低耦合的力量。
要如何提高內聚力?
內聚力可以用「這個模組是否只做一件事」來判斷。
我的標準很簡單:說出類別名稱時,如果其中的方法都能自然浮現,就是高內聚。
例如 UserService 裡混入以下內容,就是危險訊號。
- 註冊會員(本職工作)
- 寄送行銷電子郵件(別人的工作)
- 產生 CSV 報表(又是別人的工作)
這就像互不相關的工作一起住在同一間房子裡。
這時可以讓 EmailSender 負責電子郵件,讓 ReportGenerator 負責報表,各自擁有自己的房間。
這樣之後修改寄信邏輯時,就不用再打開 UserService。變更的波及範圍變小了。
低耦合與高內聚最終追求同一個方向:阻止變更擴散。
常見問題(Q&A)
Q. 耦合度與內聚力,應該先在意哪一個?
我建議先處理內聚力。把模組拆到各自專注於一件事時,耦合度通常也會自然整理好。
Q. 耦合度一定是 0 才最好嗎?
不是。模組完全沒有連結,程式就無法執行。目標不是零,而是「只保留必要程度,而且保持鬆散」。
Q. 需要從一開始就設計得完美嗎?
不需要。我也會先讓程式運作,等到修改開始痛苦時,再逐步切斷耦合、提高內聚。重構本來就是反覆進行的事。
耦合度與內聚力不是華麗的新技術,卻是能寫出長久存活程式碼的人共有的習慣。
不妨在今天寫的程式碼中問自己一次:「如果修改這裡,影響會擴大到哪裡?」累積這個問題,設計直覺就會快速成長。加油!

