Item 48: Improve the Suspect Code’s Readability and Structure#

雜亂、寫得差的程式碼是 bug 的溫床。清理程式碼可以揭露隱藏的 bug,讓你得以修復它們。

在開始清理之前,確保你有時間和權限完成它。將 cosmetic changes 和 refactorings 與實際的 bug fix 分開到不同的 commits 中。

第一步:修正格式#

  • Spacing:確保程式碼一致地遵循運算子和保留字周圍的空格規則
  • Indentation:始終使用相同數量的空格(通常 2、4 或 8),一致地套用
  • 對齊:對齊相似的運算式可以讓差異更突出
  • 空行分隔:用空行分隔邏輯區塊,使程式碼結構更清晰

如果手動格式化太困難,使用工具如 clang-formatindent 自動完成。

第二步:Refactor 程式碼結構#

格式化只能改善外觀。更深層的改進需要 refactor——在維持功能的同時改善結構。以下是常見的 code smells 和對應的重構方式(多數源自 Martin Fowler 的 Refactoring):

Duplicated Code(重複程式碼)

  • 將重複的程式碼抽取到共用的函式、class 或 template 中
  • 確保整個程式使用正確的版本

Switch Statements

  • 遺漏的 case 元素容易被忽略
  • 至少加入會記錄錯誤的 default clause
  • 更好的做法:用 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