重構 (Refactoring) 學習心得筆記 — 壞味道 (Bad Smell / Code Smell)

卡哥
7 min readDec 5, 2020

文章同步刊登於:

何謂壞味道 (Bad Smell / Code Smell)

程式中需要進行重構的部分,被稱為壞味道或程式碼臭味 (Bad Smell / Code Smell)

當程式中有下列這些問題時,就可能存在壞味道:

  • 難以理解

例:函式、類別名稱名命太籠統不明確;程式碼太長,或函式做太多事;Magic Number之類的神秘數字

  • 難以修改

例:改個功能要改很多地方,重複的程式碼太多

  • 難以擴張

例:沒有使用物件導向的方式去設計

有上述三種情況之一,代表該處隱藏重建的可能性

如何判斷程式中有壞味道?

可以透過以下的口訣來判斷程式中有壞味道:

重複了

在很多個地方散佈著相似或相同的程式碼的狀態,如果要做修改,就要改很多地方,才能使其保持一致,這樣的寫法很不好

太長了

函式或類別如果太長,或是函式要帶入的參數太多(超過3個),就會變得很難讓人理解,也不好維護

名不符其實

函式或變數名稱如果無法表達其代表的功能,會讓程式不容易被理解

公開太多了

變成public之後的函式可能被其他所呼叫,會有「這個函式若移除了,會不會有其他程式影響?」的疑慮

不太像是物件導向

比方說大量使用switch敘述跟if敘述,充滿分歧的流程處理,或是大量使primitive variables (int, double…etc),不建立專用的類別,這讓程式不夠物件導向,不易擴展與維護。

壞味道與重構

壞味道的重構行進方式:

  • 搜尋與確認程式碼中的壞味道
  • 搜尋重構目錄,找出此壞味道應透過何種重構方法解決
  • 根據重構方式進行程式碼之修改

有哪些壞味道?

這一部分大概說明有哪種幾壞味道與其特性,不會細講每種壞味道的重構方法,重構方法可參考外連網站的說明,或等日後再補充

Bloaters (肥哥的味道)

巨大的類別、很肥的Method或Classes,難以維護,通常積累了一段時間,可以再細分為下列這幾種特性

Long Method (過長函式,囉唆,迂迴)

  • 指的是函式中有太多行程式碼,通常超過10行就會讓人開始想問說這個Method或Function在做什麼

Large Class (巨大類別,肥Code,大泥球,God Class)

Primitive Obsession (基本型別痴迷狂,偷懶不用OOP設計)

  • 老是用 Primitives (int, String, etc.) 而不使用小的Class來處理一些簡單的事
  • 用太多常數(Constant),像是用USER_ADMIN_ROLE = 1來表示管理者權限
  • 在資料陣列中以字串常數做為屬性名稱,喜歡用String來表示任何東西

Long Parameter List (過長的參數列)

  • 方法的參數超過三個或四個,越來越長,越長就越難讀
  • 通常是慢慢加上去的,想多傳入一個參數就直接加在後面

Data Clumps (資料團)

  • 常常一起出現的資訊,這個問題常常是Long Parameter ListPrimitive Obsession發生的原因
  • 一個Long Parameter + Primitive Obsession 的範例: String printAddress( String country, String state, String city, String address1, String address2, String zipCode) {…} (什麼都String就是怎樣)
  • 像地址本身就可以用一個class Address來取代變成只傳入Address就好: String printAddress(Address address) {…}

Object-Orientation Abusers (物件導向濫用者的味道)

程式沒有遵守物件導向程式設計的原則,程式寫的很不OOP,可以再分為下列這幾種特性

Switch Statements

  • 用了太多層或太複雜的Switch或If-else statements讓整個程式流程很混亂
  • 通常可以利用OOP的多型的特性解決

Temporary Field (迷之暫時欄位)

  • 只有在某些特定情況才需要用到的暫時性Field,其他情況不需要,讓人困惑

Refused Bequest (被拒絕的遺產)

  • 濫用繼承,子類別只用到了父類的某些方法或欄位,不需要用的到就像是被拒絕的遺產
  • 例如Bird類別有Fly這個方法,有一個子類別是駝鳥但不會Fly,跟不需要這個方法

Alternative Classes with Different Interfaces

  • 多個methods存在於不同的classes但做一樣的事

Change Preventers (改不動的味道)

這個壞味道指的是,當你要改一個地方的Code,你得必需要去改其他很多地方,這樣的Code一旦存在且越來越多,會導致程式越來越難以維護,改不動,有下列幾種特性

Divergent Change (發散式變化)

  • 當你只改了類別的一個東西,你發現你還需要改同類別的其他方法,舉例來說,當你新增了一個新的product type,你必需再去改其他的方法像是finding, displaying及ordering product,這並不是你這個change的重點

Shotgun Surgery (散彈修改)

  • 做一個小修改需後,還需要再對其他不同的類別做額外的小修改

Parallel Inheritance Hierarchies (平行繼承體系)

  • 當你加了一個subclass到某個類別,你同時必需加另一個類似的subclass到另一個類別下

Couplers (緊密耦合的味道)

發生在Class間存在了緊密耦合的關係,分為下列幾種特性

Feature Envy

  • 一個類別用了其他的類別方法勝過使用自己的,有點像是迷戀了別的類別的Feature

Inappropriate Intimacy

  • 一個類別用了太多其他類別的細節,例如直接使用別的類別的public fields

Message Chains

  • 一個方法串太多層去拿到結果,像是: $a->b()->c()->d()

Middle Man

  • 一個類別只做了一件事:就是去叫另一個類別做事,那要他幹嘛?

Dispensables (多餘的味道)

Comments (無用的註解)

  • Comment 通常都會說謊XD
  • 不該有沒用的Comment,程式最好是不需要註解就能理解

Duplicate Code (重複的程式)

  • 同樣的程式出現在不同的地方

Lazy Class (懶類別)

  • Class做的事太少,根本不需要它的存在

Data Class (資料類別)

Dead Code (死掉或無用的程式碼)

  • 一些變數、參數、方法、類別已經沒有人在使用的

Speculative Generality

  • 現階段根本用不到的程式,只是幻想以後會用到
  • YAGNI: Your ain’t gonna need it

Other Smells (其他壞味道)

Incomplete Library Class

  • 不久或遲早,一些老舊的 libraries 會無法滿求需求,必需要換掉或升級

心得

預防勝於治療,學習了解Code Smell的特徵與其未來所造成的成本,能讓工程師在寫程式的初期就避免掉一些技術債的產生,自己在寫程式的時候,就會注意到是否會產壞味道,另外在團隊做Code Review時,也能為程式品質做把關,不要讓有壞味道的程式進入專案程式碼中,有助於程式未來的可讀性及可維護性。

參考資料

--

--

卡哥

我是Oscar (卡哥),卡哥小技倆(https://carger.tips/) 站長,前Yahoo Lead Engineer、Mensa會員,超過十年的工作經驗,服務過Yahoo關鍵字廣告業務部門、電子商務及搜尋部門,喜歡彈吉他玩音樂,也喜歡投資美股、虛擬貨幣,樂於與人分享交流!