系列第一篇介紹高效測試策略與撰寫簡單的 System.gc() 偵測器。Bug 模式識別有問題的編碼慣例,常潛伏於 Bug 週邊。雖非所有模式出現處皆為 Bug,但有效偵測器能以較低成本掃描大量程式碼。本文開發更實務的 RuntimeException 捕獲 偵測器(FindBugs 內建)。
Java 異常階層讓客戶端可分別處理 FileNotFoundException 或統一捕獲 IOException。但易犯錯誤:將多個相同處理邏輯的 catch 區塊合併為單一 catch (Exception e),因 RuntimeException 繼承 Exception,導致未檢查異常被吞沒而非傳播。
// 錯誤示範:RuntimeException 捕獲
catch (Exception e) { logger.log("...", e); }
// 正確修法:先捕獲 RuntimeException 重拋
catch (RuntimeException e) { throw e; }
catch (Exception e) { logger.log("...", e); }
編譯器不提醒、程式碼審查易疏漏,故需偵測器自動化防禦。
繼承 BytecodeScanningDetector,覆寫 visitCode() 初始化收集清單,visit(CodeException) 累積每個 catch 區塊資訊(異常類型、try 區塊位元碼範圍、handler 位置)。
覆寫 sawOpcode() 處理 athrow、invokestatic、invokevirtual、invokespecial 指令:
athrow:透過 OpcodeStack 取得運算元類型累積方法中各位元碼偏移量拋出的異常類型。
visitCode() 返回後,throwList、catchList 已完整。演算法:
Exception 的區塊RuntimeException 的區塊Exception 子類別RuntimeException 且 try 區塊未拋出已檢查異常 → 潛在 Bug靜態分析最大成本在處理誤報。優化策略:
int priority = LOW_PRIORITY + 1;
if (range > 300) priority--; // try 區塊過大,可信度降低
else if (range < 30) priority++; // 區塊小,可信度提高
if (catchClauses > 1) priority++; // 多 catch 合併動機明確
if (thrownSet.size() > 1) priority--; // 拋出多種已檢查異常,捕獲 Exception 合理
if (caughtException.dead) priority--; // 死碼區塊
優先級值越低代表嚴重性越高,超過閾值的 Bug 會被忽略。
為 FindBugs 撰寫自訂偵測器能顯著提升程式碼品質。雖開發與優化過程艱辛,但一次投入可長期受惠於所有專案,建立對已知 Bug 模式的自動化防禦網。