Item 48: Improve the Suspect Code’s Readability and Structure#
雜亂、寫得差的程式碼是 bug 的溫床。清理程式碼可以揭露隱藏的 bug,讓你得以修復它們。
在開始清理之前,確保你有時間和權限完成它。將 cosmetic changes 和 refactorings 與實際的 bug fix 分開到不同的 commits 中。
第一步:修正格式#
- Spacing:確保程式碼一致地遵循運算子和保留字周圍的空格規則
- Indentation:始終使用相同數量的空格(通常 2、4 或 8),一致地套用
- 對齊:對齊相似的運算式可以讓差異更突出
- 空行分隔:用空行分隔邏輯區塊,使程式碼結構更清晰
如果手動格式化太困難,使用工具如 clang-format 或 indent 自動完成。
第二步:Refactor 程式碼結構#
格式化只能改善外觀。更深層的改進需要 refactor——在維持功能的同時改善結構。以下是常見的 code smells 和對應的重構方式(多數源自 Martin Fowler 的 Refactoring):
Duplicated Code(重複程式碼):
- 將重複的程式碼抽取到共用的函式、class 或 template 中
- 確保整個程式使用正確的版本
Switch Statements:
- 遺漏的
case元素容易被忽略 - 至少加入會記錄錯誤的
defaultclause - 更好的做法:用 polymorphism 取代
switch——將每個 case 的行為移到對應 subclass 的 method 中
Shotgun Surgery(散彈手術):
- 單一修改影響多個 methods 和 fields
- 將相關的 fields 和 methods 移到同一個 class 中
Data Clumps(資料叢集):
- 總是一起出現的資料物件應該被組成一個 class
- 同時作為參數和回傳值使用
Primitive Obsession(基本型別偏執):
- 用 integers 或 strings 表示貨幣、日期、郵遞區號等
- 引入 class 來包裝這些值
- 使用 containers(linked lists、resizable vectors)替代 primitive arrays
- 用 bespoke classes 表示物理量(時間、質量、力、能量)
Varying Interfaces(不一致的介面):
- 不一致的 method 名稱、參數順序和型別會遮蔽 bug
- 統一化 method 名稱(renaming)和參數(reordering)
Long Routines(過長的函式):
- 拆分為更小的部件,分解複雜的 conditionals
- 如果因為太多暫存變數而難以拆分,考慮轉換為 method object
Inappropriate Intimacy(不當親密):
- 程式碼組件之間不當的互動會破壞 invariants
- 確保 class 之間的關聯是單向而非雙向
- 引入 delegate methods 來取代過長的 delegation chains
// 過長的 delegation chain
account.getOwner().getName()
// 引入 delegate method 後
account.getOwnerName()Comments(過多註解):
- 註解有時用來掩蓋難以理解的程式碼
- 用名稱反映原始註解意圖的 method 來取代被註解的程式碼區塊
- 用 assertion(見 Item 43)更有效地表達註解中的 preconditions
Dead Code 和 Speculative Generality(死碼與投機泛化):
- 移除未使用的程式碼和參數
- 合併只有一個 client 的 class hierarchy
- 將名稱怪異的抽象 method 改為反映其實際功能的名稱
重點回顧#
- 以一致的方式格式化程式碼,讓你的眼睛能捕捉到錯誤模式
- Refactor 程式碼,揭露隱藏在寫得差或不必要複雜的程式碼結構中的 bug