iT邦幫忙

2021 iThome 鐵人賽

DAY 17
1
Modern Web

Javascript 從寫對到寫好系列 第 17

Day 17 - Error Handling 錯誤處理

前言

錯誤處理往往是最容易被忽略的一塊,因為

  • 程式運行順利,那當然不用考慮 error case
  • 程式被測出 bug,通常是程式碼裡面有問題,QA 會催著你趕快去修,修完之後就回到第一點,那個「運行順利」的版本了,好像又不用錯誤處理了

這樣到底什麼時候要錯誤處理?

為了方便講解,我們先來複習一下錯誤處理的必須武器 try catch,如果你已經很熟了可以跳過這一段:

try/catch/finally

try/catch/finally 屬於流程控制的邏輯區塊(跟 if/else 一樣),以下分別介紹:

openFile();
try {
  writeFile(theData); // 可能產生例外
} catch(e) {
  handleError(e); // 處理可能發生的例外
} finally {
  closeFile(); // 總是在 try 結束後關閉檔案
}
  • try
    此區塊是主要的程式執行區塊,只要在這個區塊的任何一行拋出錯誤(無論有沒有用到 throw),就不會繼續往下執行,而是跳到 catch 區塊第一行開始執行。
  • catch
    此區塊可以在錯誤發生時,自動捕捉進來這個區塊,會進來代表「出代誌」了,通常會針對錯誤做一些對症下藥或善後處理,起碼可以確保程式不會直接死當。
  • finally
    此區塊是無論如何都會執行到的,trycatch 區塊執行完就會進來,用來處理一些無論有沒有錯誤都要做的事情(比如把 loading 關掉、寫 log 等)。

錯誤的種類

來談談錯誤的種類,了解一下「錯誤」到底是什麼?才會知道該怎麼處理它:

程式開發者的錯誤(programmer errors):

即程式本身的 bug,錯誤是程式本身沒寫對造成的。常見的例如:

  • 語法錯誤(syntax error):少括號、關鍵字拼錯等
  • 取值錯誤(reference error):變數、函式忘記宣告就使用
  • 類型錯誤(type error):在 Number 類型的變數使用 Object 的函式

這類型的錯誤代表與開發者的意圖背道而馳,因此沒有懸念,就是一定要把它改對,通常透過開發人員工具可以輕鬆找到原因。

運算的錯誤(operational errors)

在程式本身沒有 bug 的情況下,錯誤發生在系統本身,通常是程式與外部互動下發生,外部可能是使用者、網路遠端、檔案系統。常見的例如:

  • 使用者輸入極端值
  • 網路連線問題
  • 記憶體超出負荷

這類型的錯誤代表,通常在使用者「預想」情況下,程式是可以正常運作的,但是如果:

十筆資料可以跑,那十萬筆呢?
都市可以跑,那偏鄉地區呢?
json 檔案可以處理,那 xml 呢?

因此,其實這類型的錯誤不太算是「錯誤」,畢竟部分的 case 都還是可以運作的,或許比較適合稱呼為例外(exception)。

這類型的錯誤處理起來就複雜許多,需要對症下藥,

  • initial:針對首次使用服務可能會沒有初始值的問題,可以視情況先做 initial 動作。
let file;
try {
    file = readFile(filePath);
} catch (err){
    file = createFile(filePath);
}
console.log(file);
  • retry:針對網路連線類型的問題,可以限制次數 retry。
let retryCount = 0;
const retryMax = 3;
do {
    try {
        console.log(retryCount);
        // ...
        // 可能失敗的 code 放這 
        // ...
        break;
    } catch (err){
        console.log(err);
        retryCount++;
        if (retryCount >= retryMax) {
            // 超過 retry 次數強制跳出
            break;
        } else {
            // 還沒超過可以再試一次
            continue;
        }
    }
} while (true);
  • unknown:面對未知的問題,都應該要有最後一道防線,發生例外時記下 Log,跳出 toast 訊息提醒前端,並關掉 loading 讓使用者仍然可以進行其他操作等。
setLoading(true);
try {
    // ...
    // 可能失敗的 code 放這 
    // ...
} catch (err){
    logToDB(err);
} finally {
    setLoading(false);
    toast('系統不穩,請稍後再試');
}

我可以用 if/else 來處理錯誤嗎?

未知的問題在 catch 處理很合理,有一些已知的問題也放在 catch 處理,但既然我都知道這邊有可能會出問題了,為何不乾脆用 if/else 判斷處理?

這個問題其實我也覺得稍微模糊,我的想法是,需要先去定義出,catch 究竟要接收什麼?是所有取不回資料的狀況嗎?還是針對非正常流程的處理?針對不同的目的,應該使用不同的處理,以下是我的一些想法

適合放在 catch 處理的:

  • I/O 時發生錯誤,比如: 讀寫檔案、fetch 資料途中,發生例外情況
  • 無法完成預期的工作,比如: 想要讀取登入後的畫面,但因為還沒登入導致的錯誤
  • 內部錯誤,比如後端自己死掉,或是前端傳送錯誤參數

不適合放在 catch 處理的(可以用 if/else 處理):

  • 查不到資料(而非查資料失敗)的情況,比如: 資料庫查詢,query 不到東西的情況下,需要處理空值,而非拋出錯誤。
  • 詢問類,比如: 手機要讀取聯絡人清單的權限,當回傳的結果是 false,也就是使用者不授予權限時。

但這也不是硬性規定,單純是不同的想法導致不同的設計,甚至不同的 API 在處理錯誤的狀態也都不盡相同,因此筆者認為,只要整個 app 在面對錯誤是一致就可以了 (比如查無資料要嘛都放 else,要嘛都放 catch)。

為什麼錯誤處理是最容易被忽略的一塊

回歸到開頭的問題,為什麼錯誤處理往往是最容易被忽略的一塊?

  1. 相對陌生
    網路上的教學影片,實體課程裡面的教材,甚至 StackOverflow 等技術討論論壇,絕大多數的篇幅也都是在教如何「寫出你想要的 feature」,而非「修補可能發生的 exception」。
  2. 黑天鵝
    解決問題的第一步,是發現並重現問題,這樣修好了才知道自己不是矇到。但很多錯誤是在特定環境才會發生,比如直接操作 A 沒事,但先操作 B 再操作 A 就會錯誤。甚至要是極端情況(極短時間、大量資料)才發生,因此光是「產生」這種例外就很困難了。
  3. 看不見的防護網
    就算真的把這些難纏的問題搞定了,成果也很難被看見,因為對於使用者/PM 來說,發生錯誤令人煩躁,但沒發生錯誤卻像是基本,也許只有 20% 的時間會發生錯誤,但我們卻需要花費 80% 的時間去處理。

類比來說,其實錯誤處理很像是現實社會中的「保險」:

  1. 相對陌生
    許多人常常「主動」去自學股票、網頁、廚藝等學問,而保險也是一門學問,但有趣的是,大部分接觸到保險的人屬於「被動」接觸,也就是迫於需要,不得不去理解,往往是生活中出現漏洞才會想到。
  2. 黑天鵝
    大家都知道保險重要,因為永遠不知道明天跟意外哪個先到,尤其意外愈嚴重愈重要,但嚴重的意外本來就很少見,當我們意識到時,往往已經深陷泥沼。
  3. 看不見的防護網
    即便我們真的把保險買齊了,醫療意外儲蓄一應俱全,甚至連長照險都超前部署,生活品質短時間仍然不會有變化,畢竟保險確保的是,即便意外發生,仍然確保你有一定的生活品質。

當然啦,錯誤處理跟保險還是有很多細節、情境的不同,不可完全類比,這邊只是筆者有感而發XD,覺得有些事情,我們在寫程式的時候會碰到,在現實生活中也會碰到。

結語

錯誤/例外處理是很多開發者不太想去碰的一塊,很容易只關注在「正常」,而比較少考慮「異常」。

市場上大部分的商業專案、產品,PM 當然也都是要求先有功能,畢竟沒功能的話,就算有一堆 try catch 也是枉然。

但正是因為「少而重要」,才能體現出價值,如果能夠在這點更謹慎,無疑是朝向「更好」的 developer 邁進!

愈深的洞穴裡
藏著愈閃耀的寶石

參考資料

MDN - 例外處理


上一篇
Day 16 - Asynchronous 非同步進化順序 - Async/Await
下一篇
Day 18 - 未知與空值 undefined、null、NaN
系列文
Javascript 從寫對到寫好30

1 則留言

0
TD
iT邦新手 4 級 ‧ 2021-10-11 22:54:00

這讓我想到 笑談軟體工程:例外處理設計的逆襲 這本書的存在...

ycchiuuuu iT邦新手 5 級 ‧ 2021-10-12 12:24:02 檢舉

哇讓我來瞧瞧,看起來是整本書都充滿了,讓人不想看到的東西們XD

我要留言

立即登入留言