軟體設計

耦合度與內聚力:軟體設計的根基

程式碼明明能正常執行,為什麼打開檔案要修改一個功能時,卻得東修西補?

閱讀 4 分鐘
耦合度與內聚力:軟體設計的根基 封面圖

程式碼明明能正常執行,為什麼打開檔案要修改一個功能時,卻得東修西補?

只改了一個按鈕顏色,付款邏輯卻爆掉了。這種經驗大家多少都有過吧。

能用兩個詞精準解釋這個原因的概念,就是耦合度與內聚力。

耦合度越低越好,內聚力越高越好。好的設計,有八成都是從這句話開始的。

本文會結合我的經驗,說明這兩個概念究竟是什麼、為什麼被稱為軟體設計原則的根基,以及如何應用到實際程式碼中。

先把重點整理出來。

  1. 耦合度:模組彼此糾纏的程度 → 越低越好
  2. 內聚力:單一模組中的程式碼專注於一件事的程度 → 越高越好
  3. 這兩者是 SOLID、設計模式等知名原則的基礎。
  4. 目標只有一個:「容易修改,也容易替換的程式碼」。

耦合度與內聚力,到底是什麼?

先用一句話整理這兩個概念:

耦合度指模組與模組之間的關係,內聚力則指單一模組內部的連結程度。

因為方向完全相反,很容易混淆;我是這樣記的。

耦合度是「與外部的距離」,內聚力是「內部的凝聚」。

耦合度高時,修改 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. 需要從一開始就設計得完美嗎?

不需要。我也會先讓程式運作,等到修改開始痛苦時,再逐步切斷耦合、提高內聚。重構本來就是反覆進行的事。


耦合度與內聚力不是華麗的新技術,卻是能寫出長久存活程式碼的人共有的習慣。

不妨在今天寫的程式碼中問自己一次:「如果修改這裡,影響會擴大到哪裡?」累積這個問題,設計直覺就會快速成長。加油!

延伸閱讀