讓我們來看一段有問題的代碼:
handler.sendMessageDelayed(msg, -1);
這個方法的作用是延遲一段時間之后姻灶,發(fā)送一條消息癌蚁。其中第一個參數(shù)msg是要發(fā)送的消息,第二個參數(shù)是延時的時間算行,單位是ms,按理來講這個延時的時間必須大于等于0苫耸。但是萬一其他人在調(diào)用的時候?qū)⑦@個參數(shù)傳進去一個負數(shù)州邢,比如-1,如果是你褪子,你會怎么處理這種情況量淌?
我們的代碼在運行的過程當中骗村,難免會出現(xiàn)一些錯誤,有開發(fā)人員人為導致的問題呀枢,比如除數(shù)為0叙身,傳進來一個空指針,或者某個人的年齡被設(shè)置為了負數(shù)硫狞,有些是外部原因信轿,比如正在讀取某個文件的時候,這個文件被刪了残吩,或者服務器給了你一個不存在的鏈接财忽。
采取哪種錯誤處理方式,取決于錯誤的產(chǎn)生原因和嚴重程度泣侮。
采用默認值
用我們剛才的例子來舉例即彪,handler.sendMessageDelayed的第二個參數(shù)不能為負數(shù),但是如果為負數(shù)活尊,源碼當中是這么處理的:
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
把小于0的輸入隶校,都當做0來處理。這是一種非常實用的處理方式蛹锰。我們平時就經(jīng)常用到這種方式深胳,只是可能沒有留意而已,比如SharedPreferences:
sharedPreferences.getString("username", "");
在獲取數(shù)據(jù)的時候铜犬,如果失敗舞终,則返回一個默認值,而不是null癣猾,會使得你的程序更加健壯并且更加優(yōu)雅敛劝。所以不要把第二個參數(shù)設(shè)置為null,因為如果設(shè)置為null的話纷宇,接下來又要有一個是否為null的判斷夸盟,白費了這個方法的設(shè)計者這樣設(shè)計的苦心。
斷言
Java提供了斷言機制像捶,采用assert關(guān)鍵字上陕。
assert s != null;
斷言就像是“活的”注釋,它告訴我們程序運行到這個地方的時候作岖,應該處于什么樣的狀態(tài)唆垃,或是滿足什么樣的條件。
需要特別說明一下痘儡,如果你在Android Studio中使用assert關(guān)鍵字辕万,IDE會建議你這樣使用:
if (BuildConfig.DEBUG && s == null) throw new AssertionError();
因為用assert關(guān)鍵字來斷言有個缺陷,就是無論你是在Debug版本還是Release版本當中,這些斷言都無可避免的會被執(zhí)行渐尿,而如果我們采用了上面的寫法醉途,在Release版本中,這些斷言就不會存在了砖茸。
此外隘擎,Google的Guava庫當中,也有一些用來起到類似斷言功能的方法凉夯,推薦大家使用货葬。
Preconditions.checkNotNull();
Preconditions.checkArgument();
Preconditions.checkElementIndex();
Preconditions.checkState();
Preconditions.checkPositionIndex();
Preconditions.checkPositionIndexes();
運行時異常
在這里,有一種情況需要特別注意一下:
Thread thread = new Thread();
thread.start();
thread.start();
我們都知道劲够,一個線程是不能啟動兩次的震桶,如果開發(fā)人員啟動了兩次,這種錯誤該如何處理征绎?
程序員在編程過程當中人為導致的錯誤蹲姐,所以應該拋出運行時異常。實際上android源碼當中是這樣處理這個問題的:
public synchronized void start() {
if (hasBeenStarted) {
throw new IllegalThreadStateException("Thread already started");
}
...
}
針對這類問題人柿,對應的解決辦法應該是避免多次啟動柴墩。
所有的運行時異常都應該從根源上去避免這個錯誤,而不是用try-catch代碼塊來捕獲這個異常凫岖。
忽略
有人可能會這么處理剛才線程被啟動兩次的問題:
public synchronized void start() {
if (hasBeenStarted) {
return;
}
...
}
如果這個線程已經(jīng)被啟動江咳,則返回,什么都不做隘截。這種做法有一定的缺陷扎阶,它在一定程度上放任了開發(fā)人員犯錯誤,同時它也有好處婶芭,我們來看另外一個例子:
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
...
}
FileOutputStream的close方法就是這么做的,它忽略了多次調(diào)用着饥。如果有兩種情況下犀农,都需要關(guān)閉流,那么這樣可以省掉一些繁瑣的判斷宰掉。建議在線程啟動呵哨、文件流打開的時候要嚴格限制只允許調(diào)用一次,而停止或者關(guān)閉的操作允許多次調(diào)用轨奄,因為一般停止和關(guān)閉這類操作都不止一處需要調(diào)用孟害,至少有正常關(guān)閉和程序執(zhí)行過程中遇到異常關(guān)閉兩種,所以在關(guān)閉的時候不要限制那么嚴格也是有好處的挪拟。
非運行時異常
人們對非運行時異常褒貶不一挨务,大家爭論的地方主要在于非運行時異常必須得處理,而且它讓代碼變得很難看。一行代碼變成了四行之外谎柄,還多了一層縮進丁侄。結(jié)果就是哪里出現(xiàn)非運行時異常,哪里的代碼就會丑爆了朝巫。那么何時該使用非運行時異常鸿摇,一般來說,非運行時異常都是可以恢復的劈猿。
非運行時異常的優(yōu)點
強制程序員去處理出錯的情況拙吉。如果不強制大家去處理這個情況,很多人都會選擇不處理異常揪荣,比如下面的方法:
file.delete();
這個方法是有一個返回值的筷黔,返回true表示刪除成功,false表示刪除失敗变逃,這里必逆,沒有拋出非運行時異常,所以很多人都會閉上眼睛假裝不會失敗揽乱,導致程序不穩(wěn)定名眉。
非運行時異常的缺點
- 那些發(fā)生概率極低或者明知道不會發(fā)生問題的地方,也必須要捕獲異常凰棉。
比如下面的代碼:
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
這段代碼丑陋的地方在于损拢,這個線程,我根本不打算去執(zhí)行interrupt方法撒犀,sleep方法就根本不會拋出異常福压,catch代碼段根本不可能執(zhí)行到,或者即便拋異常了或舞,也沒什么大不了的荆姆,但是我們還是得去捕獲這個異常。Google也發(fā)現(xiàn)了這個問題映凳,所以給了大家另外一個選擇:
SystemClock.sleep(1000);
- 一些設(shè)計的不好的異常胆筒,即便捕獲了,也無法處理诈豌。
try {
JSONObject jsonObject = new JSONObject("");
} catch (JSONException e) {
throw new RuntimeException(e);
}
比如這里的JSONException仆救,雖然它是非運行時異常,但是幾乎不可恢復矫渔,所以在catch代碼塊中也就只能再拋出一個運行時異常彤蔽。
總結(jié)
實際工作中遇到的情況各種各樣,但是一個總的原則就是通過分析錯誤的產(chǎn)生原因和嚴重程度來選擇處理錯誤的方式庙洼。