如何避免空指標出錯?
一家專門幫助開發人員瞭解生產中發生問題的以色列公司OverOps,對生產過程中出現的最重要的java異常進行了研究。猜猜哪一個處於第一個?空指標異常。並不是因為開發人員忘記新增空值檢查,而是因為開發人員過多使用空值。
所以這些NULL來自何處?
在C#和Java中,所有引用型別都可以指向null。我們可以透過以下方式獲得指向null的引用:
- “未初始化”的引用型別變數 - 使用空值初始化並隨後賦予其實際值的變數。錯誤可能導致它們永遠不會被重新分配。
- 未初始化的引用型別類成員。
- 顯式賦值為null或從函式返回null
以下是我注意到函式返回null的一些模式:
1. 錯誤處理
輸入無效時返回null。這是返回錯誤程式碼的類似方法。我認為這是一種舊式的程式設計風格,起源於不存在例外的時候。
2.實體的可選資料
實體的屬性可以是可選的。如果沒有可選屬性的資料,則返回null。
public class Person { public string FirstName { get; set; } // can return null public string MiddleName { get; set; } public string LastName { get; set; } } |
2.分層模型
在分層模型中,我們通常可以上下導航。當我們處於頂端時,我們需要一種方式來表達,通常是返回null。
interface IEmployee { string FullName { get; } string IdNumber { get; } ... // returns null when the IEmployee is the CEO, because CEO don't have a manager IEmployee Manager { get; } IEnumerable<IEmployee> ManagerOf{ get; } } interface ITreeNode<T> { // returns null when the node is the root ITreeNode<T> Parent { get; } IEnumerable<ITreeNode<T>> Children{ get; } T Data { get; } } |
3.查詢功能
當我們想要透過集合中的某些條件查詢實體時,我們返回null作為表示未找到實體的方式。
當我們想要根據特定條件在集合中找到一個實體時,我們返回null作為一種說法找不到該實體的方式
class Employe { string Id { get; set;} string Name {get; set;} ... } class Company { // returns null if there is no employe with that id public Employe FindEmployeById(string id) { ... } } |
使用空值有什麼問題?
引發NullPointerException的程式碼可能離fix bug很遠。它使得追蹤真正的問題更加困難。特別是如果程式碼是分支的。
在下面的程式碼示例中,有一個錯誤,在A類的某個地方,導致實體為null。但是NullPointerException是在類B的函式內引發的。可以想象現實程式碼可能要複雜得多。
class A { void DoSomething1() { Entity1 entity = null; if (...) { if (...) { entity = CreateEntity(1); } } else { if (...) { entity = CreateEntity(2); } } DoSomething2(entity); } void DoSomething2(Entity1 entity) { if(...) { new B().DoSomething(entity); } } } class B { void DoSomething(Entity1 entity) { if (...) { var x = entity.Prop1; } } } |
第二:會隱藏了錯誤:我遇到了空檢查,看起來開發人員正在思考“我知道我應該檢查null但我不知道當函式返回null時它意味著什麼,我不知道該怎麼做”或“我認為這不能為空,但只是為了確保,我不希望它破壞生產“。它通常看起來像這樣:
public void registerItem(Item item) { if (item != null) { ItemRegistry registry = peristentStore.getItemRegistry(); if (registry != null) { Item existing = registry.getItem(item.getID()); if (existing.getBillingPeriod().hasRetailOwner()) { existing.register(item); } } } } |
這種型別的空檢查會導致某些邏輯在沒有了解它的情況下不會發生。編寫這種程式碼意味著流的某些邏輯失敗但整個流程成功。它還可能導致某些其他功能中的錯誤,這些功能假定其他功能完成了它的工作。
想象一下,你在網上購買一張演出門票。你有成功的訊息!節目的最後一天到了,你早點下班,安排一個保姆,然後去看節目。當你到達時發現你沒有門票!而且沒有空座位。你回家困難和困惑。你能看到這種空值檢查會如何導致這種情況嗎?
缺少C#和Java中的非Nullable引用型別
在C#和Java中,引用型別始終指向null。如果null是它的有效輸入或輸出,透過檢視函式方法簽名我們是無法瞭解情況的,我相信大多數函式不會返回null或接受null作為入參。
因為很難知道函式是否返回null(除非有記錄),開發人員要麼在不需要時進行空檢查,要麼在需要時不檢查空值(是的,有時在需要時進行空檢查。
這種糟糕的設計選擇導致我之前在“隱藏錯誤”中描述的問題和當然很多NullPointerException。失敗的情況。
像Kotlin這樣的語言旨在透過區分可空引用和不可空引用來消除NullPointerException異常。這允許捕獲分配給非空引用的null,並確保開發人員在解除引用可引用引用之前檢查null,所有這些都在編譯時就會實現。
Microsoft透過在C#8中引入Nullable引用型別採用相同的方法。
那我們該怎麼辦?聽取鮑勃叔叔的意見
眾所周知的“鮑勃叔叔” 羅伯特·C·馬丁寫了一本關於清潔程式碼的最著名的書籍(令人驚訝的是)“清潔程式碼”。在Uncle Bob聲稱的這本書中,我們不應該返回null,也不應該將null傳遞給函式。
消除試驗Null的技術模式
我並不是說這是每個場景的最佳解決方案。
1. 將功能拆分為兩個
返回null的每個函式都將轉換為2個函式。一個具有相同簽名的函式丟擲異常而不是返回null。第二個函式返回一個布林值,表示它是否有效以呼叫第一個函式。我們來看一個例子:
class Employe { string Id { get; set;} string Name {get; set;} ... } class Company { public bool ContainsEmployeById(string id) { ... } |
public Employe FindEmployeById(string id) { ... } } void PayEmploye(HttpRequest httpRequest,Company company) { string id = GetID(httpRequest) if (company.ContainsEmployeById(id)) { var employe = company.FindEmployeById(id); PayEmploye(employe); } else { ResponseWithError(String.Format("employe with id {0} don't exist" , id)); } } |
ContainsEmployeById的邏輯基本上與FindEmployeById相同,但不返回員工。這裡會遇到DB效能問題。讓我們介紹一個類似但不同的模式:返回true時的布林函式也會返回我們搜尋的資料。它看起來像這樣:
class Company { |
public Employe FindEmployeById(string id) { ... } |
public bool TryFindEmployeById(string id , ref Employe employe) { } } void PayEmploye(HttpRequest httpRequest,Company company) { string id = GetID(httpRequest); Employe employe; if (company.FindEmployeById(id , ref employe)) { PayEmploye(employe); } else { ResponseWithError(String.Format("employe with id {0} don't exist" , id)); } } |
這種模式的一個常見用途是int.Parse和int.TryParse。
事實上,我可以將一個函式分成兩個函式,每個函式都有自己的用法,這表明返回null是有違反單一責任原則的程式碼氣味。
拆分介面
我們可以從Liskov原則推匯出的實用指南是類必須實現它實現的介面的所有功能。返回null或丟擲異常是不實現函式的方法。因此,返回null是違反Liskov原則的程式碼氣味。
如果一個類不能實現特定介面的函式,我們可以將該函式移動到另一個介面,每個類只實現它可以的介面。
相關文章
- Java中如何避免空指標異常Java指標
- 如何避免Java程式碼中的空指標錯誤NullPointerException? - foojayJava指標NullException
- 使用Java 8 Optional避免空指標異常Java指標
- easyexcel字型空指標錯誤Excel指標
- [轉載] java避免空指標異常_第1部分:在現代Java應用程式中避免空指標異常Java指標
- 野指標 空指標指標
- 防止空指標指標
- Java中如何處理空指標異常Java指標
- 聊一聊日常開發中如何優雅的避免那無處不在的空指標異常指標
- 又見懸空指標指標
- GO 空指標和nilGo指標
- Java8的Optional:如何幹掉空指標?Java指標
- java空指標出現的情況:拆箱裝箱Java指標
- 8.空指標異常指標
- 使用Spring 的 Null-Safety免受空指標錯誤SpringNull指標
- IDEA多執行緒下空指標斷點除錯Idea執行緒指標斷點除錯
- C指標錯誤指標
- Activity基類實現儲存Bundle資料,避免空指標及重複勞動指標
- Java 14版本中將加入發現空指標錯誤提示功能Java指標
- 我還以為空指標錯誤是我安裝不來...指標
- NullPointerException空指標異常的理解NullException指標
- 如何理解指向指標的指標?指標
- 為什麼回覆貼子的時候又出現“空指標”錯誤指標
- springboot整合redis,redisTemplate 空指標Spring BootRedis指標
- SpringDateJpa使用JpaRepository方法出現空指標異常的問題Spring指標
- 通達信莊家誘空洗盤出擊指標公式原始碼指標公式原始碼
- Spring中new出一個物件導致的空指標異常Spring物件指標
- 【MybatisPlus】資料庫的datetime型別欄位為空的時候,報錯空指標?MyBatis資料庫型別指標
- 空指標漏洞防護技術(提高篇)指標
- NULL 指標、零指標、野指標Null指標
- RecyclerView.addFocusables出現空指標異常NullPointerException的解決辦法View指標NullException
- 避免Java堆空間錯誤的5個步驟Java
- 如何正確理解「指標」和「標籤」指標
- 關於sizeof,對空指標sizeof(*p)可以嗎?指標
- Kafka SimpleStringSchema 可能會造成空指標異常Kafka指標
- java.lang.NullPointerException 空指標異常問題JavaNullException指標
- java 空指標異常造成的原因有哪些Java指標
- 空指標漏洞防護技術(初級篇)指標