錯誤處理與穩健性

檢查每一個可能失敗的呼叫(checking every call that can fail)

想像飛行員的起飛前檢查清單。重點不在於飛行員預期襟翼壞掉——而在於那唯一一次它真的壞了時,跳過這項檢查就會害死所有人。檢查每一個可能失敗的呼叫,就是把同樣的紀律套用到程式碼上:每次你呼叫一個可能失敗的函式,就看它的答覆並據以行動,即便幾乎每次答覆都是「沒事」。你跳過的那次罕見失敗,恰恰就是會反咬你的那一次。

實務上這表示:如果一個函式回傳一個示意成功或失敗的值,你就必須在繼續之前檢視那個值。write() 回傳實際寫入的位元組數——可能比你要求的少,所以你必須檢查它。malloc() 可能回傳 NULL——在使用指標前要檢查。close() 也可能失敗(延遲的寫入錯誤會在這裡浮現)——要檢查。寫成 read(fd, buf, n); 然後就往下走的誘惑非常大,因為它「通常都會動」。紀律則說:把回傳值接住、和失敗哨符比對、然後分支。相反的習慣——忽略回傳值——是 C 程式中最常見的默默損壞來源之一,因為程式會帶著錯誤的假設(一個沒寫成的檔案、一個未初始化的緩衝區)繼續跑下去,直到傷害在離成因很遠的地方才顯現。

為何重要:沒檢查的呼叫,正是「編譯通過也跑起來了」如何變成「它默默把檔案截斷,一個禮拜都沒人發現」。編譯器能幫忙——C++ 的 [ [nodiscard] ] 屬性和 gcc 的 warn_unused_result 能讓忽略某些回傳值變成警告——但核心防線是人的紀律。誠實的提醒:檢查不代表所有事都要在本地處理;它代表你看了結果並做了決定(處理它、向上傳遞、或刻意且顯眼地忽略它),而不是根本連看都不看。

一個常見的錯誤:ssize_t n = write(fd, buf, len); 卻不檢查。如果磁碟滿了,write() 可能回傳 -1 或一個不足的數量;程式卻「成功」了,什麼都沒存到。修正:ssize_t n = write(fd, buf, len); if (n < 0) { perror("write"); return -1; } if (n < len) { /* 短寫:把剩下的寫完 */ }

接住並檢視回傳值,把一個默默丟失資料的錯誤,變成一個被處理過的情況。

有些呼叫在特定情境下確實不會有意義地失敗,忽略它們也站得住腳——但那就要明講出來(例如轉型為 (void) 或加註解)。危險的是「沒檢視就忽略」,而不是「刻意忽略」。

又称
never ignore a return value不忽略回傳值