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