簡體   English   中英

如何在java類方法或構造函數中插入前置條件?

[英]How do I insert a precondition in a java class method or constructor?

這是我正在參加的java課程。 該書提到了先決條件和后置條件,但沒有給出任何如何編碼它們的例子。 它繼續討論斷言,我把它斷了下來,但我正在做的賦值具體說明插入前置條件並用斷言測試前置條件。

任何幫助都會很棒。

像Eiffel這樣的語言支持“前置條件”和“后置條件”作為語言的基本部分。

人們可以提出一個令人信服的論點,即“對象構造函數”的整個目的正是建立“類不變”。

但是使用Java(就像幾乎所有其他的C ++面向對象語言一樣),你幾乎不得不偽造它。

這是一篇關於利用Java“斷言”的優秀技術說明:

這里給出的一些例子將前提條件表示為參數驗證,並在斷言中運用它們。 非私有方法應始終執行參數驗證,因為其調用方不在其實現范圍內。

在面向對象系統的背景下,我傾向於認為參數valdiation並不構成前提條件 - 盡管我在google-sphere中看到了很多這種方法的例子。

我對合同的理解始於[BINDER1999]; 它根據被測對象的狀態定義了不變量,前置條件和后置條件; 表示為布爾謂詞。 此處理考慮如何管理類封裝的狀態空間,其中哪些方法表示狀態之間的轉換。

在參數和返回值方面討論前后條件比在狀態空間方面的討論更容易傳達! 所以我可以看出為什么這種觀點更為普遍。

總結一下,正在討論的合同有三種類型:

  • 不變量是對被測對象的測試,從構造結束(任何構造函數)到破壞的開始都是如此(盡管在發生過渡時它可能會被破壞)。
  • 前提條件是對被測試對象的測試,對於要在被測試對象上調用的被測方法,該測試必須為真。
  • 后置條件是對被測試對象的測試,在被測方法完成后必須立即進行測試。

如果采用(明智的)方法,重載方法必須在語義上等效,那么對於類中給定方法的任何重載,前置條件和后置條件都是相同的。

在考慮干預和改寫方法時,合同驅動設計必須遵循Liskov替代原則; 這導致以下規則:

  • 派生類的不變量是其本地不變量及其基類的邏輯與。
  • 重寫方法的前提條件是其本地前置條件的邏輯或,以及其基類中方法的邏輯或。
  • 重寫方法的后置條件是其本地后置條件的邏輯AND,以及其基類中的方法的邏輯AND。

當然,請記住,無論何時測試前置或后置條件,都必須測試被測試類的不變量。

在Java中,契約可以寫為受保護的布爾謂詞。 例如:

// class invariant predicate
protected bool _invariant_ ()
{
    bool result = super._invariant_ (); // if derived
    bool local  = ... // invariant condition within this implementation
    return result & local;
}

protected bool foo_pre_ ()
{
    bool result = super.foo_pre_ (); // if foo() overridden
    bool local  = ... // pre-condition for foo() within this implementation
    return result || local;
}

protected bool foo_post_ ()
{
    bool result = super.foo_post_ (); // if foo() is overridden
    bool local  = ... // post-condition for foo() with this implementation
    return result && local;
}

public Result foo (Parameters... p)
{
    boolean success = false;
    assert (_invariant_ () && foo_pre_ ()); // pre-condition check under assertion
    try
    {
        Result result = foo_impl_ (p); // optionally wrap a private implementation function
        success = true;
        return result;
    }
    finally
    {
        // post-condition check on success, or pre-condition on exception
        assert (_invariant_ () && (success ? foo_post_ () : foo_pre_ ());
    }
}

private Result foo_impl_ (Parameters... p)
{
    ... // parameter validation as part of normal code
}

不要將不變謂詞滾動到前置或后置條件謂詞中,因為這會導致在派生類中的每個測試點多次調用不變量。

這種方法使用一個包裝器來測試方法,其實現現在是一個私有實現方法,並使實現的主體不受合同斷言的影響。 包裝器還處理異常行為 - 在這種情況下,如果實現拋出異常,則再次檢查前置條件,正如預期的異常安全實現一樣。

請注意,如果在上面的示例中,'foo_impl_()'拋出異常,並且'finally'塊中的后續先決條件斷言也會失敗,那么來自'foo_impl_()'的原始異常將會丟失而有利於斷言失敗。

請注意,以上內容是從我的頭頂寫的,因此可能包含錯誤。

參考:

  • [BINDER1999] Binder,“測試面向對象系統”,Addison-Wesley 1999。

更新2014-05-19

關於投入和產出合同,我已經回歸基礎。

上面的討論基於[BINDER1999],根據被測物體的狀態空間來考慮合同。 將類建模為強封裝的狀態空間是以可擴展的方式構建軟件的基礎 - 但這是另一個主題......

考慮在考慮繼承時如何應用(和要求)Lyskov替換原則(LSP) 1

派生類中的重寫方法必須可替換基類中的相同方法。

要可替換,派生類中的方法對其輸入參數的限制必須不比基類中的方法更嚴格 - 否則它將在基類方法成功的地方失敗,從而破壞LSP 1

類似地,輸出值和返回類型(不是方法簽名的一部分)必須可替代基類中的方法生成的 - 它必須至少在其輸出值中具有限制性,否則這也會破壞LSP 1 請注意,這也適用於返回類型 - 可以從中派生關於共變量返回類型的規則。

因此,重寫方法的輸入和輸出值的合同遵循相同的繼承條件和前后條件下的組合規則; 為了有效地實施這些規則,它們必須與它們適用的方法分開實施:

protected bool foo_input_ (Parameters... p)
{
    bool result = super.foo_input_ (p); // if foo() overridden
    bool local  = ... // input-condition for foo() within this implementation
    return result || local;
}

protected bool foo_output_ (Return r, Parameters... p)
{
    bool result = super.foo_output_ (r, p); // if foo() is overridden
    bool local  = ... // output-condition for foo() with this implementation
    return result && local;
}

請注意,它們分別與foo_pre_()foo_post_()幾乎相同,並且應該在與這些合同相同的測試點的測試工具中調用。

為方法族定義了前置條件和后置條件 - 相同的條件適用於方法的所有重載變體。 輸入和輸出合同適用於特定方法簽名; 但是,為了安全且可預測地使用這些,我們必須理解我們的語言和實現的簽名查找規則( 參見 C ++ using )。

請注意,在上面,我使用表達式Parameters... p作為任何一組參數類型和名稱的簡寫; 暗示一種varatic方法並不意味着什么!

只需使用assert來編寫前置條件。 例如:

...
private double n = 0;
private double sum = 0;
...
public double mean(){
  assert n > 0;
  return sum/n;
}
...

如果程序的執行要正確繼續,前提條件是必須在程序執行的給定點保持的條件。 例如,聲明“x = A [i];” 有兩個先決條件:A不為空,0 <= i <A.length。 如果違反了這些先決條件中的任何一個,那么語句的執行將產生錯誤。

此外,子程序的前提條件是當調用子程序以使子程序正常工作時必須為真的條件。

這是細節

這是一個例子: preconditions-postconditions-invariants

要在Java中應用前置條件和后置條件技術,您需要在運行時定義和執行斷言。 它實際上允許在代碼中定義運行時檢查。

public boolean isInEditMode() {
 ...
}

/**
* Sets a new text.
* Precondition: isEditMode()
* Precondition: text != null
* Postcondition: getText() == text
* @param name
*/
public void setText(String text) {
 assert isInEditMode() : "Precondition: isInEditMode()";
 assert text != null : "Precondition: text != null";

 this.text = text;

 assert getText() == text : "Postcondition: getText() == text";
}

/**
* Delivers the text.
* Precondition: isEditMode()
* Postcondition: result != null
* @return
*/
public String getText() {

 assert isInEditMode() : "Precondition: isInEditMode()";

 String result = text;

 assert result != null : "Postcondition: result != null";
 return result;
}

在此處關注上述代碼的詳細信息

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM