有一種 bug,永遠只在你不在電腦前的時候出現。使用者截圖傳來,QA 重現不了,正式環境的 log 一片乾淨。你懷疑是使用者亂點,但問題就是每週固定回來一次。
這類「幽靈 bug」在整合型系統裡特別常見。電商串金流、串物流、串 ERP,每一個介面都是一顆可能鬆掉的螺絲。真正棘手的地方不是修,而是讓它願意在你面前壞一次。
先分清楚:是隨機,還是你以為它隨機
工程師講「這個 bug 隨機發生」時,九成的情況是還沒找到觸發條件。真正隨機的錯誤很少,多半是某個維度沒被記錄下來。
常見被忽略的維度:
- 時間:週期性排程、憑證過期、跨時區換日、月底結算
- 使用者狀態:session 剛過期的瞬間、剛換過密碼、剛升級方案
- 資料狀態:某張表剛好在某個大小、某個欄位第一次出現 null
- 上游服務:第三方 API 那邊剛好在部署、限流剛好被打到
舉個例子,假設一個訂單系統每逢週一早上就有幾筆訂單卡住。第一直覺是流量高,但擴容之後沒改善。真正的原因可能是週末排程跑的資料同步任務在週一清晨才完成,導致某段時間內快取跟 DB 不一致——這種問題你盯著應用程式 log 是永遠找不到的,因為錯誤根本不在應用層。
排查的第一步,不是加 log,而是把所有能想到的維度列出來,然後看哪些你其實沒在觀測。
從最不可能的地方開始懷疑
工程師的直覺通常會先懷疑自己最近改過的程式碼,這在多數情況下是對的。但當一個 bug 已經找了兩三天還沒頭緒,代表直覺路徑已經走完了,該換方向。
這時候的經驗法則是:懷疑那些「應該不會壞」的東西。
# 示意:一段看起來人畜無害的重試邏輯
def call_payment_api(order):
for attempt in range(3):
try:
return payment.charge(order)
except TimeoutError:
continue
raise PaymentFailed()
這段程式碼看起來沒問題,但如果金流 API 是非冪等的,timeout 之後重試就可能造成重複扣款。而扣款成功但回應 timeout 的情境,通常在測試環境永遠遇不到,只會在正式環境某個網路抖動的瞬間發生一次。
類似的「隱形假設」還有:
- 資料庫 transaction 假設不會被外部呼叫打斷
- 前端假設 API 永遠回傳同樣的欄位順序
- Log 假設寫入不會失敗、所以沒處理 exception
- 快取假設 TTL 到了就一定會重抓,沒考慮 thundering herd
真正的除錯功力不在會用什麼工具,而在看到一段程式碼時,能列出它背後有多少個沒明說的假設。每一個沒明說的假設,都是未來的 bug。
我們的觀察
處理過夠多整合案的團隊都有一個共識:詭異 bug 通常不詭異,只是觀測不足。與其花時間猜,不如先補齊 log、trace ID、上下游時間戳,讓下一次它出現時你能看清楚全貌。修 bug 是一次性的,但把系統變得可觀測,會長期回本。而在報價客製化系統時,我們通常會建議客戶把可觀測性當成第一版就要有的功能,而不是等到出事才加。
