Item 49: Fix the Bug’s Cause, Rather Than Its Symptom#

用局部修復來掩蓋問題是一種令人驚訝地誘人的「修復」方式。以下是一些用 conditional statement 來「修復」的典型例子:

  • if (p != null) 來避免 null pointer dereference
  • if (nVehicleWheels == 0) 來迴避除以零
  • if (a < 0) a = 0; 來將不正確的數值硬塞進合理範圍
  • if (surname.equals("Wolfeschlegelsteinha")) 來修正被截斷的姓氏

為什麼 Coding Around Bugs 是壞事#

上述某些語句可能有合理的解釋。但如果 conditional 是為了修補 crash、exception 或錯誤結果而放上去的——而不理解根本原因——那就不可原諒。

圍繞 bug 寫程式碼會造成以下問題:

  • 這種「修復」可能引入新的、更隱微的 bug,因為它 short-circuit 了某些功能
  • 不修復根本原因,其他較不明顯的症狀可能仍然存在,或者 bug 以不同的形式再次出現
  • 程式碼變得不必要地複雜,難以理解和修改
  • 根本原因變得更難發現,因為「修復」隱藏了它的表現(例如原本可以引導你找到根本原因的 crash)

修補 bug 的症狀而非原因是一種虛假的節省,會讓你的程式背負 technical debt

應該泛化而非特殊化#

一個相關但較輕微的問題是:試圖除錯和修復特殊 case,而解決方案其實是用更簡單的方式泛化處理。例如,將角度正規化到 0 到 2*pi 的範圍:

// 錯誤:只處理特殊 case,不能處理 < 2*pi 或 > 2*pi 的角度
if (angle < 0)
  angle += Math.PI;
else if (angle > 2 * Math.PI)
  angle -= Math.PI;

// 較好但仍複雜且可能不精確:
while (angle < 0)
  angle += Math.PI;
while (angle > 2 * Math.PI)
  angle -= Math.PI;

// 正確的泛化解法:
angle = angle - 2 * Math.PI * Math.floor(angle / (2 * Math.PI));

重點回顧#

  • 永遠不要圍繞 bug 的症狀寫程式碼:找到並修復根本原因
  • 盡可能泛化複雜 case 的處理,而非逐一修復特殊 case