錯誤處理與穩健性

可復原錯誤 vs 臭蟲(a recoverable error versus a bug)

想像你開一家店。顧客刷卡被拒,是每天都會發生、預期之內的情況——你有一套客氣的應對說詞,店照常營業。但如果你發現收銀機默默把數字加錯了一個禮拜,那就不是「情況」——那是機器的缺陷,正確的反應是停止使用它並修好,而不是繼續結帳。這是兩種根本不同的「出了問題」,而把它們搞混,是處理錯誤時最常見的錯誤之一。

可復原錯誤是一種來自外部世界、你的程式可以合理預期並應付的失敗:一個不存在的檔案、一條斷掉的網路連線、一顆滿了的磁碟、一個在該輸入數字處打了字母的使用者。這些不是你程式的錯;它們是關於環境的事實。正確的反應是處理它們——回報問題、重試、退而求其次、詢問使用者、或把失敗向上傳給能做決定的呼叫者。臭蟲(也叫程式設計師錯誤或缺陷)則是你自己程式碼邏輯裡的瑕疵:解參考一個你程式碼的不變式說絕不可能為 NULL 的指標、索引超出陣列尾端、抵達一個你以為不可能到達的 switch case。對臭蟲的正確反應不是「復原」——根本沒有合法的狀態可復原到,因為程式現在處於一個你證明過絕不可能發生的狀態。誠實的做法通常是快速失敗:立刻停下(往往透過斷言或 abort()),讓臭蟲被大聲抓到、損壞不再擴散。

為何這個區分是承重結構:它告訴你該伸手去拿哪個工具。可復原錯誤需要錯誤檢查程式碼(回傳碼、errno、Result 型別)與優雅的處理。臭蟲則需要開發期的斷言,以及正式環境中一次乾淨而大聲的崩潰——因為試圖「處理」一個臭蟲,意味著替一個你並不理解的情況寫程式碼,而那通常只是把損壞藏起來,讓最終的失敗更難診斷。經典的錯誤是:用斷言去驗證使用者輸入(把可復原錯誤當成臭蟲,於是它在發行版裡消失),或把一個空指標解參考包在 try/catch 裡然後繼續(把臭蟲當成可復原的,於是程式帶著壞掉的狀態跛行下去)。

fopen() 因設定檔不存在而回傳 NULL,是個可復原錯誤:回報它,或許退回預設值。一個函式收到一個它自己的契約說不可能為 NULL 的指標,則是個臭蟲:對它下斷言並在測試時大聲崩潰,而不是把它糊過去、搞壞狀態。

外部世界的失敗去處理;不變式破壞的失敗去抓住並停下。

同一個徵狀(一個 NULL 指標、一個 -1)可能是兩者之一,取決於是誰的錯、以及你的程式碼是否預期到它。要問:這是環境在亂來(去處理它),還是我自己的邏輯有錯(一個臭蟲——快速失敗)?貼錯標籤,正是上面那兩種經典錯誤的成因。

又稱
expected error versus programmer errorerrors versus defects可復原錯誤與程式缺陷