切換語言為:簡體
令程式設計師頭疼的NPE問題歷史,以及如何優雅的處理空指標問題

令程式設計師頭疼的NPE問題歷史,以及如何優雅的處理空指標問題

  • 爱糖宝
  • 2024-05-29
  • 2110
  • 0
  • 0

下面這個報錯,相信沒有任何一個 Java 程式設計師沒有被它折磨過。我們對他的熟悉程度簡直超過了 Hello World。

令程式設計師頭疼的NPE問題歷史,以及如何優雅的處理空指標問題

何止是熟悉,那簡直是深惡痛絕,以至於我對它都產生了後遺症。

每當本地除錯出現這個錯誤的時候,都恨不得掐一下大腿,然後默默的對自己說:垃圾,還犯這麼愚蠢的錯誤呢?

不知道有多少同學和我一樣有這種感受呢?

回想起我之前接手的一個專案,線上出現了問題,當我到了伺服器一看日誌,只有幾個單詞,那就是 java.lang.NullPointerException,那一刻我是頭暈目眩,差點一頭撞在 27 寸的顯示器上。回想上一次出現這種症狀,還是幾年前擠早高峰的公交車,擠的我雙腳離地,外加有點低血糖。

當然主要問題並不是 NPE(NullPointerException),還是要仰仗前輩異常處理的“非常優秀”,異常包裹的嚴嚴實實的,只留了java.lang.NullPointerException這一點點資訊。

於是只能開啟程式碼,找到報錯的介面,一步步排查,滿眼看去,皆可空指標啊。從此之後,空指標異常給我留下了深深的陰影。

好在從 JDK 14之後,NPE 異常不再僅僅是簡單的這幾個單詞了,而會附帶更加具體的異常資訊,比如對一個賦值為 null 的字串求長度,能捕捉到下面這樣的異常資訊:

Cannot invoke "String.length()" because "s" is null

空指標的由來

要說空指標異常,那還不只是 Java 的問題,絕大多數語言都有這個問題,比如 C++、C#、Go,但是也有沒有這個問題,比如 Rust 。

空指標最早是程式設計界的鼻祖級人物 Tony Hoare 引入的,早在 1965年,他設計 ALGOL 60 語言的時候引入了Null 的設計,ALGOL 可謂是 C 語言的祖宗。ALGOL 中的 Null 被後來的眾多語言設計者引入,就包括前面提到的這些語言。

Tony Hoare 不僅發明了我們熟悉的 Null,還是令眾多演算法殘廢聞風喪膽的快速排序演算法(Quick Sort)的發明者,這個演算法也是當前世界上使用最廣泛的演算法之一。

令程式設計師頭疼的NPE問題歷史,以及如何優雅的處理空指標問題

空指標後遺症.png

2009年3月他在Qcon技術會議上發表了題為「Null引用:代價十億美元的錯誤」的演講,回憶自己1965年設計第一個全面的型別系統時,未能抵禦住誘惑,加入了Null引用,僅僅是因為實現起來非常容易。它後來成為許多程式設計語言的標準特性,導致了數不清的錯誤、漏洞和系統崩潰,可能在之後40年中造成了十億美元的損失

如何應對空指標

處理空指標有一些措施,我們常常稱之為「防禦式程式設計」,這個說法也很形象,你不防著它,它真的就上來傷害你。

1、主動檢查空指標,要使用一個變數之前,要檢查這個變數是不是空,不是空再操作,比如常用的對字串判空。

public static boolean isEmpty(CharSequence cs) {
 return cs == null || cs.length() == 0;
}

對應的很多字串工具類都有 isEmpty、isNotEmpty、isNotBlank 這種方法。

令程式設計師頭疼的NPE問題歷史,以及如何優雅的處理空指標問題

同樣的,還有對於集合的判斷,好多工具包都有 CollectionUtil.isEmpty這樣的方法。

令程式設計師頭疼的NPE問題歷史,以及如何優雅的處理空指標問題

爲了避免空引用異常,有時候我們寫的程式碼可能想下面這個樣子,一步一判空。這樣可以提高程式碼的健壯性和可靠性,但是看上去並不是很美觀。

public static String getUserOrderDetail(Integer userId) {
 User user = User.getUser(userId);
 if (user != null) {
  Order order = user.getOrder();
  if (order != null) {
   Address address = order.getAddress();
   if (address != null) {
    String detail = address.getDetail();
    if (detail != null) {
     return detail;
    }
   }
  }
 }
 return "不好意思,找了半天,沒找到";
}

還好,Java 8 中引入的 Optional 類可以簡化這個流程。

public static String getUserOrderDetail(Integer userId) {
 return Optional.ofNullable(User.getUser(userId))
   .map(User::getOrder)
   .map(Order::getAddress)
   .map(Address::getDetail)
   .orElse("不好意思,找了半天,沒找到");
}

2、 能不返回 NULL 的話,就儘量不返回 NULL

比如有些獲取集合的方法,沒有結果的話,可以返回一個空列表。這種方式對於提供給前端或者消費者使用的介面更加適用,返回一個空集合要遠比返回一個空更友好。

3、 能拋異常的話,寧可拋異常,也不要返回 NULL

還有一些情況,丟擲給呼叫者一個具體的異常,要比返回一個 NULL 更加能讓呼叫者清楚到底發生了什麼。

比如根據一個使用者的資訊,但是發現使用者不存在了,直接返回給呼叫者一個「使用者不存在」的異常資訊更明確,而不是返回一個 NULL,讓呼叫方去猜。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.