为什么NULL是错误的?
Java中NULL用法的簡單示例:
這種方法有什么問題?
它可能返回NULL而不是對象-這是錯誤的。 在面向?qū)ο蟮姆独?#xff0c; NULL是一種可怕的做法,應(yīng)不惜一切代價避免使用NULL 。 關(guān)于此出版物已經(jīng)有很多意見,包括“ 空引用”, Tony Hoare撰寫的《十億美元的錯誤》演講和David West撰寫的整本《 Object Thinking 》。
在這里,我將嘗試總結(jié)所有參數(shù),并舉例說明如何避免使用NULL并使用適當(dāng)?shù)拿嫦驅(qū)ο蟮慕Y(jié)構(gòu)代替它們。
基本上,可以使用NULL兩種替代方法。
第一個是Null Object設(shè)計模式(最好的方法是使其成為常數(shù)):
public Employee getByName(String name) {int id = database.find(name);if (id == 0) {return Employee.NOBODY;}return Employee(id); }第二種可能的替代方法是在無法返回對象時拋出異常 ,從而快速失敗 :
public Employee getByName(String name) {int id = database.find(name);if (id == 0) {throw new EmployeeNotFoundException(name);}return Employee(id); }現(xiàn)在,讓我們看看反對NULL的參數(shù)。
除了上面提到的Tony Hoare的演示文稿和David West的書以外,我在寫這篇文章之前還閱讀了這些出版物:Robert Martin的Clean Code ,Steve McConnell的Code Complete ,John Sonmez的“ No”到“ Null” , 是否返回?zé)o效的不良設(shè)計? StackOverflow上的討論。
臨時錯誤處理
每次獲取對象作為輸入時,都必須檢查它是否為NULL或有效的對象引用。 如果忘記檢查,則NullPointerException (NPE)可能會在運行時中斷執(zhí)行。 因此,您的邏輯將受到多次檢查以及if / then / else分支的污染:
// this is a terrible design, don't reuse Employee employee = dept.getByName("Jeffrey"); if (employee == null) {System.out.println("can't find an employee");System.exit(-1); } else {employee.transferTo(dept2); }這就是應(yīng)該用C和其他命令式程序語言處理異常情況的方式。 OOP引入了異常處理,主要是為了擺脫這些臨時的錯誤處理塊。 在OOP中,我們讓異常冒泡直到它們到達(dá)應(yīng)用程序范圍的錯誤處理程序,并且我們的代碼變得更加簡潔明了:
dept.getByName("Jeffrey").transferTo(dept2);考慮NULL引用是過程編程的繼承,請改用1)Null對象或2)異常。
模棱兩可的語義
為了明確傳達(dá)其含義,必須將函數(shù)getByName()命名為getByNameOrNullIfNotFound() 。 每個返回對象或NULL函數(shù)都應(yīng)該發(fā)生同樣的情況。 否則,對于代碼閱讀器來說,模棱兩可是不可避免的。 因此,為了保持語義的明確性,應(yīng)為函數(shù)指定更長的名稱。
要擺脫這種歧義,請始終返回真實對象,空對象或引發(fā)異常。
有人可能會爭辯說,為了性能起見,我們有時必須返回NULL 。 例如,當(dāng)?shù)貓D中沒有這樣的項目時,Java中接口Map get()方法將返回NULL :
Employee employee = employees.get("Jeffrey"); if (employee == null) {throw new EmployeeNotFoundException(); } return employee;由于Map使用NULL ,因此此代碼僅搜索一次Map 。 如果我們將Map重構(gòu),以便其方法get()在未找到任何內(nèi)容的情況下將引發(fā)異常,則我們的代碼將如下所示:
if (!employees.containsKey("Jeffrey")) { // first searchthrow new EmployeeNotFoundException(); } return employees.get("Jeffrey"); // second search顯然,此方法的速度是第一個方法的兩倍。 該怎么辦?
Map界面(對其作者沒有冒犯)具有設(shè)計缺陷。 它的方法get()應(yīng)該一直返回一個Iterator以便我們的代碼如下所示:
Iterator found = Map.search("Jeffrey"); if (!found.hasNext()) {throw new EmployeeNotFoundException(); } return found.next();順便說一句,這正是C ++ STL map :: find()方法的設(shè)計方式。
計算機(jī)思維與對象思維
有人知道Java中的對象是指向數(shù)據(jù)結(jié)構(gòu)的指針,而NULL是指向0x00000000的指針(在Intel x86處理器中為0x00000000 if (employee == null)可以理解if (employee == null)語句。
但是,如果您開始以對象為對象進(jìn)行思考,那么這種說法就沒有意義了。 這是從對象角度看我們的代碼的樣子:
- Hello, is it a software department? - Yes. - Let me talk to your employee "Jeffrey" please. - Hold the line please... - Hello. - Are you NULL?對話中的最后一個問題聽起來很奇怪,不是嗎?
相反,如果他們在我們請求與Jeffrey通話后掛斷電話,這會給我們造成麻煩(異常)。 此時,我們嘗試再次致電或通知主管,我們無法聯(lián)系Jeffrey并完成更大的交易。
或者,他們可以讓我們與不是Jeffrey的其他人交談,但是可以幫助我們解決大多數(shù)問題,或者在我們需要“特定于Jeffrey”的東西時拒絕幫助(空對象)。
緩慢失敗
上面的代碼沒有快速失敗 ,而是嘗試緩慢消失,從而殺死了其他人。 它沒有讓所有人都知道出了什么問題并且應(yīng)該立即開始異常處理,而是向客戶端隱藏了此故障。
該參數(shù)與上面討論的“臨時錯誤處理”非常接近。
最好使代碼盡可能脆弱,并在必要時讓代碼中斷。
使您的方法對它們操作的數(shù)據(jù)極為苛刻。 如果提供的數(shù)據(jù)不足或根本不適合該方法的主要使用場景,請讓他們通過引發(fā)異常來進(jìn)行抱怨。
否則,返回一個Null對象,該對象暴露一些常見行為,并在所有其他調(diào)用上引發(fā)異常:
public Employee getByName(String name) {int id = database.find(name);Employee employee;if (id == 0) {employee = new Employee() {@Overridepublic String name() {return "anonymous";}@Overridepublic void transferTo(Department dept) {throw new AnonymousEmployeeException("I can't be transferred, I'm anonymous");}};} else {employee = Employee(id);}return employee; }可變和不完整的對象
通常, 強烈建議設(shè)計時牢記不變性的對象。 這意味著對象在實例化過程中會獲得所有必需的知識,并且在整個生命周期中都不會改變其狀態(tài)。
通常,在延遲加載中使用NULL值,以使對象不完整且可變。 例如:
public class Department {private Employee found = null;public synchronized Employee manager() {if (this.found == null) {this.found = new Employee("Jeffrey");}return this.found;} }該技術(shù)盡管被廣泛使用,但卻是OOP中的反模式。 主要是因為它使對象負(fù)責(zé)計算平臺的性能問題,而這是Employee對象不應(yīng)該意識到的。
對象不必管理狀態(tài)并公開其與業(yè)務(wù)相關(guān)的行為,而必須處理其自身結(jié)果的緩存-這就是延遲加載的意義所在。
緩存不是員工在辦公室里做的事情,對嗎?
解決方案? 請勿以上述原始方式使用延遲加載。 相反,請將此緩存問題移至應(yīng)用程序的另一層。
例如,在Java中,您可以使用面向方面的編程方面。 例如, jcabi-aspects具有@Cacheable批注,用于緩存方法返回的值:
import com.jcabi.aspects.Cacheable; public class Department {@Cacheable(forever = true)public Employee manager() {return new Employee("Jacky Brown");} }我希望這種分析令人信服,您將停止NULL您的代碼!
相關(guān)文章
您可能還會發(fā)現(xiàn)以下有趣的帖子:
- Java代碼中的典型錯誤
- 實用程序類的OOP替代
- 避免字符串串聯(lián)
- 對象應(yīng)該是不可變的
翻譯自: https://www.javacodegeeks.com/2014/09/why-null-is-bad.html
總結(jié)
以上是生活随笔為你收集整理的为什么NULL是错误的?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 没有IF-ELSE的工厂
- 下一篇: 港币符号和美元符号的区别