繁体   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