程式碼壞味道與架構權衡 三分鐘斷捨離:從 Shotgun Surgery 到 God Object,掌握內聚與耦合的平衡點

me
林彥成
2023-09-22 | 4 min.
文章目錄
  1. 1. 什麼是程式碼的壞味道 (Code Smells)?
  2. 2. 程式碼的壞味道
    1. 2.1. 1. 以管理面來說
    2. 2.2. 2. 以組織和分類來說
  3. 3. FAQ:程式碼異味常見問題
    1. 3.1. Q1:重複的程式碼 (DRY 原則) 一定要消除嗎?
    2. 3.2. Q2:如何分辨一個物件是「高內聚」還是「垃圾桶」?
    3. 3.3. Q3:遇到 God Object (上帝物件) 該怎麼動手重構?

什麼是程式碼的壞味道 (Code Smells)?

程式碼的壞味道 (Code Smells) 指的是程式碼中可能潛藏問題的跡象,它不一定是功能上的錯誤(Bug),但卻暗示了系統設計上的缺陷,可能導致未來維護困難。常見的味道包括 Shotgun Surgery (散彈槍式修改):改一處動全身,需在多處同步調整;以及其對立面 Divergent Change (發散式修改):一個類別承載過多責任,導致任何需求變動都要改它。理解壞味道的本質在於掌握 Cohesion (內聚)Coupling (耦合) 的權衡。好與壞往往是主觀且視情境而定的,「適度」的重複有時比「過度」的抽象更容易維護。


延續上一天慾望的源頭管理,今天想談價值與選擇,從小到大我們都會被給很多標準答案:

  • 人生就要斷捨離?
  • 有重複的程式碼是程式碼的壞味道?
  • 不愛讀書就是壞小孩

同樣的一件事情你可以有很多不同種的解釋,甚至很多上一輩在教育上會有自己堅持的看法,大絕招大概就是「十年後你就知道了」。

人們有不同的觀點和價值觀,因此對於事物的評價是主觀的是相對於其他事物而言的,從這個角度來看「不好」可能只是一個個人或群體的觀點,只是取決於它在特定情境下的對比,不一定代表客觀事實。

別人說不好不一定代表真的不好。

程式碼的壞味道

評價程式碼或程式架構也是一個主觀的過程,受到個人觀點、經驗和期望的影響,一個人的不好也可能是另一個人的好。

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

1. 以管理面來說

  • 把元件拆開:一開始我們被要求做出五個角色要用的刀子,以刀子來說就是刀加上刀柄,但隨著時間過去不同的角色會有不同的需求,漸漸的演化成美工刀、水果刀、菜刀等等。當需要改相同且類似的部分就會面臨到一次要改很多地方的命運,這種修改方式也被稱做散彈槍式的修改 (Shotgun Surgery)
  • 把物件合併集中:當相同類型物件集中之後,會漸漸演化集多功能於一身的瑞士刀,這時候又會出現另外一個問題叫 Divergent Change (發散式修改),因為你會在一個元件上長出各種奇形怪狀的東西,漸漸的變成上帝等級的全能物件 (God object)。

2. 以組織和分類來說

  • Cohesion (內聚):簡單來說就是把全部相關的東西能放一起就放一起叫做內聚,但不知道怎麼分類的東西全部放在桶子裡就會叫垃圾桶。
  • Coupling (耦合):出門前你需要分別從內衣褲抽屜、衣櫃、鞋櫃分別找到需要的穿搭單品才可以出門,這就叫做耦合。

把元件合在一起的時候好處是集中管理,需求更動屬於共用的地方改一次就好了,壞處是有可能因為這個修改就改壞大家共用的地方,也可能因此讓共用的地方越長越大。

把元件依照責任分開,好處是當需求越來越牛鬼蛇神,越來越「在地化」,A 說希望 1 2 3 4 5,B 說 6 7 8 9 10,這時候分開就展現出了好處,就跟兩個不同的國家一樣分開治理分開發展,即使剛開始同文同種,後來也演變成完全不同的樣貌。那壞處大概是有共用的地方就需要改好幾個,造成 Shotgun Surgery。

好與壞是一體兩面的東西,重要的是要在多個角度和標準下評價,並盡量尋求多方反饋。有時候,負面評價可能提供了改進的機會,但也可能只是基於誤解或主觀情感。

最終,我們應該尊重不同的觀點,並根據客觀標準來評價程式碼的品質,最好的程式碼評價是建立在事實和證據之上,而不僅僅是基於主觀感受。

人生沒有好與壞,只是不一樣。


FAQ:程式碼異味常見問題

Q1:重複的程式碼 (DRY 原則) 一定要消除嗎?

A:DRY (Don’t Repeat Yourself) 是重要原則,但不可過度迷信。如果兩段程式碼只是「現在」長得一樣,但未來發展的「演化方向」不同(例如:兩個不同國家的業務邏輯),過早合併反而會導致嚴重的 Coupling (耦合)。有時候,容忍少量的重複比面對複雜的錯誤抽象更好。

Q2:如何分辨一個物件是「高內聚」還是「垃圾桶」?

A:觀察物件的「職責」。如果一個物件裡的所有方法都是為了完成同一個目標(如:處理訂單金額),那就是高內聚。如果裡面既有處理 UI 顯示、又有處理 API 調用,還有處理資料格式轉換,且這些方法互不相干,那它就是一個垃圾桶

Q3:遇到 God Object (上帝物件) 該怎麼動手重構?

A:建議採用「蠶食」策略。不要試圖一次重寫整個物件,而是先從中識別出最獨立的小功能,將其提取 (Extract) 為獨立的元件或 Hook。持續將責任轉移出去,直到原本的 God Object 變回一個單純的協調者。


掌握對「壞味道」的敏銳度,不是為了寫出完美的程式碼,而是為了在適當的時機做出正確的折衷。記住,架構的藝術就在於權衡!


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