爲開發健壯的程序,咱們常常用空指針代替異常狀況,但這實際上卻把控制流限制在方法調用和返回的普通方式,同時也隱藏了異常狀況發生的跡象。在這篇專欄裏,Eric Allen 展現了這種錯誤模式(他稱之爲空標誌錯誤模式)怎樣產生難以調試的意外結果。和咱們討論過的其它錯誤模式同樣,您能夠應用某種編程技巧來減小這種錯誤的出現。
空標誌錯誤模式
在個人上一篇文章中,我說明了用空指針代替各類不一樣基本類型的數據是如何成爲引發 NullPointerException 異常最廣泛的緣由之一的。這一次,我將說明用空指針代替異常狀況怎麼也會致使問題的出現。在 Java 程序中,異常狀況一般是經過拋出異常,並在適當的控制點捕獲它們來進行處理。可是常常看到的方法是經過返回一個空指針值來代表這種狀況(以及,可能打印一條消息到 System.err)。若是調用方法沒有明確地檢查空指針,它可能會嘗試丟棄返回值並觸發一個空指針異常。html
您可能會猜測,之因此稱這種模式爲空標誌錯誤模式,是由於它是不一致地使用空指針做爲異常狀況的標誌引發的。java
原由
讓咱們來考慮一下下面的這個簡單的橋類(從 BufferedReaders 到 Iterators):程序員
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Iterator;編程
public class BufferedReaderIterator implements Iterator {設計模式
private BufferedReader internal;模塊化
public BufferedReaderIterator(BufferedReader _internal) {
this.internal = _internal;
}工具
public boolean hasNext() {
try {測試
boolean result = true;網站
// Let´s suppose that lines in the underlying input stream are known
// to be no greater than 80 characters long.
internal.mark(80);this
if (this.next() == null) {
result = false;
}
internal.reset();
return result;
}
catch (IOException e) {
System.err.println(e.toString());
return false;
}
}
public Object next() {
try {
return internal.readLine();
}
catch (IOException e) {
System.err.println(e.toString());
return null;
}
}
public void remove() {
// This iterator does not support the remove operation.
throw new UnsupportedOperationException();
}
}
由於這個類做爲 Iterator 接口的橋接實現,代碼必須從 BufferedReader 捕獲 IOException 異常。每一種方法經過返回某個缺省值來處理 IOException。對於 hasNext,返回 false 值。這是合理的,由於若是 IOException 異常被拋出,客戶就不該該期望能從 Iterator 檢索到另外一個元素。另外一方面,在 IOException 異常(由於它取決於 internal.readLine() 的返回值)和 internal 是空的狀況下,next 都返回 null。但這不是 Iterator 對象的客戶所期待的。正常狀況下,在沒有更多元素的 Iterator 上調用 next 時,會拋出一個 NoSuchElementException 異常。若是咱們的 Iterator 的客戶依賴於這種行爲,它極可能會嘗試丟棄從調用 next 返回的空指針,結果致使 NullPointerException 異常。
無論 NullPointerException 異常何時出現,都要對如上所述的狀況做檢查。這種錯誤模式的出現很廣泛。
預防措施
儘管這種錯誤模式常常出現,使用空標誌還是很是沒有根據的(與上例的狀況同樣)。讓咱們來重寫 next,使它如咱們指望的同樣拋出 NoSuchElementException 異常:
public Object next() {
try {
String result = internal.readLine();
if (result == null) {
throw new NoSuchElementException();
}
else {
return result;
}
}
catch (IOException e) {
// The original exception is included in the message to notify the
// client that an IOException has occurred.
throw new NoSuchElementException(e.toString());
}
}
請注意:要使其他的代碼能使用修改過的方法,咱們還必須:
導入 java.util.NoSuchElementException。
修正 hasNext,使其再也不調用 next 來進行測試。最簡單的修正方法是隻要直接調用 internal.readLine()。
另外一種處理 IOException 異常的方法是捕獲它們,並代替它們拋出 RuntimeException 異常。決定這樣作是基於對目標平臺上預計 IOException 異常出現頻率的估計。若是很頻繁,那麼您可能想試着從中恢復。
調用這個新 next 方法的任何代碼可能都不得不處理拋出的 NoSuchElementException 異常。(固然,代碼能夠簡單地選擇忽略它們並容許程序異常終止。)若是這樣,與原始代碼拋出的 NullPointerException 異常相比,產生的錯誤消息和拋出異常的位置所提供的信息要豐富得多。若是拋出的異常是檢查過的異常(好比 IOException),那麼它會更有用,由於除非處理了異常,不然類的客戶代碼將不編譯。利用這種方法,咱們甚至能夠在程序運行前排除某些錯誤發生的可能性。可是,在這個示例中,不破壞 Iterator 接口,就不能拋出這樣一個檢查過的異常。所以,爲了重複使用在 Iterators 上運行的代碼,咱們犧牲了一些靜態檢查。靜態檢查的目的和重複使用的目的之間的這種矛盾是很廣泛的。
總結
在我完成這篇文章前,我要提醒許多常用空標誌的程序員注意。許多程序員會爭辯說這會使他們的程序更「健壯」。畢竟,他們可能會說,健壯的系統可以適當地處理不一樣的狀況,而不是一遇到小問題就拋出異常。可是這種爭辯忽視了這樣一種事實,即異常是加強代碼健壯性的有力工具,它容許在異常狀況下控制能快速傳送到最適合控制的位置。另外一方面,空標誌的使用把控制流限制在方法調用和返回的普通方式(固然,一直到整個程序崩潰)。此外,這樣使用空標誌,程序員有效地掩蓋了異常狀況出現位置的跡象。誰知道空指針在被丟棄前從方法到方法傳遞了多遠?這隻能使得診斷錯誤以及肯定怎樣修正它們更加困難。經驗證實這種代碼常常中斷。咱們首要關注的應該是避免這種困惑,使診斷儘量容易。所以,做爲準則,我努力編寫能夠儘快通知異常狀況的代碼,而且嘗試着僅從沒有指示程序錯誤的異常狀況中恢復。
即便在代碼中儘可能避免使用空標誌,您仍要不可避免地處理使用了空標誌的舊代碼。事實上,許多 Java 類庫自己,好比咱們上面使用過的 Hashtable 類和 BufferedReader 類都用了空標誌。當使用這樣的類時,您能夠經過在執行前,顯式檢查操做是否將返回空來避免錯誤。例如,對於 Hashtables,我老是在調用 get 以前用 containsKey 進行測試。可是,儘管採用這種預防手段,這種錯誤模式仍然是最常碰到的錯誤模式之一。
下面是本週的錯誤模式的小結:
模式:空標誌
症狀:使用空指針做爲異常狀況的標誌的代碼塊報告 NullPointerException 異常。
原由:調用方法沒有檢查做爲返回值的空指針。
治療和預防措施:拋出異常來報告異常狀況。
在下一篇文章中,我將討論與類強制轉換異常有關的錯誤模式。
參考資料
請務必閱讀 Eric Allen 的前一個關於錯誤模式的診斷 Java 代碼專欄:
The Dangling Composite bug pattern(developerWorks,2001 年 3 月)
錯誤模式:介紹(developerWorks ,2001 年 2 月)
一種防止異常狀況處理不一致的方法是面向表徵的編程: 一種將程序的常常繞過模塊邊界的部分模塊化的編程風格。請查看 AspectJ,Java 語言的一種面向表徵的擴展,帶有支持許多流行的 Java IDE 的實現。
靜態肯定可能出現空指針異常的方法是一種被稱爲 set-based analysis 的技術。The Carnegie Mellon School of Computer Science 網站爲這種方法提供了簡短介紹,同時還提供與本文相關的一些技術出版物的連接。
DePaul 大學的軟件工程部已經在自動化定理方面作了一些工做,在 Java 代碼中偵測出空指針異常。
訪問 Patterns 主頁,獲取關於設計模式以及怎樣使用它們的好的介紹。
請查看 JUnit,經過將代碼 "test-infested" 來捕捉更多的錯誤。
本文轉自 ☆★ 一應俱全 ★☆ - www.baoluowanxiang.com 轉載請註明出處,侵權必究!
原文連接:http://www.baoluowanxiang.com/a/program/java/2011/0401/3053.html