iT邦幫忙

2024 iThome 鐵人賽

DAY 2
0

同步至 medium

單一職責原則 ( SRP Single Responsibility Principle )

單一職責原則 ( SRP Single Responsibility Principle ) 之前在談論這個主題時,有提到《 Clean Architecture 》在這的定義如下 :

一個元件只有一個角色引起變化

然後當時我自已的想法為 :

角色代表這個類別的使用者(即實際執行方法的地方),而變化則是指因為這個角色的某個需求,導致需要修改程式碼。

後來,我參考了《Clean Architecture 實作篇(第二版)》中的定義:

每個元件都應該僅有一種且唯一的被修改的理由

他有給一個範書中提供了一個範例,對我來說是很容易理解的::

A → B + C + D + E ( A 依賴 B + C + D + E,就是 A 有用這些元件的啥 ) 

B → E 

C → E

上面的範例代表這 :

  • A 依賴很多元件
  • E 被很多人用

因此,A 的修改理由有 B + C + D + E,所以它有 4 種修改的理由,而 E 的修改理由僅有它自己需要加新功能,只有 1 種。因此,我認為 SRP 的被修改的理由可以總結如下圖:

https://ithelp.ithome.com.tw/upload/images/20240916/20089358OGKQz8dn2H.png

然後以程式碼來看如下 :

  • 呼叫 getOrdersByUserId 的地方,就是所謂的使用者情境
  • orderRepository 就是這個方法所依賴的元件
function getOrdersByUserId (user: User){
   if(user.role !== 'USER'){
     throw new Error('the user is not USER role')
   }
   const orders = await orderRepository.getOrders({ userId: user._id})
   return orders;
}

~ 小備註 ~

DIP Dependency inversion principle (DIP) 是後面會提到的元則,它的主要重點在於 :

高層次的模組不應該依賴於低層次的模組,兩者都應該依賴於抽象介面。抽象不要依賴細節,細節要依賴與抽象

這個原則的實際應該用有一部份就是用來處理依賴元件變動的部份,所以 SRP 與 DIP 實際上是很有關係的。


使用上的經驗

然後這幾年後又有不少新的感受,想來談談。

首先在《 Clean Architecture 》所提到的角色這個概念我後來發現是有一點模糊的,比較準確的說是每個人的定義會不同,例如有人覺得是用這個 class 的地方 ? 或是實際上用這個 class 的實際角色 ( 例如 api 權限設計 ) 或者是 class 本身所代表的角色。。

然後接下來 《 Clean Architecture 實作篇 》提到的,我自已覺得是很好理解,但如果要做到這裡說的,那是不是要像 E 一樣,或是說每個元件只能依賴一個元件而以 ? 好像那裡怪怪的對吧 ?

後來我自已在總結以下,然後變成我自已在開發時,常常會想思考的 3 件事 :

  • 一個元件會不會在被其它地方 ( context ) 使用時,需要進行變更呢 ?
  • 元件所依賴的東西會不會太多呢 ?
  • 如果這個 class 有相同依賴的一些方法,可不可以拉成另一個 class 呢 ? 也就是把依賴是相同的放在一起。

例如我有一個方法叫 getOrdersByUserId,那它是否符合 SRP 呢 ?

function getOrdersByUserId (user: User){
   if(user.role !== 'USER'){
     throw new Error('the user is not USER role')
   }
   const orders = await orderRepository.getOrders({ userId: user._id})
   return orders;
}

先想一下他現在的使用情境。他現在是用來處理『 讓用戶前台看到他的所有訂單 』的需求,然後下面是我腦袋開發時會想的事情 :

  1. ( 情境 ) 如果是後台要來看使用者的訂單呢 ? => 後台使用不需要 role 檢查。
  2. ( 角色 ) 如果換成 admin 要來取得到某個使用者的所有訂單,而且會看到更多欄位,例如 invoice,我這裡面的程式碼是不是要改呢 ? => 對要改,因為我這裡限制住了 role 為 USER,而且回傳也要多加 invoice。
  3. ( 情境 )如果換成其它元件要來取得到 orders 資訊也可以用他嗎 ? ( module 的使用情況不明 ) => 對 ~ 要改 ~
  4. ( 依賴 )他有沒有依賴很多元件呢 ? => 沒有,所以不用擔心依賴變了,我這要修改。

所以嚴格來說這個方法是沒有符合 SRP 的,比較好的修改方向我自已覺得是以下兩個 :

  1. getOrdersByUserId 改成純 search 用的方法例如叫 searchOrders,也就是說只要帶入 search 參數就可以取得到對應的 user 資料,然後外面如何包,就讓使用的地方決定。
  2. 或是直接將這個方法改名,讓它的方法使用範例進行限制,例如 getOrdersByGuestUserForXXX

然後基本上走到後面,應該就慢慢的變成 1 + 2 一起使用了。

然後這裡也想順到提一下 :

Naming 影響到範圍,也就是說你取名越抽象,例如 getUsers 這也代表假設了任何人都可以叫這隻,但裡面實作有沒有符合,就又是另一個問題了。


小結

  • 每一次在開發一個元件時,記得要想像未來使用它的地方 (情境、角色、其它),記得不要總是想做一個可以支援所有情境與角色的東西。
  • 不要有太多的依賴,因為被修改的理由會增加。
  • 不要做的太抽象,因為就代表情境與角色會增加。
  • 如果真的太多依賴,就考慮將相同依賴的放在一起。
  • 如果真的太多情境要使用,那就要將相同情境的放在一起。
  • 元件 Naming 代表這個範例適用的範圍 所以當你取名是要先想好,這個方法或類別的使用範圍到那
  • 元件 Naming 叫啥就做啥,不要 Naming 和實作是不一致的,例如 getArticles 但實際上限制在只有某個角色可以看,又或是偷偷多了一個 article 計數器。

不過認真的說,我也很難說不要太多的依賴是多少依賴,不要太抽象是多抽象才算,很多時後只能靠感覺…,但通常你發現一個 class 好大,通常十之八九就是有問題了,所以我通常在 code review 時,只會抓個大概,大部份都是回來重構時,再來看看。


上一篇
Day-01: 之開篇與軟體工程維護性
下一篇
Day-03 : 設計原則之 SOLID - ISP、DIP
系列文
一個好的系統之好維護基本篇 ( 馬克版 )30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言