文章最後更新於 2021 年 2 月 11 日
何謂壞味道 (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)
- 類別包含太多屬性、方法,做太多事,不符合SRP (Single Responsibility Principle)
- Primitive Obsession (基本型別痴迷狂,偷懶不用OOP設計)
- 老是用 Primitives (int, String, etc.) 而不使用小的Class來處理一些簡單的事
- 用太多常數(Constant),像是用
USER_ADMIN_ROLE = 1
來表示管理者權限 - 在資料陣列中以字串常數做為屬性名稱,喜歡用String來表示任何東西
- Long Parameter List (過長的參數列)
- 方法的參數超過三個或四個,越來越長,越長就越難讀
- 通常是慢慢加上去的,想多傳入一個參數就直接加在後面
- Data Clumps (資料泥團)
- 常常一起出現的資訊,這個問題常常是Long Parameter List與Primitive 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 (資料類別)
- 表示只有fields和getter/setter的類別,但有些情況下是可以存在的,像是DTO (Data Transfer Object)
- Dead Code (死掉或無用的程式碼)
- 一些變數、參數、方法、類別已經沒有人在使用的
- Speculative Generality
- 現階段根本用不到的程式,只是幻想以後會用到
- YAGNI: Your ain’t gonna need it
Other Smells (其他壞味道)
- Incomplete Library Class
- 不久或遲早,一些老舊的 libraries 會無法滿求需求,必需要換掉或升級
心得
預防勝於治療,學習了解Code Smell的特徵與其未來所造成的成本,能讓工程師在寫程式的初期就避免掉一些技術債的產生,自己在寫程式的時候,就會注意到是否會產壞味道,另外在團隊做Code Review時,也能為程式品質做把關,不要讓有壞味道的程式進入專案程式碼中,有助於程式未來的可讀性及可維護性。
參考資料
文章同步刊載於Medium:重構 (Refactoring) 學習心得筆記 — 壞味道 (Bad Smell / Code Smell)
關於作者
- 我是Oscar (卡哥),前Yahoo Lead Engineer、高智商同好組織Mensa會員,超過十年的工作經驗,服務過Yahoo關鍵字廣告業務部門、電子商務及搜尋部門,喜歡彈吉他玩音樂,也喜歡投資美股、虛擬貨幣,樂於與人分享交流!
最新文章
- 2024 年 8 月 26 日Java如何在 Java Spring Boot 中輕鬆使用 @Cacheable 提高應用效能
- 2024 年 8 月 25 日技術文章新手必看:MongoDB 實用入門指南 – 從零開始學習 NoSQL 數據庫
- 2024 年 7 月 18 日未分類ChatGPT, Claude AI 進階提示詞技巧:掌握AI對話的藝術 (Prompt Engineering)
- 2024 年 6 月 11 日程式設計Java 中的 volatile
如果對文章內容有任何問題,歡迎在底下留言讓我知道。
如果你喜歡我的文章,可以按分享按鈕,讓更多的人看見我的文章。