2008-09-14 172 views
33

我剛剛開始瀏覽John Robbins的「調試MS .Net 2.0應用程序」,並且因爲Debug.Assert(...)的傳福音而感到困惑。Debug.Assert與特定投擲異常

他指出,實施得當斷言儲存狀態,有點,錯誤狀況,例如:

Debug.Assert(i > 3, "i > 3", "This means I got a bad parameter"); 

現在,個人來說,沒有太瘋狂了,我認爲他這麼愛重申他的測試實際上合理的「商業邏輯」評論,也許「因爲flobittyjam widgitification過程,絕不會發生」。

所以,我認爲我把Asserts看作是一種低級的「讓我們保護我的假設」的東西......假設一個人認爲這是一個測試,只需要在調試中做 - 即你保護自己免受同事和未來的程序員的影響,並希望他們真正地測試事情。

但是我不明白的是,他接着說,除了正常的錯誤處理外,你還應該使用斷言。現在我想象的是這樣的:

Debug.Assert(i > 3, "i must be greater than 3 because of the flibbity widgit status"); 
if (i <= 3) 
{ 
    throw new ArgumentOutOfRangeException("i", "i must be > 3 because... i=" + i.ToString()); 
} 

我得到的錯誤條件測試Debug.Assert重複?我想我會得到它,如果我們談論的是一個非常重要的計算僅調試雙重檢查...

double interestAmount = loan.GetInterest(); 
Debug.Assert(debugInterestDoubleCheck(loan) == interestAmount, "Mismatch on interest calc"); 

...但我不明白它的參數測試,這些值得肯定檢查(在DEBUG和Release版本中)...或不。我錯過了什麼?

回答

48

斷言不用於參數檢查。應始終進行參數檢查(並根據文檔和/或規範中的指定前提條件),並根據需要拋出ArgumentOutOfRangeException

斷言是用於測試「不可能」的情況,即您(在您的程序邏輯中)假定爲真的事情。這些斷言是爲了告訴你這些假設是否因任何原因而被打破。

希望這會有所幫助!

+10

斷言可以用於*內部*方法調用的參數檢查的方法(由屬於同一個組件的代碼調用),而不是外部的方法調用(由另一個組件調用) 。例如,我可能會斷言Double類型的私有方法參數不是NaN。 – RoadWarrior 2008-10-31 11:48:52

2

IMO它只是一個開發時間的損失。正確實施的例外可以讓你清楚地瞭解發生了什麼。我看到太多應用程序顯示晦澀的「斷言失敗:我< 10」錯誤。我認爲斷言是臨時解決方案。在我看來,程序的最終版本中不應該有任何斷言。在我的練習中,我使用斷言進行快速和骯髒的檢查。代碼的最終版本應考慮到錯誤的情況並相應地執行。如果發生不好的事情,你有兩個選擇:處理它或離開它。函數應該拋出一個有意義描述的異常,如果傳入了錯誤的參數。我沒有看到重複驗證邏輯的要點。

4

我使用明確的檢查,拋出異常公衆保護私人方法的方法和斷言。

通常,顯式檢查會防止私有方法看到不正確的值。所以真的,斷言正在檢查一個不可能的情況。如果一個斷言觸發了,它告訴我這個類的公共例程中包含的驗證邏輯存在缺陷。

17

有一個溝通方面來斷言vs異常拋出。

假設我們有一個帶有Name屬性和ToString方法的User類。

的ToString如果實施這樣的:

public string ToString() 
{ 
    Debug.Assert(Name != null); 
    return Name; 
} 

它說,名稱應該永遠不能爲null並且在用戶類的錯誤,如果它是。

如果是的ToString實現這樣的:

public string ToString() 
{ 
    if (Name == null) 
    { 
      throw new InvalidOperationException("Name is null"); 
    } 

    return Name; 
} 

它說,來電者被錯誤地使用的ToString如果名稱爲空,應檢查之前調用。

實現既

public string ToString() 
{ 
    Debug.Assert(Name != null); 
    if (Name == null) 
    { 
      throw new InvalidOperationException("Name is null"); 
    } 

    return Name; 
} 

說,如果名稱爲null,那裏的用戶類的錯誤,但我們仍要處理。 (用戶在打電話前不需要檢查姓名。)我認爲這是Robbins推薦的安全類型。

3

可以捕捉併吞下異常,從而使測試中看不見的錯誤。 Debug.Assert不會發生這種情況。

沒有人應該有一個捕獲處理程序捕獲所有異常,但人們無論如何都這樣做,有時它是不可避免的。如果您的代碼是從COM調用的,則互操作層捕獲所有異常並將它們轉換爲COM錯誤代碼,這意味着您將看不到未處理的異常。斷言不會因此受到影響。

此外,當異常將被處理時,更好的做法是採取小型轉儲。 VB比C#更強大的一個領域是,當異常處於運行狀態時,您可以使用異常過濾器捕捉小型轉儲,並保持其餘的異常處理不變。 Gregg Miskelly's blog post on exception filter inject提供了一個有用的方法來從c#中做到這一點。

關於資產的另一個注意事項......它們與單元測試代碼中的錯誤條件的效果很差。值得擁有一個封裝來關閉你的單元測試的斷言。

0

這裏是2美分。

我認爲最好的方法是使用斷言和例外。這兩種方法之間的主要區別是,如果Assert語句可以很容易地從應用程序文本(定義,條件屬性...)中移除,而拋出的Exception依賴於(最終)由一個更難刪除的條件代碼具有預處理器條件的多段)。

每個應用程序異常都應該正確處理,而斷言只有在算法開發和測試過程中才能得到滿足。

如果將空對象引用作爲例程參數傳遞,並且使用此值,則會得到空指針異常。確實:爲什麼你應該寫一個斷言?在這種情況下浪費時間。 但是,班級例程中使用的私人班級成員呢?當這些值設置在某個地方時,如果設置了空值,最好檢查一個斷言。這只是因爲當你使用成員時,你會得到一個空指針異常,但你不知道該值是如何設置的。這會導致重新啓動程序,破壞所有入口點的使用以設置私有成員。

異常更有用,但它們可能非常繁重,可能會使用過多的異常。而且他們需要額外的檢查,可能不希望優化代碼。 個人而言,只有代碼需要深度捕獲控制(catch語句在調用堆棧中非常低)或者函數參數未在代碼中硬編碼時,我纔會使用異常。

4

我在考慮關於測試問題時提供關於調試與斷言的指導時,我已經考慮過這麼長時間了。

你應該能夠測試你的班級有錯誤的輸入,壞的狀態,無效的操作順序和任何其他可能的錯誤情況,並斷言應該從來沒有旅行。每個斷言都檢查應該總是是真實的,不管執行的輸入或計算。

經驗好規則,我在抵達:

  1. 斷言是不可靠的代碼,其功能正常獨立配置的更換。它們是互補的。

  2. 在單元測試運行期間,即使在喂入無效值或測試錯誤條件時,斷言絕不應該跳閘。代碼應該處理這些條件而不會發生斷言。

  3. 如果斷言行爲(無論是在單元測試還是在測試過程中),該類都會被竊聽。

對於其他所有錯誤 - 通常到環境(網絡連接丟失)或濫用(調用者傳遞一個空值) - 這是更漂亮和更可以理解爲使用硬檢查&例外。如果發生異常,主叫方知道這可能是他們的錯。如果發生斷言,調用者知道它可能是代碼中聲明所在的錯誤。

關於重複:我同意。我不明白你爲什麼要用Debug.Assert和異常檢查複製驗證。它不僅給代碼添加了一些噪音,而且對於誰在過錯中混淆了水域,但它是一種重複的形式。一個良好的使用斷言的

1

例子:

Debug.Assert(flibbles.count() < 1000000, "too many flibbles"); // indicate something is awry 
log.warning("flibble count reached " + flibbles.count()); // log in production as early warning 

我個人認爲,斷言應該,當你知道的東西是外面希望限制使用,但可以肯定它的合理安全繼續。在所有其他情況下(我沒有想到,請隨時指出情況)使用例外來強制和快速地失敗。

對於我來說,最重要的權衡是您是否希望使用異常來降低實時/生產系統以避免腐敗並簡化故障排除,或者您是否遇到了永遠不應該被允許在測試/調試版本,但可以允許繼續生產(記錄警告當然)。

cf.http://c2.com/cgi/wiki?FailFast 複製並從Java問題改性:Exception Vs Assertion