iT邦幫忙

2024 iThome 鐵人賽

DAY 12
0
Software Development

一個好的系統之好維護基本篇 ( 馬克版 )系列 第 12

Day-12: 實務時 Code Review 時會看的地方 ( 錯誤處理 )

  • 分享至 

  • xImage
  •  

同步至 medium

https://ithelp.ithome.com.tw/upload/images/20240926/200893583ebUabBau5.png


看點 21. 不要回傳自定義錯誤碼

以下面的範例,但會有以下幾個問題 :

  • 首先是正常的邏輯與異常處理的 code 合在一起 ( 就是都回 int ),在維護時增加複雜度。
  • 外面 client 可能會忽略了回傳的處理。
  • 就算外面有處理=,還是需要往內追,才能知道 code 代表是什麼意思。
  • 分清楚什麼時後是正常什麼時後是錯誤

但事實上正常情況下,如果不是自幹狂或是團隊一點規範都沒有,那不然正常情況下應該不太有人會自已寫自訂錯誤碼出來。

enum ErrorCode {
  SUCCESS = 0,
  USER_NOT_FOUND = 1,
  PERMISSION_DENIED = 2,
}

function getUser(userId: string): { data?: string; errorCode: ErrorCode } {
  if (userId === "123") {
    return { data: "User data", errorCode: ErrorCode.SUCCESS };
  } else {
    return { errorCode: ErrorCode.USER_NOT_FOUND };
  }
}

看點 22. 有沒有偷偷忽略例外 (Ignored Exception)

就是 try catch 時 catch 裡面什麼都不寫,或是做一些處理。這個有些人看到會覺得是工程師的問題,他是痴帳或是不負責任嗎 ? 但這個事實上我覺得是幾個原因的,

其中我常看的到原因如下,它特別容易發生在一個方法用在四處的情況。

對 a 呼叫者(情境)來說,現在這個方法不是 error。

軟體工程在錯誤處理中,大部份的工程師應該都知道一個規則來決定是不是要丟例外 :

那就是從呼叫者的角度定義例外

但很慘的就是,這個可憐的方法的呼叫者就是一堆……

至於說要如何解呢 ? 我現在也想不太到比較實際的方法,然後以下只是我的想法解 :

  • 在設計一個方法時,想一下 SRP 原則,不要每次都想設計可以共同多個 context 的多功能方法。
  • 在越接近 domain layer 的,越要限縮這個方法支援的 context 比較好,因為 domain 的錯誤,很吃 context,所以就有可能發生 a 情況下這個不是錯誤,但 b 情況下這個就是錯誤。
try {
  // 可能會出錯的代碼
  a()
} catch (error) {
  // 忽略錯誤
}

看點 23. 不要讓太詳細的錯誤處理曝露給用戶

就是有些情況下,你打某個 api,然後你會看到很詳細的錯誤訊息,這個在 dev 環境還 ok,但是在 production 環境,那可能就會產生以下的問題 :

  • 安全風險:實現細節(如文件路徑、資料庫錯誤等)暴露給攻擊者,可能被利用來進行攻擊。
  • 用戶體驗不佳:用戶可能會被技術細節混淆,而無法理解實際的問題,降低用戶對系統的信任。
  • 數據洩漏: 敏感信息(如用戶數據、系統路徑、API密鑰等)如果包含在錯誤信息中,可能導致數據洩漏。

就記得在最後出去一層的地方還是要轉換一下 ~


看點 24. 不要有巢狀 Try 敘述 (Nested Try Statement)

就是 try catch 地獄,但事實上會不會發生問題,不好說,但主要影響的是以下幾個 :

  • 程式難以閱讀、維護。
  • 程式難以測試。
  • 可能掩蓋真實錯誤。
function readFileContent(filePath) {
  try {
    const content = fs.readFileSync(filePath, 'utf8');
    
    try {
      const jsonData = JSON.parse(content);
      console.log('JSON data:', jsonData);
      return jsonData;
    } catch (parseError) {
      console.error('Error parsing JSON:', parseError.message);
      return null;
    }
    
  } catch (readError) {
    console.error('Error reading file:', readError.message);
    return null;
  }
}

看點 25. 有沒有細心的處理資源釋放

例如下面這個 bad 的範例,他就是沒有考慮到 readSync 如果有錯誤的情況。

import * as fs from 'fs';
// Bad
function readFile(filePath: string): string {
  const fileDescriptor = fs.openSync(filePath, 'r');
  const buffer = Buffer.alloc(100);
  
  fs.readSync(fileDescriptor, buffer, 0, 100, 0);
  
  // 上面那行出錯,就不會釋放資源囉
  fs.closeSync(fileDescriptor); 

  return buffer.toString();
}

// Good
function readFile(filePath: string): string {
  const fileDescriptor = fs.openSync(filePath, 'r');
  const buffer = Buffer.alloc(100);

  try {
    fs.readSync(fileDescriptor, buffer, 0, 100, 0);
    return buffer.toString();
  } finally {
    // 確保文件資源被釋放
    fs.closeSync(fileDescriptor);
  }
}

看點 26. 有沒有符合團隊強健度等級

這個東西是看 Teddy 老師寫的《笑談軟體工程:例外處理設計的逆襲》裡面所提到的強健度等級,然後整個分級如下圖 :

https://ithelp.ithome.com.tw/upload/images/20240926/200893581hUpecnOmf.png
圖片來源: 笑談軟體工程:例外處理設計的逆襲

基本上這幾個都算很好理解,就不解釋了。然後我們家比較偏算是等級 1 與等級 2 中間。第級 2 代表的事實上就是我們常見的 transaction rollback 或是 resource cleanup 之類的,但是我們家的程式碼很多 legacy code 層層依賴且層層包,可能一個 function 產的 resource 連線四處用,有沒有 cleanup 或 rollback 都多不知道啊……因為真的很難追啊……

理想上我會想達到等級 3,但是以我們家的情況下有點難度,所以目前也只會選擇特殊的地方來做到等級 3。


小結

在這篇文章中,我探討了軟體工程中錯誤處理設計的幾個重要看點,並對一些常見的問題進行了深入分析。以下是文章中的每一個看點:

  • 看點 21: 不要回傳自定義錯誤碼
  • 看點 22: 有沒有偷偷忽略例外(Ignored Exception)
  • 看點 23: 不要讓太詳細的錯誤訊息曝露給用戶
  • 看點 24: 不要有巢狀 Try 敘述(Nested Try Statement)
  • 看點 25: 有沒有細心的處理資源釋放
  • 看點 26: 有沒有符合團隊強健度等級

認真的推薦 Teddy 老師寫的《笑談軟體工程:例外處理設計的逆襲》,裡面有很多東西讓我受用無窮。


上一篇
Day-11: 實務時 Code Review 看 Class 地方 2 ( 封裝 )
下一篇
Day-13: 契約式設計 ( DBC Design By Contract ) vs 防禦式程式設計( Defensive Programming )
系列文
一個好的系統之好維護基本篇 ( 馬克版 )30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言