null?對象?異常?到底應該如何返回錯誤信息


這篇文章記錄我的一些思考。在工作了一段時間之后。

問題的核心很簡單:到底如何返回錯誤信息。

學生時代,見到過當時的老師的代碼:

1 if (foo() == null) {
2 
3 }

 

當然,這位老師是一位比較擅長c/c++的老程序員,所以他的代碼其實使用c寫的。但是意思和這段代碼類似。當時,我很好奇為什么要對一個方法的返回值是不是null進行判斷。現在當然很清楚了:在很多win32的API里面,是通過返回值為null來傳遞“函數調用失敗”這一種信息的。

那么,這么做好嗎?

我翻看了很多的博客,大致上說這種寫法不好的占多數。最重要的理由如下:

  • 無法展示更詳細的錯誤信息
  • 容易讓調用者忽略

這種說法基本還是有道理的。因為返回值為null,確實很明顯得存在這兩種問題。

對於第一點,其比較關鍵的影響就是,既然不知道更多信息,也就無法在日志中體現。從而不能針對可能出現的多種異常進行不同的日志打印和處理。曾經也發生過因為缺失了日志打印,導致花很多時間來定位一個問題的情況。尤其是線上環境不比本地隨意debug,一個簡單的問題經過幾次這種“信息丟失”之后,就有可能成為一個“疑案”。

而第二點,更是嚴重影響系統安全性、穩定性。人都有不小心的時候,如果忘了對某個接口的返回值進行校驗,就會導致代碼邏輯是按照“這件事已經發生並且正確發生”寫的,但是實際執行過程中發生了錯誤。一方面比較難排查,第二方面是可能導致一個問題在另外的地方爆發出來。

 

那么是不是絕對不能這么做呢?其實也不盡然。簡單來說,如果調用方在系統內部、代碼執行邏輯可控、在返回null之前有正確的日志打印、調用方對這個異常確實沒有辦法處理。滿足這幾個條件就可以使用返回null。但是還是具有危險性。

 

這個問題的改進方案是返回一個對象。比如寫一個Result對象。

 1 public class Result {
 2 
 3     private Integer code;
 4 
 5     private String message;
 6 
 7     private Object data;
 8     
 9     // getter and setter
10 
11 }

 

這樣就有一個優化:能夠返回錯誤的信息和一些數據。本次方法成功了嗎?如果失敗需要打印什么信息?如果成功,是不是需要一些內容?

 

但是這種寫法也有問題。

最主要的是其適用性問題。就是說如果很多方法使用同一個Result,能保證都能通過這種方式返回其需要的數據嗎?一些特別的方法返回的東西不能夠使用一個Object進行轉化。(或者說這樣做代價不小)

其次,很多代碼業務邏輯極其簡單,如果強行使用結果類進行封裝,反倒有畫蛇添足之嫌。

最后,如果要嚴格使用這個類,代碼開發的代價很大。

那么這種做法適合什么場景呢?一句話:對外接口。一方面我們要按照一個統一的規則去返回信息(正確或者錯誤)。第二方面,很難要求調用方去捕獲我們的異常。如果他們忘了捕獲,是不是就有可能會造成更嚴重的情況?所以,這種情況下,我們需要告知調用方失敗信息。但是又不能要求對方做出多少改變。

 

 

最后一種解決方案是拋異常。

 Effective Java里面用一句話解釋什么時候需要拋異常,什么時候不需要。“當你對異常什么也做不了的時候,就不要拋異常”(大意,有可能記錯了)。

 這句話是很有道理的。因為如果你顯式地拋出異常,那么調用方就需要去捕獲。如果要求對方捕獲,可是卻對這種異常什么也做不了。那么拋異常是不是就不那么有意義了?

這個地方的關注焦點在於,調用方如何處理這個異常?假如說,調用方只是打印日志,算不算這個異常有用?在最開始的時候,我曾經認為這種情況可以認定為這個異常對調用方沒有幫助。而現在我的想法產生了變化。簡單來說和具體業務有關。

再進一步來說:如果調用方在得到這個錯誤信息之后,代碼邏輯會改變,就適合拋異常。而如果調用方即使得知了錯誤信息,也不需要改變代碼運行邏輯,就可以更簡化得處理。比如這個接口是刪除數據。那么在失敗的情況下,就必須通過拋異常或者別的方式,告知調用方數據刪除失敗。否則調用方的代碼可能會忽視數據刪除失敗。這里就不適合返回null,因為返回null的檢查不是強制性的,調用方如果沒有進行判斷,就可能導致后面的邏輯完全錯誤。

第二種情況就在於對一些緩存的查詢。如果失敗,或者沒查到,拋異常就不再是一個必須的選項了。當然,如果拋異常,能讓調用方更清晰地了解為啥沒查到緩存,是網絡問題,還是參數不正確,或者確實沒有這個緩存。這里的重點在於,調用方即使知道了這些信息,恐怕也無法做更多的事,即使知道了是網絡問題,也很難通過代碼解決。而對於打印日志,這種“沒查到緩存”的情況是否有必須在調用端強調其原因,就看具體的業務了。不過我個人覺得,這里的日志打印交給服務提供方比較好,其一是提供方肯定比調用方知道的信息更全。其二是,提供方本來就有責任去維護系統的穩定性。所以對於日志,能做更多。

除了這些理由之外,還有一個點。就是在項目結構的關鍵層級,是否需要補貨所有異常。比如controller層,如果拋出異常,就會直接給用戶造成嚴重影響。當然如果每個函數都簡單地try catch住所有異常,則也是不負責任的,這樣不利於排查問題。

 

總結起來,是否拋異常,還是通過什么方式返回錯誤信息。主要取決於:

  • 調用方拿到錯誤信息能做什么
  • 業務上調用方是否需要獲取具體的錯誤信息
  • 是否有必要捕獲所有異常

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM