閱讀筆記:Good Code, Bad Code Chapter 4 Errors

總結

此篇文章是 Good Code, Bad Code: Think like a software engineer 第四章(Errors)的閱讀筆記。除記錄章節重點外,本篇文章也提供一些如何在 TypeScript 實作此章建議的範例程式碼。

本章重點如下:

筆記

Recoverability

本書認為軟體錯誤可分為「可復原」與「無法復原」兩種類型,此二類型的特徵如下:

Robustness vs. failure

failing fast and failing loudly

注意穩健性(Robustness)

以前端開發來說,在 JavaScript 中如果拋錯(throw new Error('...'))但未搭配 try...catch 語法來捕捉錯誤的話,即會導致整段程式碼停止執行。在前端遵守 failing fast and failing loudly 原則時,需注意是否為該功能附上對應的 try...catch 語法,否則服務會在錯誤被拋出時即停止運行,導致使用體驗不佳。

以 React 為例,元件 ErrorBoundary 可視為整個 APP 的 try...catch 語法;如果整個 APP 中有部分程式碼會拋錯,但沒有緊鄰的 try..catch 來捕捉該錯誤,此錯誤最終會被 ErrorBoundary 攔截。工程師可透過此元件決定如何處理錯誤(發送 GA 事件來記錄錯誤資訊、顯示提示用的 UI 元件等等)。

需注意:目前 TypeScript 尚未支援在 function 的回傳型態中定義該 function 是否會拋錯(參考 How to declare a function that throws an error in Typescript),這部分就得仰賴文件說明、或是呼叫 function 的使用者需意識到該 function 得搭配 try...catch 來確保程式碼的穩健性。

範例:如何在 TypeScript 中提示錯誤

function getSquareRoot(arg) {
  // 根據參數 arg 回傳其開根號後的值,如果 arg 不是大於等於零的數字,則此 function 無法對該 arg 開根號
}

以上列 getSquareRoot 為例,在 TypeScript 中有以下幾種「讓工程師察覺到參數 arg 的值不合理」的手段:

直接拋錯

優點:提供明確錯誤訊息,呼叫此 function 的工程師會知道為何出錯 缺點:如果使用此 function 的工程師沒有提供對應的 try...catch 語法的話,整段程式碼一出錯(被餵了不合法的參數)就會停止執行,且目前 TypeScript 還無法透過型別提示該 function 是否會拋錯,需要引用 function 的工程師主動確認文件或原始碼

function getSquareRoot(arg) {
  if (typeof arg !== 'number') {
    throw new Error(
      `getSquareRoot arg not valid, should be number, but receive ${arg}, type: ${typeof arg}.`
    );
  }
  // ...
}

回傳 null

優點:可透過 TypeScript 的型別定義讓呼叫此 function 的工程師意識到回傳的值可能為 null 或數字,工程師需要先檢查回傳的值的型別,才能繼續執行接下來的邏輯 缺點:回傳 null 無法提供太多有意義的錯誤資訊

function getSquareRoot(arg) {
  if (typeof arg !== 'number') {
    return null;
  } else {
    // ...
  }
}

回傳 magic value

下下策,回傳的值基本上無法提供有意義的錯誤資訊,也無法透過型別檢查得知回傳的值是否有效;儘管有這種作法但並不推薦實務使用

function getSquareRoot(arg) {
  if (typeof arg !== 'number') {
    return -1;
  } else {
    // ...
  }
}

參考文件