---
title: "單一職責"
date: "2026-07"
category: "設計模式與架構"
tags: ["設計模式與架構"]
description: "我對單一職責（SRP）的啟發式理解是「能單測，沒有副作用，一個函式只做一件事」，當然我知道這不完全嚴謹。但作為快速判斷我覺得是好的。 能單測..."
source: "https://enriquemark.com/zh-hant/posts/%E5%8D%95%E4%B8%80%E8%81%8C%E8%B4%A3"
---

我對單一職責（SRP）的啟發式理解是「能單測，沒有副作用，一個函式只做一件事」，當然我知道這不完全嚴謹。但作為快速判斷我覺得是好的。

能單測。意味著這個函式不依賴外部狀態，沒有額外的規則，在純粹的情況下就可以測試，往往也暗示了此函式已經分離到了相當的程度。

沒有副作用。這點不總是絕對的，比如有些函式的合法職責就是帶副作用的，像打log或者查資料庫。核心點在於——除非這真的是這個函式的合法職責，否則它不應該影響到本該由其他函式做的事情，此時採用委派是最好的。即，委派給一個專職做此時的函式，而不是自己親自去進行。

只做一件事。這裡的一件事，本身不能從字面上理解，而是要考慮抽象的層次。比如在更高的抽象層次，這裡的「一件事」能涵蓋很多，不如說是一種「職責」。這裡的「職責」本來就是蠻抽象的東西，我對其的理解是「職能」。此函式在某一地方發揮的最小的單一功能。

更明確的SRP訊號其實是「改變」。比如當未來我進行修改的時候，是不是隻會因為一個「原因」去改變他？或者更確切的說，我該這個函式的時候，不會改到不相干的東西，比如改一個查資料庫的函式還得估計別把http請求改壞了——拿著就是職責混同。正是因為混了不同的東西，才會因為不同原因來這裡改這個函式。而反過來，這種函式也是最難單測試的，因為為了測試它，你得構建不同的環境。所以「按變化原因切分程式碼」是更強烈的SRP訊號，關鍵在於維護的邊界。

還有一種函式的職責是「編排」。對這種函式來說，它就像最終的執行者一樣，把好幾個不同職責的函式打包在一起執行。要注意，業務邏輯和編排是不一樣的。編排只是按照特定的順序執行，但業務邏輯則會承擔諸多狀態控制和判斷分支，對於後者來說，這完全就是職責不清晰。雖然同樣是委派，但如果摻雜了單純執行之外的其他因素，就可能變成一種包裝——本質上只是把業務邏輯扔了出去而已，實際上還是耦合的。

關鍵在於「狀態」。如果你的狀態本身是別人的，那麼這一部分的職責最好就是回到它發生的地方。如果一段程式碼主要都是在處理別人的狀態，就說明它該拆分和重構到其狀態所屬之處了——這意味著當前的程式碼是耦合的，耦合與他處的狀態。「依戀情結」正是這樣一種壞味道，全在處理別人家的狀態。如果對方只有一個，那這一整部分應該移到那邊；如果有多個，這裡就分兩種情況：1.多個一起變，而且是為了一個目的變，每次都固定搭配，而且缺一不可，那就說明這一部分是一個單獨的職責，應該給這個單獨給一個方法或者物件，2.多個分開變，那就得拆出來，作為單獨的方法存在；以及純粹的排序，比如固定的生命週期，而不是彼此搭配的狀態操作，那就是我們上面說的編排。

區分在於職責，如果某個職責本身已經有人——或者現在還沒人——那就應該由它去做。而不是由職責不同的另一個方法去做。重點是分辨三個：委託、依戀和編排。這裡有個地方很難搞清楚，那就是「自己的狀態」——到底是啥？這裡的核心就是，那些固定搭配，反覆操作的狀態，就是「自己」，這個「自己」在我們給他封裝之前可能是不存在的，但一旦包裝好，就可以統一把這謝謝狀態給他自己管理了，此時它再修改的就是自己的狀態，自己負責。當我不是單純在這裡進行一次的操作（這是委派）而是每次都在做一個流程（一組操作）的時候，這說明我可能越界了。
