把錯誤處理當作第一等的考量(error handling as a first-class concern)
想像你做一頓飯,卻只為「一切順利」做打算——烤箱正常、沒有鍋子溢出、沒有客人過敏。只要任何一件事出錯,這頓晚餐就毀了,而你毫無備案。真正下廚的難處,以及真正寫程式的難處,大半都在於「出事時你怎麼辦」。把錯誤處理當作第一等的考量,意思是你從一開始就為失敗而設計,而不是等「順利路徑」能跑了之後才硬補上去。
具體來說,系統程式所做的幾乎每件事都可能失敗:開檔可能失敗(檔案不存在)、用 malloc() 配置記憶體可能失敗(它會回傳空指標)、從通訊端讀取可能失敗(連線斷了)、寫入磁碟可能失敗(磁碟滿了)。一支認真看待錯誤的程式,會對其中每一個操作先問「如果這裡就失敗了,我該怎麼辦?」,然後才問「成功時我要做什麼?」。錯誤路徑——失敗時會跑到的那段程式碼——是真正必須撰寫、閱讀並測試的程式碼,要和成功路徑一樣用心,而不是一句含糊的「這應該不會發生」的註解。
為何重要:在系統程式設計中,草率的錯誤路徑代價特別高。一個錯誤處理不當的網頁,只是版面跑掉;但一個錯誤處理不當的資料庫或核心,卻可能搞壞資料、把資源洩漏到整台機器卡死,或讓每一個依賴它的程式都當掉。誠實地說,在一支健壯的系統程式裡,錯誤處理往往佔了一半以上的程式碼;而「在我機器上能跑」的程式碼和「能在真實世界存活」的程式碼,差別幾乎全在於出錯時它如何表現。
比較同一行的兩種寫法。草率:char *buf = malloc(n); buf[0] = 'A';——如果 malloc() 因記憶體耗盡而回傳 NULL,這次寫入就會當掉(或更糟,搞壞記憶體)。謹慎:char *buf = malloc(n); if (buf == NULL) { /* 回報並乾淨地退出 */ return -1; } buf[0] = 'A';——malloc() 的失敗是事先規劃好的情況,而不是意外。
同一個操作;謹慎的版本把失敗當成一種正常、預期之內的情況。
第一等並不表示要在每個函式裡處理每一種錯誤——而是要對每一次失敗,有意識地決定該處理、向上傳遞、還是中止。真正的錯誤是根本不做決定,默默忽略掉失敗。