[分享] 用一個簡單的數學公式來幫忙設計OOP類別
大家好,小弟一直覺得 OOP 很困難、設計類別很困難。
我一直想找一個比較量化分析的方式,在工作時輔助設計類別。
於是我設計了一個簡單的數學公式
跟大家分享一下這個公式,謝謝大家
網頁好讀版: https://devs.tw/post/340
--- 正文開始 ---
我一直覺得 OOP 很困難、設計類別很困難。
需要根據經驗、遵守一些原則才能寫出比較好的程式碼。
產出的程式碼也只有其他 senior 看得懂為什麼優質,junior 連分辨好壞都不容易。
也就是大家只能依靠質化分析,用文字描述為什麼好、為什麼壞。
我一直想找一個比較量化分析的方式,在工作時輔助設計類別。
於是我設計了一個簡單的數學公式,今天跟大家分享一下。
# 內聚係數 (Cohesion Ratio)
要替 OOP 類別打分數聽起來有點荒唐,但我設計了一個簡單的量化分析方法。
我把這個量化分析算出來的值稱為「內聚係數」,英文叫做 Cohesion Ratio(下文以
CR 代稱)。
CR 的計算方式非常簡單,就是一個類別裡面:
```
(每個方法用到的屬性數量加總)/(屬性的數量 * 方法的數量)
```
舉例來說,如果今天有一個類別長這樣:
```
class Apple
{
private $propertyX;
private $propertyY;
private $propertyZ;
function methodA()
{
// blah
}
function methodB()
{
// blah
}
function methodC()
{
// blah
}
function methodD()
{
// blah
}
}
```
那麼這個類別屬性的數量是3,方法的數量是4。所以 CR 的分母就是 `3 * 4 = 12`
如果 methodA 用到了 propertyX,methodB 用到了 propertyX 跟 propertyY,methodC用到所有屬性,methodD 沒有使用到任何屬性,那麼 CR 的分子就是 `1 + 2 + 3 + 0 =6`
便可以算出來 CR 就是 `6 / 12 = 0.5`
如果每個方法都用到了所有屬性,那麼 CR 就會是 `(3 + 3 + 3 + 3) / 12 = 1`
如果每個方法都沒有用到任何屬性,那麼 CR 就會是 `(0 + 0 + 0 + 0) / 12 = 0`
你可以用這個公式幫任何一個類別打分數,分數越高,代表它是一個越好的類別!
CR 最高就是 1,最低就是 0
實務上大概不可能寫出 CR = 1 的類別,但要避免寫出 CR 接近 0 的類別。總之 CR 越高越好。
接著讓我使用這個「內聚係數」來評論、量化分析一些開發情境吧!
# God Object 上帝物件
這是一個知名的 anti-pattern,也就是一個類別負責了過多任務,變成極為肥大的類別。
這種類別的屬性很多、方法也很多,而且大部份的方法都只用到少數幾個屬性。
根據內聚係數的公式,你會發現這種類別的 CR 一定非常低,非常接近0
該如何改善呢?根據內聚係數的公式,你會發現:把類別拆小就可以了。
把相關的 property 跟 method 放在一起,很輕易就能拆出多個 CR 更高的類別。
當你這麼做的同時,通常很神奇地,類別命名會自然浮現。
# Long Method 過長方法
有時候你已盡力用上所有最佳原則、遵循 SOLID 指引,但還是有些 method 很長。
理性上你覺得這太長了,需要改寫,但感性上又覺得它們的確是一整串連續整體的邏輯。
該怎麼辦呢?
像這樣的 code,裡面出現的變數一定很多,每段任務分別用到部份的變數。
你可以分兩個步驟改寫:
步驟一、先把每段任務拆成各自的多個小 method,把用到的變數傳進去。
你會發現同樣的好幾組變數,反覆傳到好幾個 method,似乎讓 code 變得更糟糕了!
步驟二、把這些反覆在傳的變數,變成類別的 property。
你會發現很多 method 不再需要依靠參數,直接跟 property 互動即可。
並且因為這些 property 反覆在多個 method 被使用,這時 CR 也跟著提高了!
很簡單吧!改寫之後更可讀、更好維護,輕鬆改善 long method 的問題!
話說回來,那把出現的所有變數都變成 property 如何?
你會發現有些變數只在一兩個 method 被使用,提昇為 property 反而會導致 CR 下降!
內聚係數的量化分析明確告訴你:這樣不太好!
# 基於 Active Record Pattern 的 model
雖然一大堆 web 框架預設使用 Active Record Pattern 作為 domain modeling 的方式,但你會發現這方式問題還不小。
相關的 class 屬性幾乎被綁定成跟資料庫 table 的欄位一樣。
本文提到的利用 CR 協助類別設計的技巧就很難幫上忙。
這也是 Active Record Pattern 評價兩極的主要原因之一。
所以 google keyword: active record pattern anti oop 才會有一堆相關文章。
# Service Object
那些基於 Active Record Pattern 進行 domain modeling 的社群因此會提倡 service
objects 的使用。
這些 service object 類別通常都沒有 property,只有一些把 entity 作為參數、純粹描述商業邏輯的 method。
雖然是把 code 拆分到多個類別了,但你會發現,CR 的分子分母都是 0,這種類別有內聚性可言嗎?
好像不該寫出這些類別?但我覺得這些 service object 很好用阿,怎麼辦?
其實 OOP 講究把相關的資料跟行為放在一起,像這種「單純描述行為」的類別,從 OOP的角度來看的確很差勁。
這些類別給人 Procedural Programming 的感覺似乎更多一些。
所以 google keyword: service object anti oop 才會有一堆相關文章。
其實也沒關係,反正現代程式設計本來就是各種 Programming Paradigm 都摻雜了一些。
如果說一定要從 OOP 的角度去反省,那你不妨這樣去想:
> 這些 business logic 我急著要上線使用,暫時不知道屬於 domain modeling 的哪裡,所以沒有內聚性可言,暫時都先做成 service object 擺著。未來當我發現某些參數重複出現在一些 service object 時,我可以再把他們一起重寫成一個獨立的類別,到時這些類別就會有不錯的 CR 值了!
通常到了那個時候,公司更確定自己的經營方向、工程師更確定 domain modeling 該怎麼設計,所以 CR 自然會通通提昇!到時你做的類別命名也不再是 XXXService 、
XXXManager 這種含糊結尾,你會很容易找到一個很滿意的名稱!
目前這種 CR = 0/0 的類別,就當作是一種權宜之計吧!
話說回來,既然這些 CR = 0/0 的類別已經不太 OOP 了,你大可發揮創意,把這些商業邏輯放在你爽的地方就好。
- 可以利用程式語言的 trait/mixin 特性來寫這些純行為的邏輯。
- 直接開一個檔案裡面只有一堆 function 根本不寫 class 也可以。
- 找個類別寫一大堆「靜態方法」也可以。
反正本來就沒有內聚性可言,只是找個地方寫的權宜之計,還有很多 service object 之外的作法。
可以當成是進行到一半的 domain modeling,未來有機會再完成領域建模吧!
(其實零除以零的結果並不是零,所以 CR 在此處也沒有否定 service object。它只是說:我無言了)
# 結論
OOP 程式設計易學難精,本文提到的內聚係數與量化分析只是一種輔助工具。
經典的各種程式設計原則、SOLID 原則、設計模式,都還是需要花時間乖乖學習。
實務上也沒有什麼真的 CR = 0.6 以上才算及格之類的數字,CR 數字大小終究是另一個依賴經驗的主觀問題。
我個人通常在設計類別實在很卡關、實在很趕時間的時候,才會拿內聚係數來思考一下。
因為它很無腦簡單,就是屬性跟方法拆一拆、搬一搬而已,相關的命名會自然浮現。
有機會你也可以試試看,用內聚係數幫你的類別們做個簡單的量化分析。
順利提高係數的時候,真的會有一種感覺:我的類別內聚性還不錯哦!
--- 正文結束 ---
最後順帶一提 https://devs.tw 是我開發用來給工程師 寫筆記、分享技術文章的論壇
支援 Markdown 格式,歡迎一起加入寫作與分享的行列,謝謝!
--
總覺得這有點把簡單的東西複雜化了不過有心給推
昨天在臉書python社群有看到 不錯
覺得有趣
很酷
覺得會走向跟KPI一樣的結局
這個東西跟架構還差太遠了吧......
濃濃民科風 想當碼農務必詳讀
很棒啊可以先去投研討會論文
覺得OOP難不去搞懂而搞這套只會讓架構更爛而已
Bansiya02-QualityModel 入門paper 給有興趣的人
可以google參考一下,CBO,coupling between object,概念
很像
你想做的是Cyclomatic complexity?我覺得可以當作參考
Cyclomatic complexity針對一個函數內,原文要做是class間
公司在用understand這軟體,可參考一下他可提供的metrics
但我同意回文那篇,算數字像是硬做KPI
感覺非常奇怪的算法 method越多分數越低 全部寫在main裡
面 會拿到滿分
很有趣, 要是有這工具我肯定用
77
[閒聊] 無視任何防禦的傷害類別有哪些稱呼?單純很無聊的閒聊文 玩LoL都知道有一種無視防禦與魔防的傷害類別 系統上是稱為『真實傷害』,簡單說就是數字顯示多少就打多少 然後戰女神系列也有無視防禦魔防的傷害類別,叫做『貫通攻擊』 千年戰爭也有類似的,雖然系統沒有給特定名稱35
[心得] C#基礎名詞解釋會發這篇文主要是面試被洗臉 我都會做啊 但我就不會解釋啊 雖然是寫給自己看的 但就分享出來吧13
Re: [請益] 多型用在哪本魯 OO 不太好 但你這例子多型嗎 這就只是子類別繼承父類別的屬性吧 多型比較像這樣吧 class DataLoader {5
Re: [請益] 比物件導向更先進的程式設計思想?JavaScript 是一個基於原型(Prototype-based)的程式語言 在本質上很難將它歸類為程序導向語言,或是物件導向語言 類別: JavaScript 中沒有類別(Class)的概念,但是有物件(object)的概念 而這個物件概念的物件,則是以GUI的 Widget為主10
Re: [請益] Spring boot的依賴注入降低耦合的例子很久沒寫Java了 就個人觀念提供簡單思考線索 基本上根據你的內容覺得你對解耦合還沒有很理解 講直白一點 當你import類別就是耦合了9
Re: [新聞] 動視暴雪「多元化工具」為遊戲角色種族原文吃掉 如果講究重視平等, 就不該用計分的方式, 在統計的規則裡面, 有度量類別5
Re: [請益] 如何選擇適合的設計模式我對看到的其中幾句話有一些其他想法和想補充的地方 和大家分享 ※ 引述《strlen (strlen)》之銘言: : ..... : ..... 非到必要時刻不要使用2
Re: [請益] 南部新手轉換跑道家教多年,非本科初學者建議以下幾點要弄清楚 1. 語言只是解決問題的工具,要學習的針對要解決的事情和問題,提出解決方案和優劣 比較。 2. 在不同領域的公司裡面擔任什麼角色 傳產 科技業 金融業 etc- 商標 = 讓消費者可以用來『識別』產品或服務來源的文字或圖樣 註冊商標的審查也是依據上述的原則來進行,簡單來說就是, 避免消費者看到商標B會『混淆誤認』成是商標A的商品或服務。 若有可能會混淆誤認,先申請的商標就會阻礙後申請的商標,與有沒有商業登記無關。 另外,商標有分45個類別,各類別代表不同的商品與服務,有些類別彼此會影響