如何在java类方法或构造函数中插入前置条件?

时间:2012-11-18 18:53:58

标签: java assert design-by-contract preconditions

这是我正在参加的java课程。该书提到了先决条件和后置条件,但没有给出任何如何编码它们的例子。它继续讨论断言,我把它断了下来,但我正在做的赋值具体说明插入前置条件并用断言测试前置条件。

任何帮助都会很棒。

5 个答案:

答案 0 :(得分:4)

像Eiffel这样的语言支持“前置条件”和“后置条件”作为语言的基本部分。

人们可以提出一个令人信服的论点,即“对象构造函数”的整个目的是正是来建立“类不变量”。

但是使用Java(就像其他几乎所有的C ++面向对象语言一样),你几乎不得不伪造它。

这是一篇关于利用Java“assert”的优秀技术说明:

答案 1 :(得分:4)

这里给出的一些例子将前提条件表示为参数验证,并在断言中运用它们。非私有方法应始终执行参数验证,因为其调用方不在其实现范围内。

我倾向于认为参数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 _()'抛出一个异常,并在最后的'中随后的先决条件断言阻止也失败,那么来自&f; 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_()几乎相同,应该在与这些合同相同的测试点的测试工具中调用。

为方法族定义了前置条件和后置条件 - 相同的条件适用于方法的所有重载变体。输入和输出合同适用于特定方法签名;但是,为了安全和可预测地使用这些,我们必须理解我们的语言和实现的签名查找规则( cf。 C ++ using)。

请注意,在上文中,我使用表达式Parameters... p作为任何参数类型和名称集的简写; not 意味着暗示一种varatic方法!

答案 2 :(得分:0)

只需使用assert对前提条件进行编码即可。例如:

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

答案 3 :(得分:0)

如果要正确执行程序,前提条件是必须在程序执行的给定点保持的条件。例如,声明“x = A [i];”有两个前提条件:A不是空的并且0 <= i&lt;则为a.length。如果违反了这些前提条件中的任何一个,则执行该语句将产生错误。

此外,子程序的前提条件是在调用子程序时必须为真的条件才能使子程序正常工作。

Here is Detail&amp;

以下是示例: preconditions-postconditions-invariants

答案 4 :(得分:0)

要在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;
}

请按照上述代码的详细信息here