背景
最近在做公司系統(tǒng)的模塊化重構(gòu)豆励,發(fā)現(xiàn)了大量的函數(shù)和方法會在一定情況下返回一個Null值途戒。雖然我能理解當(dāng)時作者一定是想告訴我們此函數(shù)或方法在一定條件下返回空值哀卫。但是真的使用返回Null的方式來表達返回空值真的合適嗎母市?
知識回顧
先從語意上理解什么是Null够坐?
我從WIKIpedia上選了靠前的萄唇、編程角度上來說關(guān)于Null的解釋檩帐。
Null 是一特殊指針值(或是一種 對象引用)表示這個指針并不指向任何的對象。這樣的指針稱之為 Null指針[1]
--WIKIpedia
計算機專業(yè)出身的同學(xué)應(yīng)該能夠很好理解這段話的意思另萤。意思是說Null的類型是指針(因為它是Null指針)湃密。
在許多定義里,Null 意指 "沒有值" 或是 "未知的值"四敞。
--WIKIpedia
這段話則說明了Null在語意上的模糊性泛源。既可能表示“沒有值”,也可能表示“未知的值”忿危。
為什么不要返回Null
-
語意的角度
先看一段代碼达箍。這段代碼使用了React 16 里面的新特性。我先不說這段代碼的意思铺厨,我們嘗試只看代碼猜一猜這段執(zhí)行這段代碼以后會發(fā)生什么事情缎玫。
const MAX_PIZZAS = 20;
function addAnotherPizza(state, props) {
if (state.pizza === MAX_PIZZAS) {
return null;
}
return {
pizza: state.pizza + 1,
}
}
this.setState(addAnotherPizza);
在上面的代碼里面, 我們可以知道作者定義了一個常數(shù)MAX_PIZZAS,和一個函數(shù) addAnotherPizza解滓,然后執(zhí)行 React.Component的setState方法赃磨。
好,讀方法名字的時候洼裤,我們大概可以猜測到這是一個更新披薩數(shù)量的的方法邻辉。直意過來就是“添加另一個披薩”。直接看這個函數(shù)的返回值,可以知道這個方法返回了一個對象(新的state)恩沛,這個新的對象更新了pizza的數(shù)量(其實這種命名也不好在扰,應(yīng)該表明是numOfPizza)。但是我們也發(fā)現(xiàn)雷客,當(dāng)pizza數(shù)量達到一個上限的時候芒珠,就會返回一個null。
OK搅裙。想象一下你是一個新來這個項目的員工皱卓,你來猜測一下這里返回一個null代表了什么意思。你可能會說部逮,那肯定就是不再添加pizza的數(shù)量咯娜汁。對一半吧。那為什么返回null而不返回空對象呢兄朋?
在揭曉正確答案之前掐禁,首先說一下什么是語意。我理解的語意就是你寫的變量名颅和,函數(shù)名傅事,方法名,類名峡扩,接口名等等有意義蹭越。
在語言學(xué)上,指發(fā)出訊息者想要表示或傳達給發(fā)現(xiàn)者或接收者的理念教届;
--wikipedia
也就是說我們編寫的代碼不能只有機器能夠理解响鹃,以后接我們工作的同學(xué)也能輕易地理解我們的代碼到底想表達什么意義。
把上面的的函數(shù)翻譯成人話的話案训,就是:
A: 再加一個披薩 ( 函數(shù)名: addAnotherPizza)
B: 當(dāng)前不足20個买置,好的,成功加一個披薩
...
A: 再加一個披薩
B:當(dāng)前已經(jīng)夠20個萤衰。那么堕义, null。
你能夠理解嗎脆栋?
回到上面的代碼倦卖。React 16版本里面setState接收null值來明確告訴React不要渲染。所以除了不再添加一個披薩到新的state以外椿争,還有一個意思就是告訴React不要做渲染了怕膛。
至少我作為一個新的代碼維護者的角度來說的話,我不能輕易地秦踪、明確地判斷出當(dāng)addAnotherPizza返回null時褐捻,到底想表達什么掸茅。
-
防范NPE的角度 (NullPointerException)
有JAVA背景的同學(xué),應(yīng)該對于NPE一點也不陌生柠逞。NPE應(yīng)該是伴隨了我們很多的開發(fā)時間昧狮。NPE主要發(fā)生在調(diào)用一個對象的方法時,檢測到應(yīng)該有的對象引用的值是null板壮。
所以為了防范NullPointerException, 我們?yōu)槲覀兊姆椒ê秃瘮?shù)添加了數(shù)不清的null 檢查邏輯逗鸣。使用null值檢查邏輯防范NullPointerException是一個很好的編程習(xí)慣。這可以增強系統(tǒng)的健壯性绰精。
但是撒璧,如果你仔細觀察我們的代碼,可以發(fā)現(xiàn)很多的null檢查是不必要的笨使。特別是對于一些自建系統(tǒng)的接口函數(shù)調(diào)用卿樱。舉個如下的列子:
class Member {
private String phoneNumber;
private String memberId;
public String getPhoneNumber(String memberId){
if ( memberId == this.memberId ) {
return this.phoneNumber;
} else {
return null
}
}
}
相信大家在很多的代碼里面看見過類似的邏輯。即當(dāng)成功時返回某項值硫椰,反之則返回null繁调。
從語意的角度上說的話,你找某個管理員去詢問某個member的電話號碼靶草,得到null的回饋時涉馁,你會怎么想?是去想這個member沒有電話號碼呢爱致?還是沒有這個member,還是這個管理員傻了寒随?
除了語意上的模凌兩可以外糠悯,在一個系統(tǒng)里面返回過多的null,也在一定程度上增加了null的檢查邏輯代碼數(shù)量妻往,其實就是給系統(tǒng)增加了不必要的復(fù)雜性互艾。所以,如果在某些情況下讯泣,比如上述返回電話號碼的情況下纫普,為何不返回一個空字符串呢?
從防范NPE的角度來看好渠,返回空字符串的話昨稼,首先,getPhoneNumber的調(diào)用者是不需要進行null檢查的拳锚。因為你不返回null假栓,那為什么還要進行null檢查?其次霍掺,主動不去返回null還有一個好處匾荆,就是我們作為一個有素養(yǎng)的程序員和工程師拌蜘,主動從系統(tǒng)健壯性的角度防范了NPE的產(chǎn)生,從源頭上保護了NPE對系統(tǒng)帶來的危害牙丽。
不給系統(tǒng)留下任何健壯性危害隱患简卧,是作為一個有素養(yǎng)的程序員應(yīng)該有的覺悟和編程習(xí)慣。作為一個有素養(yǎng)的程序員烤芦,永遠不要指望Debug來讓我們的系統(tǒng)健壯举娩,而是時時刻刻地去主動考慮如何才能保護我們的系統(tǒng)不因為我們技術(shù)的原因而崩塌。
-
面向?qū)ο缶幊痰慕嵌?/h3>
在從面相對象編程的角度來展開討論之前拍棕,我們有必要知道什么是面相對象編程晓铆。
Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which may contain data, in the form of fields, often known as attributes; and code, in the form of procedures, often known as methods.
--wikipedia
說白一點就是在系統(tǒng)里一切皆對象,對象包含了一切必要的屬性和屬性操作方法绰播。
那么我們的系統(tǒng)就是由對象和對象的關(guān)系構(gòu)成的骄噪。在上面的知識回顧當(dāng)中,我們可以知道null是一個指針類型的值蠢箩。表示該指針不指向任何的對象链蕊。從一個系統(tǒng)抽象的角度來看,指針類型屬于比較底層的類型谬泌。那么在我們的很多方法當(dāng)中去返回一個底層的類型值是否合適呢滔韵?
舉個簡單的列子,我們?nèi)耸怯杉毎M成器官然后再組成我們?nèi)诉@個類型的掌实。如果每一個細胞代表了一個實例陪蜻,那么每個實例所在的位置則由指針的值保存(實例內(nèi)存地址)。我們?nèi)诉@個系統(tǒng)中贱鼻,細胞和細胞有關(guān)系宴卖,器官與器官有關(guān)系,器官與細胞有關(guān)系邻悬。他們互相有數(shù)據(jù)的來往症昏,有數(shù)據(jù)的處理,但是唯獨沒有的是父丰,當(dāng)某個器官想查詢某個細胞狀態(tài)時肝谭,相應(yīng)器官返回一個空位置(null)的指針。因為這樣既沒有意義蛾扇,也沒有任何人體內(nèi)部器官會去做這個事情攘烛。
人體是一個非常健壯和復(fù)雜的系統(tǒng),我們可以通過觀察我們?nèi)梭w自身的系統(tǒng)來獲取編程的靈感镀首。
null在我們的編程過程中医寿,很多時候被誤用為表明想要查找的對象不存在。如果這么想的話蘑斧,只能說明你還離OOP還有一段距離靖秩。因為你還在用機器的思維在思考編寫代碼须眷。如果嚴(yán)格按照OOP來思考如何構(gòu)建我們系統(tǒng)的話,我們就會有更好的方法去表明一個不存在的對象(Clean Code: Special Case Pattern)沟突。
業(yè)界相關(guān)討論
以上都是我自己對返回null值的思考和總結(jié)花颗。在業(yè)界上,早就有相關(guān)是否返回null值有激烈的討論惠拭。沒有討論出業(yè)界一致公認的標(biāo)準(zhǔn)扩劝。
在Stackoverflow上關(guān)于是否返回null的討論: Is returning null bad design?
Null之父Tony Hoare關(guān)于Null值的文章: Null References: The billion dollar mistakes
Clean Code 作者 Robert C. Martin 關(guān)于是否返回null值的看法是 “Avoid returning null”.
其他一些作者關(guān)于是否返回null的一些討論: Why NULL is Bad?
總結(jié)
本文認為Null值不應(yīng)該由程序員在方法中返回,因為這樣做职辅,一是違背了方法名的語意棒呛。二是由于我們返回null值,更加增加了系統(tǒng)的不穩(wěn)定性域携;因為在調(diào)用者忘記做null檢查時簇秒,就一定會因為我們的方法出錯。最后秀鞭,null值所充斥的系統(tǒng)模型違反了OOP原則趋观。
所以,本文認為锋边,在我們構(gòu)建的系統(tǒng)中皱坛,不應(yīng)該有返回null值的方法和函數(shù)存在。