🎯 什麼情境該想到我
分工:這張是判準卡。本例中 MQ 消費端的正確做法 → 工具-MQ消費端防禦三原則;從生產端根治 → 工具-Transactional-Outbox-Pattern。
當你在 review 一個「看起來合理」的修法(upsert、retry、try-catch、加鎖…),想判斷它是真的修好還是只是把症狀藏起來時。
⚙️ 怎麼用
- 先問「為什麼這裡會有這個問題?」 而不是「這個修法對不對」。
- 例:不是問「upsert 寫得對嗎」,而是「為什麼這裡會需要 upsert」。
- 順著症狀跨系統往上追因果:worker 症狀 → MQ 時序 → sender 的 transaction 邊界。
- 判斷準則:如果這個修法讓 error 消失、但根本的錯誤狀態仍然存在(只是不再噴錯),它就是掩蓋,不是修復。
- 掩蓋型修法要擋下來——症狀消失後就沒人會再發現真正的問題。
★ 常見的「看起來像修復」的做法,怎麼判斷
| 做法 | 什麼時候是掩蓋 | 什麼時候是修復 |
|---|---|---|
| upsert(查不到就新增) | 資料「應該已經存在」卻查不到——你在用新資料蓋掉一個時序錯誤 | 業務上本來就允許「第一次寫入」與「後續更新」走同一條路 |
| retry | 錯誤是確定性的(邏輯錯、資料錯),重試只是延後失敗 | 錯誤是暫時性的(網路抖動、複本延遲),而且有上限與退避 |
| try/catch 吞掉錯誤 | 吞掉之後回傳一個「看起來正常」的值 | 在系統邊界把錯誤轉成明確的回應,並記錄下來 |
| 加鎖 | 用一把大鎖蓋住一個不該並發的設計 | 保護一個確實需要互斥的共享狀態,且範圍最小 |
| 加大 timeout | 真正的問題是慢查詢或死結 | 下游的正常延遲分布本來就比原設定長 |
| 重啟服務 | 記憶體或連線洩漏,重啟只是讓它晚點再爆 | 一次性的損壞狀態,且已經找到並修掉原因 |
★ 四個檢查問題
- 錯誤狀態還在嗎? 修法讓錯誤訊息消失了,但系統裡的資料或狀態仍然是錯的——那就是掩蓋。
- 能解釋為什麼會發生嗎? 講不出因果鏈(「A 在 B 之前發生,因為……」),就還沒找到根因。
- 下一個人還看得到問題嗎? 修法讓問題在監控、日誌、測試裡都消失了——之後就沒有人會再發現它。
- 換一個輸入還會發生嗎? 寫一個能重現原問題的測試;修復之後它應該通過,而且是因為對的理由通過。
★ 緊急止血時的正確做法
事故當下,掩蓋型的做法可能是對的——先讓使用者不再受影響。但要同時:
- 在程式碼裡標清楚:
// TODO(#1234): 暫時的 upsert,根因是 sender 在 commit 前發送訊息。 - 開一張追蹤根因的工單,並在事後檢討中列為行動項(見 工具-無指責事後檢討)。
- 讓暫時的做法可觀測:每次走到那條補丁路徑都記一筆指標,才知道根因修好後它是否真的不再被觸發。
🧪 我實際套用的紀錄
- 2026-07-13:(待填)
⚠️ 注意 / 什麼時候不適用
- 緊急止血時可先用掩蓋型修法擋住,但必須開票追 root cause,別讓它變成永久補丁。
- 核心心法:不要比 AI 更會 coding,要比它更會 reasoning。
🔗 相關工具
- 工具-用判斷力而非蠻力 —— 同一取向,與其埋頭多寫幾層防護,不如把根因判斷對
- 欲速則不達 —— 《45 個習慣》:「成千上萬的 +1 和 -1 修正」就是掩蓋累積出來的沼澤
- 工具-系統化除錯 —— 找根因的方法