單一職責原則 SRP 深度實踐 SOLID 原則解耦程式碼提升維護性

me
林彥成
2023-09-25 | 3 min.
文章目錄
  1. 1. 什麼是單一職責原則 (SRP)?
  2. 2. Single Responsibility Principle (SRP)
  3. 3. 康威定律 (Conway’s Law)
  4. 4. FAQ:單一職責原則常見問題
    1. 4.1. Q1:為了遵循 SRP 導致程式碼重複(DRY 衝突)怎麼辦?
    2. 4.2. Q2:如何判斷一個元件的「職責」是否過多?
    3. 4.3. Q3:SRP 只能應用在類別 (Class) 上嗎?

什麼是單一職責原則 (SRP)?

單一職責原則 (Single Responsibility Principle, SRP) 是物件導向設計 SOLID 原則的首項,核心定義為:「一個類別或模組應該只有一個理由會使其改變」。這意味著每個軟體元件應僅專注於落實一項特定的商業邏輯或職責。在實務中,SRP 建議開發者應依據「使用者角色」或「變更動機」來切分模組,例如將「資料存取(Data)」與「介面呈現(UI)」分離。遵循 SRP 能有效減少模組間的強耦合,當某項業務需求變動時,受影響的程式範圍將被限縮在最小程度,從而顯著提升系統的可讀性、穩定性與長期維護效率。


物品我們都知道要按照其功能或用途分類和整理,來確保每個物品都處於適當的位置,整理後的物品更容易被找到,因為被分類得很清楚。

在架構上,該怎麼去做設計和切分,才能讓下面一位接手的人心情不至於太差呢?

回顧一下之前的例子,專案假設今天總共用五個階段五個角色,每個角色在不同階段需要填的欄位並不相同,可以想像一個班級有不同的幹部,而班級學期報告需要大家按照各自的職掌在不同階段逐漸把內容完善。

程式會被修改的原因,通常是使用者需求的改變。

假設一個類別只做一件事,但這件事剛好被兩個角色使用到,當其中一個角色提出新的需求,調整時就影響到另外一個,這就是把元件做成瑞士刀給不同角色去使用的壞處。

這時候該怎麼做比較好,接著就讓我們來看看 Single Responsibility Principle (SRP) 的概念吧。

Single Responsibility Principle (SRP)

Single Responsibility Principle (SRP) 是指每個類別或模組只負責一個功能或職責。

一個類別或模組應該只有一個理由會使其改變。

這個說明應該更往回推,因為程式會被修改的原因,通常是使用者需求的改變。

一個類別或模組應該只對唯一的一個角色負責。

單一職責原則,是希望我們依據不同角色的使用者來進行分類,只有當這個特定群體的需求改變,程式碼才會改變。讓我們看看前後端的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# frontend 依功能/角色拆分
src
└── features
├── typeOne
│ ├── TypeOne.js
│ ├── TypeOne.styles.scss
│ └── typeOneSlice.js
└── typeTwo
├── TypeTwo.js
└── typeTwoSlice.js

# backend 依實體拆分
src/
├── entities/
│ └── user/
│ ├── model.js
│ ├── controller.js
│ └── route.js
└── index.js

這樣的程式碼會更容易理解,因為:

  • 功能清晰且有界線,減少相互依賴和複雜性。
  • 變更只會影響到相關的功能不會影響其他部分,使得修改和維護更容易。

這樣的作法雖然會導致程式碼重複,但因為模組會針對不同群的使用者分開,複製貼上的成本比起每次更動都可能影響一大群使用者來說,其代價會是相對小的

這讓我想起了大學時期曾經到恆春夏日小學當課輔老師,當時社工老師希望我們和孩子傳遞的價值觀:

「一次做一件事,念。」

單一功能原則的目的是減少同個區塊中不相關的邏輯。當被開發者找到程式碼後,可以更快確認在哪一行產生目前的行為,更容易且安全的去修改程式碼。

以撥放器的 App 來說,撥放的頁面和設定的頁面是分開的,設定的時候並不用管撥放的時候做了什麼操作。對 React 開發來說,比較好的方式就是將顯示邏輯資料邏輯分開:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 將資料和顯示邏輯分開
// 1. 資料邏輯 (Custom Hook)
function useSubmit({ strButtonDisplay, url }) {
const [data, setData] = useState(null);
function onClick() {
fetch(url)
.then((response) => response.json())
.then((myJson) => setData(myJson));
}
return { strButtonDisplay, data, onClick };
}

// 2. 顯示邏輯 (Component)
function ThemeButton() {
const { strButtonDisplay, onClick } = useSubmit({
strButtonDisplay: "送出",
url: "http://example.com/movies.json",
});
return (
<button type="button" onClick={onClick}>
{strButtonDisplay}
</button>
);
}

康威定律 (Conway’s Law)

設計系統的架構受制於產生這些設計的組織的溝通結構。
—— M. Conway

系統設計本質上其實反映了企業的組織結構,軟體的架構與團隊的組織結構是互相影響的。

系統各個模組間的介面也反應了企業各個部門之間的訊息流動和合作方式,如果組織在資源和合作上沒有真的共用某些資源,那麼在程式設計上我們就不應該共用模組


FAQ:單一職責原則常見問題

Q1:為了遵循 SRP 導致程式碼重複(DRY 衝突)怎麼辦?

A:這在架構設計中稱為「策略性的重複」。如文中所述,如果兩段程式碼雖然現在長得一樣,但它們服務於不同的「角色」或「業務動機」,那麼強行合併反而會導致耦合。在這種情況下,遵循 SRP 比遵循 DRY 更重要,因為它確保了未來的變更互不干擾。

Q2:如何判斷一個元件的「職責」是否過多?

A:觀察它的 import 數量以及描述元件功能的「動詞」。如果您在介紹一個元件時用了「和(and)」這個連接詞(例如:這是一個負責抓取資料『和』渲染清單『和』處理權限的元件),那通常代表它違反了 SRP。

Q3:SRP 只能應用在類別 (Class) 上嗎?

A:並非如此。 SRP 的精神適用於所有層級:從單個函式、一個 React Hook、到一整個微服務 (Microservices)。只要是一個獨立的程式碼單元,都應該追求職責的專一性。



喜歡這篇文章,請幫忙拍拍手喔 🤣