排查詭異 bug 最浪費時間的不是找不到原因,而是一開始就把方向押錯。當「明明邏輯沒改、昨天還好好的」這句話出現時,凶手通常不在你正在盯著的那段程式裡。
這篇想談的不是某個具體 bug,而是當你撞牆時,該怎麼重新分配懷疑的權重。
凶手最常躲在哪裡
以協和的經驗法則,把「詭異 bug」依出現頻率粗略分類,大概是這幾類:
第一類:環境差異。 本機正常、測試機正常、正式機壞掉。差別往往是 Node 版本、Python 套件的次版號、時區設定、檔案系統大小寫敏感度、或某個只在正式機才存在的反向代理。Docker 化解決了一部分,但沒解決全部——base image 拉到的不一定是同一個 digest。
第二類:邊界資料。 程式對 99% 的資料正常,遇到某筆就炸。常見是字串裡夾了零寬字元、CSV 欄位裡有未跳脫的引號、JSON 數字大到 JavaScript 的 Number 接不住、或某個欄位偶爾出現 null 而你假設它不會。
第三類:時間與順序。 Race condition、cache 過期、cron 沒跑、佇列訊息順序錯亂、資料庫 transaction 隔離等級沒設對。這類 bug 的特徵是「重現不了」,因為你每次手動測試的時序都跟線上不一樣。
第四類:第三方服務的隱性變更。 金流 API 默默調整了回傳欄位、物流系統把某個狀態碼的含義改了、雲端服務的預設行為在某次更新後不同了。你的程式沒變,但對方變了。
一套不會白費力氣的排查順序
撞到詭異 bug 時,先別急著 debug。我們通常建議客戶的工程團隊按這個順序走:
先確認「壞掉」的定義。 使用者說「下單失敗」,到底是按鈕沒反應、API 回 500、還是訂單建了但金流沒成功?不釐清這點,後面全是猜。
再確認最後一次正常是什麼時候。 這個時間點對齊到 deploy log、第三方服務的狀態頁、資料量的成長曲線。能對齊任何一條線索,懷疑範圍就大幅縮小。
然後是縮小範圍,不是放大假設。 與其想「會不會是 X 壞了」,不如做「能不能讓 X 不參與,看還會不會壞」。把懷疑的元件一個一個拿掉,比加更多 log 有效率。
Log 要分層,不要分散。 一個請求從進入到結束,能不能用同一個 trace id 串起來?如果不行,請先補這個基礎建設,再回頭追 bug。沒有 trace 的 log,跟沒有一樣。
舉個例子,假設一家中型電商反應「偶爾」訂單金額會少算運費。如果直接跳進結帳邏輯翻程式碼,可能翻一整天。但如果先去看那些有問題的訂單有沒有共同特徵——例如都在某個時段、都來自某個物流區、都是用某種優惠券——通常半小時內就能鎖定方向。
我們的觀察
大部分讓人崩潰的 bug,不是技術太難,而是資訊太少。團隊願不願意投資在 log、trace、可重現的環境、以及第三方服務的變更通知上,決定了同樣一個 bug 是兩小時還是兩天解決。寫程式的時間其實不長,難的是讓系統在出事時願意告訴你它怎麼了。
