dotNET:怎樣處理程序中的異常(理論篇)?

平時在軟件開發的過程中,首先是要保證功能可以正常運行,滿足業務需求,除此之外,還需要考慮代碼在異常的時候怎麼處理,讓程序能夠健壯地運行。正確合理地處理異常可以減少程序的 Bug、保證代碼質量,當然也不是一件很容易的事。

在日常工作中我們排查錯誤時經常會遇到這樣一些問題,如果沒有,說明你做的還不錯了:

  • 想通過日誌的方式分析錯誤原因,發現日誌記錄不完整;
  • 找到錯誤日誌了,記錄的是“未將對象引用設置到對象的實例”,也知道代碼行數,然而這一行上有多個引用類型的對象,還是不知道真實原因;
  • 問題是偶發的,無法重現。

最終需要還原數據庫進行單步調試才能解決問題,然而:

  • 客戶的數據庫涉密,不能提供;
  • 客戶的數據庫運行多年,數據量很大,無法快速備份還原;
  • 如果是互聯網 Saas 應用,更是難於將庫拿到本地進行調試。

所以需要在代碼層面、在日誌層面來進行優化來達到可以快速定位問題的目的。

dotNET 經典錯誤

上面這張圖,經歷過 dotNET Framework 時代的程序員應該都不陌生,這就是經典的「黃頁」和經典的 「未將對象引用設置到對象的實例」錯誤。

首先這個錯誤顯示非常不友好,除了讓人知道這個是 dotNET 開發的,別無他用,另外這個錯誤提示對排查錯誤也沒有幫助,只知道對象爲 null 了,但原因是什麼並不知道,只能猜,能不能猜中就得看運氣了。

正確的錯誤處理思路

一個系統一般有兩類人使用,普通用戶和系統管理員。不管是普通用戶還是系統管理員,在操作系統時都期望所有的操作是有反饋的,要麼正常返回想要的結果,要麼給出友好的錯誤提示,能夠指引進行下一步操作。

當出現異常時,可以導向一個專屬類型的錯誤提示頁面,也可以以模態的方式彈出錯誤提示,內容包含:

  • 錯誤提示,例如:系統異常,請聯繫管理員,撥打 xxx 、保存失敗,請聯繫管理員;
  • 全局錯誤碼,下面會講到;
  • 異常編碼,可以根據此編碼在後臺的日誌記錄快速查詢,異常編碼使用日期加流水號即可,建議不要使用 Guid,曾經被非技術人員當成是亂碼。

如果是系統管理員使用的功能,將真實錯誤原因顯示在錯誤提示中,我認爲也是可以的。

全局錯誤碼

設置全局錯誤碼,可以讓管理員在收到反饋的錯誤時能快速地根據錯誤碼進行問題的定位和找到解決方法。所以需要有公開的全局錯誤碼文檔,記錄錯誤的原因和解決方案參考。

大類上可以分爲 4xx 和 5xx,4xx 表示前端的參數問題、驗證問題等,5xx 表示後端的邏輯問題。

在 5xx 類型中可以再進行細分,例如:

  • 500100:表示數據庫操作相關問題
  • 500200:表示列表展示相關問題
  • 等等

異常處理的一些原則

1、在方法中不要返回錯誤碼,因爲錯誤碼的信息太單一;
2、拋異常時選擇具體的異常類型,不要直接拋出 System.Exception ;
3、錯誤信息目的是爲了讓開發人員可以定位問題和解決問題,而不是給最終用戶看,給前端用戶看的信息要友好易懂;
4、不能吞異常,比如 catch 異常後不做任何處理,如果有些資源需要清理,可以使用 try...finally 或者使用 using ;
5、只有當你知道怎麼樣從異常中恢復時,才需要去捕獲異常,在執行一些操作時,我們可能知道出現錯誤的原因,但無法恢復,這時不要去捕獲異常。

在方法中怎樣處理異常?

一個方法中有三個部分:參數、業務邏輯和返回值

參數

引用類型的參數,在方法的開始一定要做非空判斷,判斷後是拋異常還是繼續下面的邏輯這個要根據具體情況來定:

  • 如果參數爲 null 時會對後續的業務有影響,就應該拋出異常;
  • 如果我們判斷 null 後能做一些初始化處理,能讓程序繼續正常運行,而且保證業務也是正確的,就不必拋異常。

業務邏輯

業務邏輯的部分分爲三種情況:

  • 在方法內部調用其他類型的一個方法,比如 var user= userService.GetUser(); 對 user 的判斷,當爲 null 時是否拋異常,跟上面參數的邏輯一致;
  • 多個邏輯組合到一起進行判斷後,如果不能滿足下一步的輸入,應該拋出異常;
  • 對於更低一層的調用,有時會進行異常的捕獲,當捕獲到異常後,應該要拋出符合當前上下文的專有異常信息,更利於定位問題。

返回值

一個方法的返回值可以返回值類型,如 string、int、bool ,也可以返回引用類型,如返回一個 User 對象,不管是返回什麼類型,原則是一樣的,都需要更具上下文來進行判斷。

有個 GetUser 方法來獲取用戶對象 ,如果根據 Id 沒有找到用戶,可以直接返回 null ,而不是返回一個空的 User 對象,如果返回空對象,程序不會出錯,但前端展示卻沒有數據,就搞不清是沒找到用戶,還是找到了但沒值;返回 null,可以由上層來決定怎麼來處理。

再有個 GetUserList 方法根據條件獲取用戶集合,如果根據搜索條件沒有找到符合的用戶,可以返回空對象 List ,而不是返回 null 。

對於值類型也是一樣,要看上下文,比如 C# 中用來查找字符在一個字符串中的索引位置的函數 IndexOf ,返回的是 int 類型,當找不到的時候返回的是 -1 ,而不是 null 。

最後

好的異常處理可以使我們的程序更加的健壯,也能在出現問題時更好的定位和排查問題,本文的內容偏理論,下一篇以代碼示例的方式來進行演練下。

希望本文對您有所幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章