2009-09-18 80 views
8

我來自.NET背景,現在涉足Java領域。防禦性編程:Java中的指導

目前,我在設計API防禦錯誤輸入方面遇到很大問題。比方說,我有以下代碼(足夠接近):

public void setTokens(Node node, int newTokens) { 
    tokens.put(node, newTokens); 
} 

但是,此代碼可能會失敗的原因有兩個:

  1. 用戶通過一個null節點。
  2. 用戶通過一個無效節點,即一個不包含在圖中。

在.NET中,我將拋出一個ArgumentNullException(而不是NullReferenceException!)或ArgumentException分別通過違規參數(node)爲string參數的名稱。

Java似乎沒有例外。我意識到我可以更具體,只是拋出最接近描述情況的異常,或者爲特定情況編寫我自己的異常類。

這是最佳做法嗎?或者有類似於.NET中的ArgumentException的通用類?

在這種情況下檢查null是否有意義?代碼無論如何都會失敗,異常的堆棧跟蹤將包含上述方法調用。檢查null似乎是多餘的和過度。當然,堆棧跟蹤將是略微更清潔(因爲它的目標是上述方法,而不是在JRE的HashMap執行內部檢查)。但是這必須抵消額外的if聲明的成本,而且,永遠不會出現 - 畢竟,將null傳遞給上述方法並不是預期的情況,這是一個相當愚蠢的錯誤。期待它是徹頭徹尾的偏執 - 即使我沒有檢查它也會失敗,同樣的例外。

[正如已經在評論中指出的,HashMap.put實際上允許null值爲關鍵。所以對null檢查不一定是多餘這裏]

+2

調用'setTokens(NULL,0)'只會如果你正在使用'Hashtable'或'ConcurrentHashMap'因爲這些不允許'null'鑰匙丟了'NullPointerException'。另一方面,'HashMap'樂於擁有空鍵。 – pjp 2009-09-18 12:56:27

+0

@pjp:感謝您的糾正 - 我曾預料會失敗,因爲'HashMap'需要創建它的參數的散列。我想,還有一個明確檢查的理由。 – 2009-09-18 14:15:16

回答

7

不同的羣體有不同的標準。首先,我假設你知道RuntimeException s(未選中)與正常Exception s(選中)之間的區別,如果不是,則請參閱this question and the answers。如果你寫自己的例外,你可以強制它被捕獲,而NullPointerExceptionIllegalArgumentException都是RuntimeExceptions,這是在一些圈子裏皺起眉頭。其次,和你一樣,我一起工作過但不主動使用斷言,但是如果你的團隊(或API的使用者)已經決定使用斷言,那麼斷言聽起來就像正確的機制。

如果我是你,我會使用NullPointerException。原因是先例。以Sun的示例Java API爲例,例如java.util.TreeSet。這恰好使用NPE來處理這種情況,雖然看起來你的代碼只是使用了null,但它完全合適。

正如其他人所說的IllegalArgumentException是一個選項,但我認爲NullPointerException更具溝通性。

如果此API旨在供外部公司/團隊使用,我會堅持NullPointerException,但請確保它在javadoc中聲明。如果是爲了內部使用,那麼你可能會決定添加你自己的異常heirarchy是值得的,但是我個人發現,添加了巨大的異常heirarchies的APIs,只會是printStackTrace()d或登錄只是浪費精力。

在一天結束時,最主要的是你的代碼能夠清楚地進行通信。當地的例外條款就像當地的術語 - 它爲業內人士增加了信息,但可能會讓外界感到困惑。

至於檢查無效,我認爲它是有道理的。首先,它允許您在構建異常時添加有關null的消息(即節點或令牌),這將有所幫助。其次,將來您可能會使用允許nullMap實現,然後您將失去錯誤檢查。成本幾乎沒有,所以除非一個分析師說這是一個內部循環問題,我不會擔心它。

+0

謝謝,這真的是內容豐富。至於檢查與未檢查的異常:違反上述不變量是代碼中的* bug *,而不是潛在可行的條件。在這裏使用checked異常會非常瘋狂 - 也許會讓'NullPointerException'成爲一個檢查異常,並且在每個單獨的方法調用周圍放置一個'try'塊。 – 2009-09-18 14:18:15

+0

當堆棧跟蹤是解決問題的唯一線索時,異常的描述性_name_可能非常有用。 – 2009-09-18 14:42:02

+0

@Thorbjørn我通常更喜歡優秀的消息來獲得更好的命名異常,雖然兩者都很有用 – 2009-09-18 16:20:28

7

在Java中,你通常會拋出一個IllegalArgumentException

+2

BTW。我們使用Jakarta Commons-Lang來做合同風格檢查。 例如Validate.notNull(node) 或 Validate.isTrue(node.isEmpty()) 這些爲您拋出IllegalArgumentExceptions – Fortyrunner 2009-09-18 13:40:43

+1

對於「正常」的某些值。我認爲當傳遞一個空參數時,你會發現很多標準的Java API會優先於IAE拋出NPE。 – 2009-10-12 04:40:48

2

你的方法完全取決於什麼合同的功能提供給調用者 - 這是一個前提條件,即節點不是null?

如果是這樣,那麼如果節點爲空,則應該拋出一個異常,因爲它違反了合同。如果它沒有,那麼你的函數應該默默地處理空節點並做出適當的響應。

12

標準的Java異常是IllegalArgumentException。如果參數爲空,某些人會拋出NullPointerException,但對我來說,NPE有這個「有人搞砸」的內涵,並且你不希望你的API的客戶認爲你不知道你在做什麼。

對於公共API,檢查參數並儘早乾淨地失敗。時間/成本幾乎不重要。

+1

IAE與NPE的論點+1。我假設(正確或錯誤地)一個NPE是一個未知的錯誤,而一個IAE(帶有適當的信息)遵循一個明確的空檢查 – 2009-09-18 13:31:07

2

如果你想要一本關於如何編寫好的Java代碼的指南,我可以強烈推薦Joshua Bloch的書Effective Java

+0

這本書已經在我的願望清單上,但我目前已經超過了我的書本預算。 :-( – 2009-09-18 12:52:24

+2

最佳Java書如初。也許最好的編程的書的。 – 2009-09-18 13:33:56

+0

同意唐。如果你工作了很多與Java,這本書應該有絕對的頭等大事了什麼願望清單別的。這與完全樣的交易那你說說這個問題的東西。 – Jonik 2009-09-25 11:54:08

0

像其他:java.lang.IllegalArgumentException。 關於檢查空節點,在節點創建時檢查錯誤輸入是什麼?

2

這聽起來像這可能是一個assert適當的使用:

public void setTokens(Node node, int newTokens) { 
    assert node != null; 
    tokens.put(node, newTokens); 
} 
+0

所以沒有任何人實際上斷言編譯啓用?我以爲他們是很出時尚和相當多的單元測試和其它技術所取代。 – 2009-09-18 12:55:43

+0

當然,我用斷言檢查內部狀態。重要的是,它們可以在現場啓用,所以如果程序行爲異常,你會讓它們運行assert,它可能會告訴你一些關於應用程序實際使用情況的有用信息,這些信息可能沒有已經被測試覆蓋 但是對於公共API,因爲無論如何你都要明確地測試它,塞特是多餘的。 – Ken 2009-09-18 12:58:59

+0

查看pjp對skaffman的回答的評論。 – 2009-09-18 13:45:03

1

我認爲很大程度上取決於方法的合同以及調用者的知道程度。

在調用你的方法之前,調用者可以採取行動來驗證節點。如果你知道調用者並知道這些節點總是被驗證,那麼我認爲可以假設你會得到好的數據。來電者基本上負有責任。

但是,如果你是,例如,提供分發的第三方庫,那麼你需要驗證節點的空值等... ...

的illegalArugementException是java標準,但也是一個RuntimeException。所以如果你想強制調用者處理異常,那麼你需要提供一個檢查異常,可能是你創建的一個自定義異常。

1

個人而言,我想NullPointerException異常只能由意外發生,因此一定是別的東西可以用來表明非法參數值傳遞。 IllegalArgumentException對此很好。

if (arg1 == null) { 
throw new IllegalArgumentException("arg1 == null"); 
} 

這應該足以這兩個讀碼,而且這個可憐的靈魂誰得到在早上3支持呼叫。

(並且總是爲您提供的例外中的說明文本,你會明白他們一些悲傷的一天)

0

我不必討好任何人,所以我現在要做的,作爲典型的代碼是

void method(String s) 

if((s != null) && (s instanceof String) && (s.length() > 0x0000)) 
{ 

這讓我有很多睡眠。其他人不會同意。

+2

我不確定你需要做一個instanceof檢查。方法簽名需要的東西是一個字符串或其中的一個子類,但字符串是最終的。 – 2009-10-12 04:26:42

+0

@Platinum Azure:編譯器問題,應該在編譯時檢查,但我已經看到它在運行時可以得到類轉換異常的地方,所以我只是把它放在那裏。似乎更好地工作。會有不同意的人。順便說一句,我做了一些字符串測試,它似乎做了一個char []的副本,所以char [0] ++沒有任何作用...任何具有寶貴財產的大型商店都將編寫自己的編譯器以避免不知情在這樣的問題上。應該是標準的maser級別cs。 – 2009-10-14 22:34:52

+0

with's instanceof String',不需要額外的'null'檢查... – 2015-08-06 15:59:45