2011-12-10 127 views
4

我的主要問題是如何處理那些拋出的異常?代碼合同如何處理異常

例如:

Contract.Requires(foo != null); 

什麼與此拋出的異常的函數調用的UPER水平呢?

我應該忽略它嗎?當我看到它知道我的設計有問題並修復它時?

但是如果我在沒有合同的情況下部署,會發生什麼情況,我會得到一個foo == null的參數,而我的邏輯不知道如何處理像這樣的參數。然後一切都會崩潰。

有人可以解釋如何處理所有這些情況?

感謝

+1

所有這些決定取決於你,並且對每個具體應用都非常具體。 – sll

回答

18

我的主要問題是如何處理那些拋出的異常?

首先,你在技術上不能趕上這些例外。除Contract.Requires<TExc>()之外的所有方法都會拋出一個System.Diagnostics.Contracts.__ContractsRuntime.ContractException,它被嵌入您的組件中,並且是私有的。爲了趕上它,你必須捕獲所有異常這是你可以做的最糟糕的事情。

合同以及聲明是條件,必須總是正確的。如果它們不是,那麼程序處於一個它並沒有被設計的狀態,並且你不能確定它是否能夠安全地繼續。您可以將合同視爲對該語言的擴展。你不希望.NET程序讓你在'特殊情況'下違反類型安全,是嗎?合同也是如此。

在這個拋出異常的函數調用的uper級別做什麼?

合同的整體思路是強制執行呼叫者調用帶有合同的方法之前檢查。如果呼叫者沒有檢查並且做錯了事 - 它必須被修復。我的意思是:如果你有一個方法Contract.Requires(arg != null),那麼,如果你有一個null值,那麼不要叫它。

另一個問題是'你是否應該把所有的合同都放在公開的位上?'從安全位置,你最好把它們全部留下。

如果你的代碼不期望某些值,但它得到它們,唯一絕對安全的決定是停止當前操作並出錯。你不能確定,如果你忽視你的合同,你不會損壞數據或做其他不好的事情。當然,你需要一定程度的粒度來讓你的程序繼續處於安全狀態,而不是終止於大爆炸,儘管在某些情況下需要終止。

我應該忽略它嗎?當我看到它知道我的設計有問題並修復它時?

如果您釋放軟件並發現有一個用例因合同失敗而失效,即使沒有合同,也可能不起作用 - 您只是沒有想到它並且不得不做一些額外的工作來支持它。您應該仔細設計所有用例並進行徹底的QA以避免這種情況。合同與這些問題沒有關係。

但是,如果我部署沒有契約時會發生什麼情況,我會得到一個參數是foo == null,並且我的邏輯不知道如何處理像這樣的參數。然後一切都會崩潰。

這是離開合同的另一個原因。在預先設計的地方碰撞比在不期望發生的地方更好。

也許,刪除某些合同的唯一重要原因是性能:在每種方法之後檢查不變量可能非常昂貴。

+1

+1,公認的答案 – koenmetsu

0

要採取的具體的例子,該合同的通信的參數含有方法不能爲空(理想的方法的文檔也會告訴你這一點)。在編寫使用代碼時,您應該認識到這一點,如果您沒有可用的非空值作爲參數傳遞給該方法,則不應調用該方法。然後由消費者代碼決定在這種情況下應該怎麼做。它可以採取另一種執行路徑或拋出異常。正如@sll所說,在這種情況下的行動選擇完全取決於您的應用程序需求所需的邏輯。

3

Code Contracts允許您更精確地聲明您的方法接受的參數以及它返回的內容(前置條件和後置條件)。您可以聲明該字符串應該爲非空,長度大於10,並且完全由大寫字符等組成,而不是具有接受字符串(任何字符串)的函數。

如果調用者不不遵守合同,這是一個錯誤,應該這樣報告(例如應該拋出異常)。但是,將Contract.Requires()語句放入源代碼中並不會生成任何實際的IL代碼。您必須運行代碼合同重寫器來後處理您的代碼。這會將合同檢查插入最終的IL中,如果合同未得到遵守,這些檢查將會引發異常。

您也可以使用代碼合同靜態檢查器來證明合同在您的代碼中執行。如果這是真的,您可以指示重寫器不插入支票,因爲您已經證明始終遵守合同。使用公共API你不能這樣做,因爲靜態檢查器不知道你的代碼將如何被調用。但是,如果您已在公共API上聲明代碼合約,則調用者可以使用靜態檢查器來驗證其代碼是否正確。

所以要回答你的問題,你應該期待你的來電者遵守你的合同。如果調用者實際上不遵守合同,則應使用重寫器插入支票並以受控制的方式失敗。

MSDN Magazine有一篇關於Code Contracts的文章,這是瞭解這些概念的好起點。

+0

*將Contract.Requires()語句放在源文件中並不會生成任何實際的IL代碼*這並不完全準確。在CC的.NET實現中,除非重寫器已經處理了程序集,否則這會向無條件拋出異常的方法添加一個調用。 –

+0

@Pavel Gatilov:只有在使用'Contract.Requires ()'時纔會拋出異常。非通用版本標有'[Conditional(「CONTRACTS_FULL」)]',所以除非你定義'CONTRACTS_FULL',否則沒有呼叫。 –

+0

你說得對。看起來我太習慣於使用'Full'檢查級別。感謝您糾正我的錯誤。 –

2

除了帕維爾·加季洛夫的回答,請記住,你可以隨時拋出特殊例外情況爲前提

Contract.Requires<ArgumentException>(foo != null, "foo"); 

這些可以被捕獲,並在使用者處理,並能爲他們提供的處理無效的手段輸入例如:如果輸入一些無效數據,則向用戶顯示警告。

0

我一直在尋找解決方案,以瞭解如何捕獲合同條件拋出的異常。明確拋出可能發生的異常總是一個好主意。無論你想抓住他們,所以你的代碼不會停下來一個不錯的大爆炸;取決於什麼數據被驗證。我也使用合約進行用戶輸入驗證。通過合同前提條件,您可以強制用戶輸入以符合特定要求(如不爲空或空字符串)。其次,您可以使用合約來驗證您自己的內部代碼(特別是計算),並且不僅強制執行參數輸入,還強制執行結果的計算是有效的。

可以通過合同條件捕捉異常;只需調用代碼在一個try-catch塊內,並明確地捕獲你的條件將拋出的異常類型。我只會通過用戶輸入驗證來做到這一點。因爲當合同條件設置不僅驗證參數,而且必要的代碼邏輯拋出錯誤;您的代碼邏輯可能會出錯,而不是參數值。在這種情況下,最好完全停止程序。但是如果你希望你的程序以更受控制的方式終止,你可以抓住它們。然後由您決定驗證是否安全讓程序繼續運行

我發現它也可以檢查事件的空引用(至少由你自己創建)。我在我自己的示例代碼中使用它,它也捕獲合同拋出的錯誤。您需要傳遞事件或調用事件的對象作爲訪問事件的附加參數。以下代碼是我在其中一個類中的一部分:

public delegate void Transactie(Rekening rekening);//signature for events 
    public Transactie RekeningUittreksel; 
    public Transactie NegatiefSaldo; 

public void Storten(decimal bedrag,Transactie actie) 
    { 

     Contract.Requires<NullReferenceException>(actie!=null,"\n\nNo event listeners have been added yet!\n\n"); 
     VorigSaldo = Saldo; 
     Saldo += bedrag; 
     RekeningUittreksel(this); 

    } 
public void Afhalen(decimal bedrag,Transactie actie,Transactie actie2) 
    { 
     Contract.Requires<NullReferenceException>(actie!=null,"\n\nNo event listeners have been added yet!\n\n"); 
     Contract.Requires<NullReferenceException>(actie2 != null, "\n\nNo event listeners have been added yet!\n\n"); 
     VorigSaldo = Saldo; 
     if (bedrag <= Saldo) 
      { 
       Saldo -= bedrag; 
       RekeningUittreksel(this); 
      } 
      else 
      { 
       NegatiefSaldo(this); 
      } 
    } 

接下來是程序main方法的一部分。我註釋了添加事件偵聽器的行,所以上面定義的合約規則將拋出空引用異常。這是怎麼追上他們沒有與大爆炸終止:

 //mijnZichtrekening.RekeningUittreksel += pietjePek.ToonUittreksel; 
     //mijnZichtrekening.NegatiefSaldo += pietjePek.ToonNegatief; 
     try 
     { 
      mijnZichtrekening.Storten(50m, mijnZichtrekening.RekeningUittreksel); 
     } 
     catch (NullReferenceException ex) 
     { 
      Console.WriteLine(ex); 
     } 
     try 
     { 
      mijnZichtrekening.Afhalen(100m, mijnZichtrekening.RekeningUittreksel, mijnZichtrekening.NegatiefSaldo); 
     } 
     catch(NullReferenceException ex) 
     { 
      Console.WriteLine(ex); 
     } 

我改寫了一些代碼有點通過使用新的.NET 4.5合同abbreviators執行空引用有關事件的檢查:

public void Afhalen(decimal bedrag) 
    { 
     NegatiefSaldoHasListeners(this.RekeningUittreksel, this.NegatiefSaldo);//calls the contract abbreviator with delegate type parameters to check for Nullreference 
     VorigSaldo = Saldo; 
     if (bedrag <= Saldo) 
      { 
       Saldo -= bedrag; 
       RekeningUittreksel(this); 
      } 
      else 
      { 
       NegatiefSaldo(this); 
      } 
    } 

    public void Storten(decimal bedrag) 
    { 
     UittrekselHasListeners(this.RekeningUittreksel);//calls the contract abbreviator with a delegate type (event) parameter to check for Nullreference 

     VorigSaldo = Saldo; 
     Saldo += bedrag; 
     RekeningUittreksel(this); 

    } 


    public virtual void Afbeelden() 
    { 
     Console.WriteLine("Rekeningnr: {0:0000 0000 0000 0000}",Nummer); 
     Console.WriteLine("Saldo: {0}",Saldo); 
     Console.WriteLine("Creatiedatum: {0:dd-MM-yyyy}",CreatieDatum); 
    } 

    [ContractAbbreviator] 
    public void CheckArgs(string nummer, Klant eigenaar) 
    { 
     Contract.Requires<ArgumentException>(!String.IsNullOrWhiteSpace(nummer), "Geen nummer ingevuld!"); 
     Contract.Requires<FormatException>(nummer.Trim().Length == 16,"Ongeldig aantal tekens ingevoerd!"); 
     Contract.Requires<ArgumentException>(!String.IsNullOrWhiteSpace(eigenaar.ToString()), "Eigenaar niet opgegeven!"); 
    } 

    [ContractAbbreviator] 
    public void UittrekselHasListeners(Transactie actie) 
    { 
     Contract.Requires<NullReferenceException>(actie != null, "\n\nGeen event listener toegewezen!\n\n"); 
    } 

    [ContractAbbreviator] 
    public void NegatiefSaldoHasListeners(Transactie actie,Transactie actie2) 
    { 
     Contract.Requires<NullReferenceException>(actie != null, "\n\nGeen event listener toegewezen!\n\n"); 
     Contract.Requires<NullReferenceException>(actie2 != null, "\n\nGeen event listener toegewezen!\n\n"); 
    } 
+2

如果出現'NullReferenceException',可能最好使用'ArgumentNullException'。 –