2017-04-24 65 views
1

我已經讀了一下,試圖找出何時適當地使用斷言和例外,但仍然有一些我錯過了大圖。可能我只是需要更多的經驗,所以我想帶一些簡單的例子來更好地理解我應該在什麼情況下使用什麼。斷言和例外的適當使用

例1:讓我們從一個無效值的經典情況開始。例如,我有下面的類,在這兩個領域必須爲正:

class Rectangle{ 
    private int height; 
    private int length; 

    public int Height{ 
     get => height; 
     set{ 
      //avoid to put negative heights 
     } 
    } 
    //same thing for length 
} 

讓我此話,我不是在談論如何在這個例子中處理用戶輸入,因爲我可以做一個簡單的控制流程。雖然,我面臨的想法是,在別的地方可能會出現一些意想不到的錯誤,我希望能夠檢測到這個錯誤,因爲我不想要一個帶有invald值的對象。所以我可以:

  • 使用Debug.Assert並停止程序,如果發生這種情況,以便我可以糾正可能出現的錯誤。
  • 拋出一個ArgumentOutOfRangeException基本上做同樣的事情?這感覺不對,所以我應該使用它,只有當我知道我要在某個地方處理它時。雖然,如果我知道在哪裏處理異常,我不應該解決它存在的問題嗎?或者它可能意味着可能發生的事情,但你不能直接在代碼中控制,就像用戶輸入(可以處理,沒有例外,但也許別的東西不能)或加載數據?

問題:我是否明白斷言和例外的含義是正確的?另外,請舉例說明處理例外情況是否有用(因爲發生前無法控制的情況)?除了我提到的情況之外,我無法弄清楚還有什麼可以發生,但我顯然還是缺乏經驗。
爲了擴展我的問題:我可以想到爲什麼會拋出異常的各種原因,比如NullReferenceExceptionIndexOutOfBoundsException,IO異常如DirectoryNotFoundExceptionFileNotFoundException等等。雖然我無法弄清楚在哪些情況下處理除了簡單地停止程序(在這種情況下,不應該使用斷言?)或給出簡單的問題出現位置之外,它們變得有用。我知道即使這是有用的,例外也意味着將「錯誤」分類並給出如何解決它們的線索。雖然,是一個簡單的消息,真的他們對有用嗎? 這聽起來很腥,所以我會堅持「我從來沒有面對過適當的情況,因爲經驗」的口頭禪。

示例2:現在讓我們來談談用戶輸入,使用第一個示例。正如我所預料的那樣,我不會使用異常來檢查值是否爲正值,因爲這是一個簡單的控制流程。但是如果用戶輸入一個字母會發生什麼?我是否應該在這裏處理例外情況(可能是簡單的ArgumentException)並在catch塊中給出消息?或者也可以通過控制流程避免(檢查輸入是否爲int或類似的東西)?

感謝任何會清除我心有餘悸的人。

+1

沒有一個正確的答案,它完全取決於你有支持系統來處理你的代碼的客戶端程序員錯誤,不知道爲什麼。用戶輸入無效數據並不例外。 –

+0

我知道高度依賴於情況,我只是想弄清楚會出現什麼樣的情況。此外,堅持這個簡單的例子,相反,如果我的意思是驗證值只是爲了檢測代碼中的錯誤,我應該使用'Debug.Assert'。如果我打算實施保存/加載數據,並且加載文件中的某些內容可能會失敗,我可以捕獲它並使用「損壞的數據」消息停止該程序。這種推理是正確的還是我沒有達到斷言和/或例外的目的? – Harnak

+0

其不完整的問題。通過鼓勵(或迫使)他們輸入正確的數據來處理用戶。停止程序時,它可能只是幫助用戶似乎不必要 – ferday

回答

10

拋出一個ArgumentOutOfRangeException基本上做同樣的事情?這感覺不對,所以我應該使用它,只有當我知道我要在某個地方處理它時。但是,如果我知道應該在哪裏處理異常,我不應該將問題解決在哪裏嗎?

你的推理在這裏還算不錯,但不是沒錯。你掙扎的原因是因爲異常是在C#中使用件事:

  • 愚蠢的例外。當調用者知道參數無效時,骨頭異常就像「無效參數」。如果引發了一個頭引發的異常,那麼調用者有一個應該修復的bug。測試用例之外永遠不會有catch(InvalidArgumentException),因爲它永遠不會投入生產。這些例外的存在是爲了幫助您的呼叫者在他們犯錯時大聲告訴他們寫出正確的代碼。

  • 傷腦筋例外是愚蠢的例外情況主叫方無法知道該參數是無效的。這些都是API中的設計缺陷,應該消除。他們要求你用try-catches來封裝API調用,以捕捉看起來應該避免的異常,而不是被捕獲。如果你發現你正在編寫需要調用者在try-catch中包裝調用的API,那麼你做錯了什麼。

  • 致命異常是異常,如線程中止,內存不足等。發生了一些可怕的事情,並且過程無法繼續。捕捉這些東西幾乎沒有意義,因爲改善情況的能力並不多,而且可能會讓情況變得更糟。

  • 外生的例外是像「網絡電纜拔掉」的東西。您預計網絡電纜將被插入;它不是,也不可能早些時候檢查過,看看是否是這樣,因爲檢查時間和使用時間是不同的時間;電纜可以在這兩次之間拔掉。你必須抓住這些。

既然你知道四種異常是什麼,你可以看到異常和斷言之間有什麼區別。

斷言是邏輯上必須始終爲真,如果不是,那麼你有一個應該修復的錯誤。你永遠不會斷言網絡電纜插入。你永遠不會斷言調用方提供的值不爲空。應該有從來沒有是一個測試案例,導致斷言發射;如果存在,那麼測試用例發現了一個錯誤。

您斷言在您的就地排序算法運行後,非空數組中的最小元素將在開始處。應該沒有辦法可以是假的,如果有的話,你有一個錯誤。所以斷言這個事實。

A throw相反是聲明,而每個語句都應該有一個測試用例,它練習它。 「這個API在被錯誤的調用者傳遞時拋出」是它的合約的一部分,並且該合約應該是可測試的。如果你發現你正在編寫沒有可能的測試用例來驗證他們拋出的throw語句,考慮將它改爲斷言。

最後,永遠不要傳遞無效的參數,然後捕獲一個帶頭的異常。如果您正在處理用戶輸入,那麼UI層應該驗證輸入是否在語法上有效,即,預期數字的數字。 UI層不應該將可能未被修改的用戶代碼傳遞給更深層的API,然後處理產生的異常。

+0

優秀的答案,解開了許多疑問。這正是我正在尋找的東西。謝謝! 只是對拔下的電纜示例的最後一個疑問。我的意思是,我不確定自己是否正確,但我不覺得在這樣的情況下,你可以通過抓住它來做很多事情。或者你能嗎? – Harnak

2

我明白這一點。斷言適用於程序員。例外是針對用戶的。你可以在你的代碼中擁有你期望具體價值的地方。那麼你可以把斷言,例如:

public int Age 
{ 
    get { return age; } 
    set 
    { 
     age = value; 
     Debug.Assert(age == value); 
    } 
} 

這只是例子。所以如果年齡!=值沒有例外。但「嘿程序員,可能發生了一些奇怪的事情,看看這部分代碼」。

當應用程序不知道如何在特定情況下作出反應時,您會使用異常。例如:

public int Divide(int a, int b) 
{ 
    Debug.Assert(b != 0); //this is for you as a programmer, but if something bad happened, user won't see this assert, but application also doesn't know what to do in situation like this, so you will add: 

    if(b == 0) 
     throw SomeException(); 
} 

SomeException可能會在應用程序的其他位置處理。

+0

「嘿,程序員」部分非常具有說明性。我認爲我正確的目的是斷言然後:)謝謝。 儘管如此,我仍然對處理異常抱有懷疑。比方說,我有一種方法可以加載一些數據,然後調用Divide對數據進行操作(我使用加載數據或用戶輸入場景,因爲它們是我現在可以想到的唯一情況;也許**是* *我在處理**異常時仍然懷疑)。我可以處理一個例外,給出一條消息「嗨用戶,你在那裏放錯了一個文件。」如果出現問題? – Harnak

+0

首先,您應該考慮是否可以處理此異常。例如,如果你除以x/0,那麼你可能會把某種默認值作爲結果。但是,如果你真的不知道在這種「特殊」情況下該做什麼,那麼你必須通知用戶該文件已損壞或某事。但不要以我寫的關於默認值的方式誤導我。不要使用異常處理來檢查事情。異常是昂貴的,應該只在發生錯誤時使用。 –

+0

我明白。感謝您的解釋:) – Harnak