閒談軟體設計:Single Responsibility

閱讀時間約 10 分鐘
Single Responsibility Principle (LearnStuff.io)

Single Responsibility Principle (LearnStuff.io)

話說在前頭,這篇文章所提出的質疑不一定適用所有的情境,請依據讀者自己的情境酌量採用。

理所當然?

這次聊聊一個幾乎每個軟體工程師都會掛在嘴邊,但實際上寫程式是否還記得的原則:Single Responsibility。從 Wikipedia 第一行的說明

every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class, module or function.

很多人會覺得,這原則很簡單啊,但實際上卻有點模糊,什麼功能應該放在哪個類別 (模組) 或是不該放在那個類別 (模組) 的邊界卻很難拿捏。這邊說個就學時的故事,當年研究所課程助教幫我的作業做 design review,那時我把專案從檔案讀取和寫入檔案的 method 放在專案的類別中

當時覺得這樣的設計很合理,畢竟我不想替私有的成員加 setters,但助教建議我將讀取和寫入的邏輯移出,那時和助教有過熱烈的討論,最後被說服的理由便是 single responsibility,但助教的說法是 single responsibility 的另一種解讀:

A class should have only one reason to change.

如果要改變檔案的結構,或是改成讀寫 JSON 檔案,又或是要支援多種檔案格式,那要修改哪個類別?但這是Project這個類別的責任嗎?想想確實,這類別的責任應該是管理元件與之間的連結,其他都不是這個類別的責任,而且為了讀取 XML,Project類別和 JDOM 有了相依性。

生產力與單一責任原則的兩難

看到這,我想應該有人會認同助教的說法,於是我們來看下個例子,這應該很常見在 Java 處理 JSON 的轉換程式中看到:

而且我相信很多人這樣寫的時候,並不會想到 single responsibility 的原則,因為這樣寫很方便,提高生產力,同樣的類型,再看一個例子:

這寫法幾乎快變成 Java 後端的標準起手式了,畢竟回頭寫 XML mapping 的人應該越來越少了,當初第一次學 JPA 時覺得這個超方便的,而且程式和 mapping 放在同一個檔案,很容易發現 mapping 不一致的問題,但這樣寫真的符合 single responsibility 嗎?更何況不一致是雙向的,改 DB 欄位若沒想到回頭改 mapping 也是會不一致。

這情況不是 Java 獨有,自從 Swift 導入Codable介面後,實作Codable也幾乎變成標準寫法了 (若把 fullName 宣告成 computed property,可以不用寫init(from decoder)encode(to encoder),這是刻意寫的例子),不這樣寫還會被嫌,但若反問為什麼這樣寫比較好,到目前為止,我並沒有聽到任何決定性的優點讓我非這麼寫不可,我也沒那麼喜歡這樣寫。

友善的距離

至少,我會拆成兩個檔案:Customer.swiftCustomer+JSON.swift,可能有人會疑惑,程式碼明明沒有差 (其實有差,visibility 不一樣),為什麼要拆成兩個檔案,如果專案只有一個模組那確實沒太大差別,但如果有拆模組,例如CoreWebService模組,那Customer.swift會放在Core模組,另一個檔案Customer+JSON.swift會放在WebService模組,由於 extension 的 visibility 是 internal,所以在Core模組是看不到 JSON 相關的內容,任何和 JSON 相關的修改也不會動到Customer類別和Core模組,而是限縮在另一個模組裡,某種程度上還算是符合 single responsibility 原則。

因為官方提供了Codable,所以就一定要這樣寫?我個人是覺得沒必要,事實上我個人並不介意寫像這樣的程式碼跟相對應的測試:

回到單一責任原則

若真的要嚴格說,domain 類別的責任主要是封裝 domain knowledge,不在 domain 中的東西,都可以用 creator 或 pure fabrication 類別處理,而不用放在 domain 類別中。但有些東西就很難說,例如 create time 和 update time,通常在分析 problem domain 時,不會列出這兩個時間戳記,但幾乎在所有的商業應用都可以看到他們在 domain 類別中,但若仔細想,為什麼會需要這兩個時間戳記甚至是誰改的?主要是為了稽核這個目的,如果是這樣,這是各別 entity 的責任嗎?難道沒有其他的解法?

事實上是有的,雖然在 GSS 待不長,卻也跟當初的架構師學了一些技巧,那時用 Spring framework 的 AOP 寫一個攔截器,搭配自訂 annotation,攔截所有需要稽核的 Restful API end points,只要呼叫 API,攔截器便會被呼叫,此時可取得是誰呼叫 API 以及請求的內容,並在 API 結束後,攔截器再次被呼叫,取得回傳的內容,如此,就可以將所有 API 的呼叫記錄下來,甚至可以知道誰在幾點幾分下載了什麼檔案,或修改了什麼資料,如此一來在每個 entity 中加入 create time 及 update time 就不是必要的,雖然這樣做是最方便的 (但只能記住最後一次修改的時間)。

剛剛的 create time 及 update time,還能算作是 application logic,勉強能稱上是 domain 的一種,但因為使用的 framework 導入的東西,大多數都是實作層級的。除了剛剛的 annotation 外,因為導入 ORM 內建的樂觀鎖 (Optimistic Offline Lock),在物件中加入 version,也是一個當初我剛接觸時,不喜歡卻也離不開的東西,有了 version 資訊,ORM 工具就可以協助判斷這次存入的變動是根據哪個版本所做的,若 DB 其實已經有更新的版本時,ORM 會以例外的方式告知這個變動可能是過時的,這非常有用,畢竟後端要思考到多個 request 編輯同一份資料的情況,但總是覺得好像違反 single responsibility,若以 DDD 的角度,Aggregate 本身負責交易邊界,似乎還說得過去。

近期,不少程式語言 (Objective C、C#、Swift 和 Kotlin) 有 extension 的機制,在不修改原有類別 (像是其他函式庫的類別,無法修改其原始檔)的情況下擴充類別的功能,這機制十分強大,我也很常用,但這幾年,我開始覺得這機制也助長違反 single responsibility 的現象,因為方便,開始為某些類別加入 extension methods,而且越加越多。

我只能說,拜託在加入前請認真思考一下,這真的是這個類別的責任嗎?像是 derived information 用 extension 就很合適,例如先前例子中的 full name、或是用生日推算出年紀的函式,如果一開始沒有在類別中,用 extension 擴充就很不錯。但特定情境下才適用的 derived information,例如 UI 才適用的資訊,比起 extension 放在 view model 也許更合適。

另外,用 extension method 的可讀性真的比用 static utility method 高嗎?某些情況下真的比較高,像是在設計 DSL 時,可以寫出較接近句子的程式,但不是每個情況都是如此。

實際上 Kotlin extension method 轉成的 byte code 就是一個第一個參數是目標類別的 static method,然後提供一個語法糖衣。

但不同類別之間的轉換,用 extension 加到任何一邊都很怪,因為轉換這件事到底應該是誰的責任?雖然說,extension 可以把程式分到不同檔案,加上可以透過一些語言機制,讓某些 extension 的函式不被看見,但還是把功能加到原先的類別中,更何況不是所有的語言都有這類機制。

這也是我在讀過《Clean Architecture》後,重新思考的問題,不同 layer 之間資料的轉換,該是誰的責任?理想狀況是加入一個第三者:

但如果不加入第三者,我目前比較喜歡把責任放在接近 I/O 的類別身上:

總結

小結一下,若真的要符合 single responsibility,通常會得到很多很小的類別或是函式,各別完成一個小的功能,然後在某個地方被聚合起來完成一個使用案例 (use case),而不是一個很大的類別,包山包海,然後最後變成一個狀態超複雜,超級難測試的類別。也許是因為 dependency 難管理,所以很多工程師喜歡大類別,而不喜歡很多的小類別。

所以下次,當想把某個函式或屬性加到某個類別前,也許可以先想想 single responsibility,有沒有更合適的地方或是新增一個第三者類別。至於,那些為了提高生產力的寫法,到底在整個軟體開發週期中減少多少時間,我就不去討論了 (近一年都在寫 Node.js,沒有那些 annotations,也不會覺得有失去很多生產力)。


後記

現在想想,會對 JSON annotation 或是 serialization 有些堅持,真的跟當初與當時的助教 Teddy 熱烈討論後,留下深刻的印象有關。

這裡要小小抱怨一下《Kotline in Action》的中文翻譯,為什麼要把 extension 翻譯成繼承?我通常一本書不會一次看到完,有時會隔個幾週再回去看,然後看到繼承這個術語時,都還要根據上下文來判斷是類別之間的繼承還是 extension,我想超過 90% 以上學過 OOP 語言的人,看到繼承兩個字絕對不會聯想到 extension,翻譯成繼承真是爛透了。


延伸閱讀


52會員
102內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
留言0
查看全部
發表第一個留言支持創作者!
Spirit的沙龍 的其他內容
閒談軟體設計:API Naming Style
閱讀時間約 9 分鐘
閒談軟體設計:內部函式庫
閱讀時間約 7 分鐘
閒談軟體設計:Switch 壞味道
閱讀時間約 7 分鐘
閒談軟體設計:架構師難尋?
閱讀時間約 16 分鐘
你可能也想看
迎新活動「方格新手村」:新格友註冊加入方格子,知名日料吃到飽餐券送給你! 👉 還不是 vocus 的會員嗎?點此註冊,參與新手村活動 👈 近期站上也出現了不少新格友,為了歡迎各位的加入,「方格新手村」隨之登場! 即日起,只要是新註冊帳號於活動期間內發佈 3 則文章,就有機會抽獎獲得知名日料吃到飽餐券。原格友也可以一起同樂,我們準備了小任
Thumbnail
2024-06-21
101
川普當選,對台股是利多還是利空?川普在槍擊事件中所表現出來的英勇形象,讓他贏得美國總統大選幾乎已成定局。沒想到他隨口的一句話「台灣搶走美國的晶片生意,所以要付保護費」,就讓台積電在短短三天跌掉超過100 元,台股也跌掉1100點以上。台積電、台股會就此一路下跌嗎?未來該如何因應?  
Thumbnail
2024-07-20
67
金融科技與生活美學的融合 CUBE App實踐自我理想最近開啟了研究工作,也開始斜槓著手團購、行銷、洽談業務,慢慢想打造一條屬於自己的道路,而工作忙碌之虞,總忘記自己刷卡消費明細、信用卡繳費,還會忽略了最應該的投資理財,也常常在忙碌奔波中忘記信用卡優惠,國泰世華CUBE App給足了這些功能和服務。 日常生活與數位、科技形影不離,同時也拉近彼此距
Thumbnail
2024-07-11
22
從我的孕期怪事談起,閒聊人生的軟弱時刻。最近身旁有幾位正在懷孕、或剛生產完的朋友,讓我想起自己在懷孕期間印象最深刻的三件「怪事」,其中又以第三件事最誇張。
Thumbnail
2022-11-06
0
閒談-社區大樓觀景第一排公設圖書室,我們都要試著跟鄰居友好相處不知道大家在買房之前是不是都會參考親朋好友的意見,或是上網看一些買房注意事項,有時候考慮了這塊就忘了那塊,考慮的那塊又忘了這塊.......
Thumbnail
2022-08-16
8
閒談塔西佗陷阱(Tacitus Trap)塔西佗陷阱(Tacitus Trap),一個得名於古羅馬歷史學家塔西佗的政治學理論,意指倘若公權力失去其公信力,無論如何發言或是處事,社會均將給予其負面評價。 當然信著恆信、不信者恆不信,這就是真實的人生。
Thumbnail
2022-01-01
7
【閒談】關於《花漾女子》的想法雜燴 關於片名   台灣片名《花漾女子》,原文片名《Promising Young Woman》,台灣譯名將時間定格在悲劇發生前,而原文片名則進一步帶我們看見另一個可能性結果
Thumbnail
2021-03-29
1
閒談金融業法令遵循-(一)工作待遇與定位前幾年因為身體的關係,當了幾年的律師逃兵,當時開了之前的事務所以後,一時間也沒有特別想要做甚麼事情,所以就邊讀一點書、早晚運動一下,剛好聽到當年同梯朋友進去金融業工作,因此也抱著嘗(ㄊㄠˊ)試(ㄅㄧˋ)的心態,找了份銀行法令遵循的工作
Thumbnail
2021-01-08
6
閒談 那天我問隊友,怎樣才算是一部小說呢?按字數計算嗎? 他說:「故事內起承轉合都有,就算」 所以我大膽地按著他的標準,將自己寫過的故事,粗略整理出一個明細。
Thumbnail
2020-11-18
2
閒談纏論與李彪 纏中說禪,本名李彪,專欄筆名木子,其人是中國股市比較早期的操盤手,所以他比較熟悉a股的市場情況。他以“纏中說禪”為筆名,從2002年開始寫博客,直到2008年癌症病重停更,期間寫下了不少文章。而博客文章中最為著名的就是他的“教你炒股票”系列文章,他在這個系列裡講到的炒股理論和方法被粉絲稱為“纏論
2020-07-30
2
閒談末代皇帝電影:婚姻篇婚姻是人生大事,對溥儀尤其如此,因為如果皇帝大婚,就代表溥儀可以脫離眾多便宜老媽的束縛而得以親政。 但詭異的是,這個可以讓他脫離便宜老媽掌控的婚姻,卻還是要由便宜老媽進行主導並且居中角力......
Thumbnail
2020-05-29
3
閒談末代皇帝電影:童年篇上次我提到:溥儀就是個死小孩。其實這不能全怪溥儀,而要怪詭異的宮廷教育及生活制度......
Thumbnail
2020-05-29
4
閒談末代皇帝電影:登基篇近期電影「末代皇帝」重新修復上映。 為了推坑這部經典之作,本人決定以溥儀本身的自傳《我的前半生》為主要基底,和大家談一些電影中礙於篇幅或是藝術改編,而不容易察覺或是沒有呈現的真實歷史。
Thumbnail
2020-05-29
4
迎新活動「方格新手村」:新格友註冊加入方格子,知名日料吃到飽餐券送給你! 👉 還不是 vocus 的會員嗎?點此註冊,參與新手村活動 👈 近期站上也出現了不少新格友,為了歡迎各位的加入,「方格新手村」隨之登場! 即日起,只要是新註冊帳號於活動期間內發佈 3 則文章,就有機會抽獎獲得知名日料吃到飽餐券。原格友也可以一起同樂,我們準備了小任
Thumbnail
2024-06-21
101
川普當選,對台股是利多還是利空?川普在槍擊事件中所表現出來的英勇形象,讓他贏得美國總統大選幾乎已成定局。沒想到他隨口的一句話「台灣搶走美國的晶片生意,所以要付保護費」,就讓台積電在短短三天跌掉超過100 元,台股也跌掉1100點以上。台積電、台股會就此一路下跌嗎?未來該如何因應?  
Thumbnail
2024-07-20
67
金融科技與生活美學的融合 CUBE App實踐自我理想最近開啟了研究工作,也開始斜槓著手團購、行銷、洽談業務,慢慢想打造一條屬於自己的道路,而工作忙碌之虞,總忘記自己刷卡消費明細、信用卡繳費,還會忽略了最應該的投資理財,也常常在忙碌奔波中忘記信用卡優惠,國泰世華CUBE App給足了這些功能和服務。 日常生活與數位、科技形影不離,同時也拉近彼此距
Thumbnail
2024-07-11
22
從我的孕期怪事談起,閒聊人生的軟弱時刻。最近身旁有幾位正在懷孕、或剛生產完的朋友,讓我想起自己在懷孕期間印象最深刻的三件「怪事」,其中又以第三件事最誇張。
Thumbnail
2022-11-06
0
閒談-社區大樓觀景第一排公設圖書室,我們都要試著跟鄰居友好相處不知道大家在買房之前是不是都會參考親朋好友的意見,或是上網看一些買房注意事項,有時候考慮了這塊就忘了那塊,考慮的那塊又忘了這塊.......
Thumbnail
2022-08-16
8
閒談塔西佗陷阱(Tacitus Trap)塔西佗陷阱(Tacitus Trap),一個得名於古羅馬歷史學家塔西佗的政治學理論,意指倘若公權力失去其公信力,無論如何發言或是處事,社會均將給予其負面評價。 當然信著恆信、不信者恆不信,這就是真實的人生。
Thumbnail
2022-01-01
7
【閒談】關於《花漾女子》的想法雜燴 關於片名   台灣片名《花漾女子》,原文片名《Promising Young Woman》,台灣譯名將時間定格在悲劇發生前,而原文片名則進一步帶我們看見另一個可能性結果
Thumbnail
2021-03-29
1
閒談金融業法令遵循-(一)工作待遇與定位前幾年因為身體的關係,當了幾年的律師逃兵,當時開了之前的事務所以後,一時間也沒有特別想要做甚麼事情,所以就邊讀一點書、早晚運動一下,剛好聽到當年同梯朋友進去金融業工作,因此也抱著嘗(ㄊㄠˊ)試(ㄅㄧˋ)的心態,找了份銀行法令遵循的工作
Thumbnail
2021-01-08
6
閒談 那天我問隊友,怎樣才算是一部小說呢?按字數計算嗎? 他說:「故事內起承轉合都有,就算」 所以我大膽地按著他的標準,將自己寫過的故事,粗略整理出一個明細。
Thumbnail
2020-11-18
2
閒談纏論與李彪 纏中說禪,本名李彪,專欄筆名木子,其人是中國股市比較早期的操盤手,所以他比較熟悉a股的市場情況。他以“纏中說禪”為筆名,從2002年開始寫博客,直到2008年癌症病重停更,期間寫下了不少文章。而博客文章中最為著名的就是他的“教你炒股票”系列文章,他在這個系列裡講到的炒股理論和方法被粉絲稱為“纏論
2020-07-30
2
閒談末代皇帝電影:婚姻篇婚姻是人生大事,對溥儀尤其如此,因為如果皇帝大婚,就代表溥儀可以脫離眾多便宜老媽的束縛而得以親政。 但詭異的是,這個可以讓他脫離便宜老媽掌控的婚姻,卻還是要由便宜老媽進行主導並且居中角力......
Thumbnail
2020-05-29
3
閒談末代皇帝電影:童年篇上次我提到:溥儀就是個死小孩。其實這不能全怪溥儀,而要怪詭異的宮廷教育及生活制度......
Thumbnail
2020-05-29
4
閒談末代皇帝電影:登基篇近期電影「末代皇帝」重新修復上映。 為了推坑這部經典之作,本人決定以溥儀本身的自傳《我的前半生》為主要基底,和大家談一些電影中礙於篇幅或是藝術改編,而不容易察覺或是沒有呈現的真實歷史。
Thumbnail
2020-05-29
4