简体   繁体   English

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

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

This is for a java class I'm taking. 这是我正在参加的java课程。 The book mentions preconditions and postconditions but doesn't give any examples how to code them. 该书提到了先决条件和后置条件,但没有给出任何如何编码它们的例子。 It goes on to talk about asserts, I have that down, but the assignment I'm doing specifically states to insert preconditions and test the preconditions with asserts. 它继续讨论断言,我把它断了下来,但我正在做的赋值具体说明插入前置条件并用断言测试前置条件。

Any help would be great. 任何帮助都会很棒。

Languages like Eiffel support "preconditions" and "postconditions" as a basic part of the language. 像Eiffel这样的语言支持“前置条件”和“后置条件”作为语言的基本部分。

One can make a compelling argument that the whole purpose of an "object constructor" is precisely to establish "the class invariant". 人们可以提出一个令人信服的论点,即“对象构造函数”的整个目的正是建立“类不变”。

But with Java (as with just about every other post-C++ object oriented language), you pretty much have to fake it. 但是使用Java(就像几乎所有其他的C ++面向对象语言一样),你几乎不得不伪造它。

Here's an excellent tech note on leveraging Java "assert": 这是一篇关于利用Java“断言”的优秀技术说明:

Some examples given here express preconditions as parameter validation, and exercise them in assertions. 这里给出的一些例子将前提条件表示为参数验证,并在断言中运用它们。 A non-private method should always perform parameter validation as its caller is out of scope of its implementation. 非私有方法应始终执行参数验证,因为其调用方不在其实现范围内。

I tend to argue that parameter valdiation does not constitute a precondition, in the context of object oriented systems - although I see plenty of examples of this approach in the google-sphere. 在面向对象系统的背景下,我倾向于认为参数valdiation并不构成前提条件 - 尽管我在google-sphere中看到了很多这种方法的例子。

My understanding of contracts started with [BINDER1999]; 我对合同的理解始于[BINDER1999]; which defined invariant, precondition, and postconditions in terms of the state of the object-under-test; 它根据被测对象的状态定义了不变量,前置条件和后置条件; expressed as Boolean predicates. 表示为布尔谓词。 This treatment considers how the state-space encapsulated by a class is managed, in which methods represent transitions between states. 此处理考虑如何管理类封装的状态空间,其中哪些方法表示状态之间的转换。

Discussion of pre- and post-conditions in terms of parameter and return values is much easier to convey than discussions in terms of state-spaces! 在参数和返回值方面讨论前后条件比在状态空间方面的讨论更容易传达! So I can see why this view is much more prevalent. 所以我可以看出为什么这种观点更为普遍。

To recap, there are three types of contract under discussion: 总结一下,正在讨论的合同有三种类型:

  • The invariant is a test on the object-under-test which is true from the end of construction (any constructor), to the start of its destruction (although it may be broken while a transition is taking place). 不变量是对被测对象的测试,从构造结束(任何构造函数)到破坏的开始都是如此(尽管在发生过渡时它可能会被破坏)。
  • A pre-condition is a test on the object-under-test which must be true for the method-under-test to be invoked on the object-under-test. 前提条件是对被测试对象的测试,对于要在被测试对象上调用的被测方法,该测试必须为真。
  • A post-condition is a test on the object-under-test which must be true immediately after the method-under-test completes. 后置条件是对被测试对象的测试,在被测方法完成后必须立即进行测试。

If you adopt the (sensible) approach that overloaded methods must be semantically equivalent, then pre- and post-conditions are the same for any overload of a given method in a class. 如果采用(明智的)方法,重载方法必须在语义上等效,那么对于类中给定方法的任何重载,前置条件和后置条件都是相同的。

When interitance and overridden methods are considered, contract-driven-design must follow the Liskov Substitution Principle; 在考虑干预和改写方法时,合同驱动设计必须遵循Liskov替代原则; which results in the following rules: 这导致以下规则:

  • The invariant of a derived class is the logical-AND of its local invariant, and that of its base class. 派生类的不变量是其本地不变量及其基类的逻辑与。
  • The pre-condition of an overridden method is the logical-OR of its local pre-condition, and that of the method in its base class. 重写方法的前提条件是其本地前置条件的逻辑或,以及其基类中方法的逻辑或。
  • The post-condition of an overridden method is the logical-AND of its local post-condition, and that of the method in its base class. 重写方法的后置条件是其本地后置条件的逻辑AND,以及其基类中的方法的逻辑AND。

Remember, of course, that whenever a pre- or post-condition is tested, the invariant for the class-under-test must also be tested. 当然,请记住,无论何时测试前置或后置条件,都必须测试被测试类的不变量。

In Java, contracts can be written as protected Boolean predicates. 在Java中,契约可以写为受保护的布尔谓词。 For example: 例如:

// 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
}

Don't roll the invariant predicate into the pre- or post-condition predicates, as this would result in the invariant being called multiple times at each test-point in a derived class. 不要将不变谓词滚动到前置或后置条件谓词中,因为这会导致在派生类中的每个测试点多次调用不变量。

This approach uses a wrapper for the method-under-test, the implementation for which is now in a private implementation method, and leaves the body of the implementation unaffected by the contract assertions. 这种方法使用一个包装器来测试方法,其实现现在是一个私有实现方法,并使实现的主体不受合同断言的影响。 The wrapper also handles exceptional behaviour - in this case, if the implementation throws and exception, the pre-condition is checked again, as expected for an exception-safe implementation. 包装器还处理异常行为 - 在这种情况下,如果实现抛出异常,则再次检查前置条件,正如预期的异常安全实现一样。

Note that if, in the example above, 'foo_impl_()' throws an exception, and the subsequent pre-condition assertion in the 'finally' block also fails, then the original exception from 'foo_impl_()' will be lost in favour of the assertion failure. 请注意,如果在上面的示例中,'foo_impl_()'抛出异常,并且'finally'块中的后续先决条件断言也会失败,那么来自'foo_impl_()'的原始异常将会丢失而有利于断言失败。

Please note that the above is written off the top-of-my-head, so may contain errors. 请注意,以上内容是从我的头顶写的,因此可能包含错误。

Reference: 参考:

  • [BINDER1999] Binder, "Testing Object-Oriented Systems", Addison-Wesley 1999. [BINDER1999] Binder,“测试面向对象系统”,Addison-Wesley 1999。

Update 2014-05-19 更新2014-05-19

I have gone back-to-basics with regards to contracts on input and outputs. 关于投入和产出合同,我已经回归基础。

The discussion above, and based on [BINDER1999], considered contracts in terms of the state-space of objects-under-test. 上面的讨论基于[BINDER1999],根据被测物体的状态空间来考虑合同。 Modelling classes as strongly encapsulated state-spaces is fundamental to building software in a scalable manner - but that is another topic... 将类建模为强封装的状态空间是以可扩展的方式构建软件的基础 - 但这是另一个主题......

Considering how the Lyskov Substitution Principle (LSP) 1 is applied (and required) when considering inheritance: 考虑在考虑继承时如何应用(和要求)Lyskov替换原则(LSP) 1

An overridden method in a derived class must be substitutable for the same method in the base class. 派生类中的重写方法必须可替换基类中的相同方法。

To be substitutable, the method in the derived class must not be more restrictive on its input parameters than the method in the base class - otherwise then it would fail where the base class method succeeded, breaking LSP 1 . 要可替换,派生类中的方法对其输入参数的限制必须不比基类中的方法更严格 - 否则它将在基类方法成功的地方失败,从而破坏LSP 1

Similarly the output value(s) and return type (where not part of the method signature) must be substitutable for that produced by the method in the base class - it must be at least as restrictive in its output values otherwise this also would break LSP 1 . 类似地,输出值和返回类型(不是方法签名的一部分)必须可替代基类中的方法生成的 - 它必须至少在其输出值中具有限制性,否则这也会破坏LSP 1 Note that this also applies to the return type - from which rules on co-variant return types can be derived. 请注意,这也适用于返回类型 - 可以从中派生关于共变量返回类型的规则。

Therefore contracts on the input and output values of an overridden method follow the same rules for combination under inheritance and pre- and post-conditions respectively; 因此,重写方法的输入和输出值的合同遵循相同的继承条件和前后条件下的组合规则; and in order to implement these rules effectively they must be implemented separate from the method to which they apply: 为了有效地实施这些规则,它们必须与它们适用的方法分开实施:

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

Note that these are almost identical to foo_pre_() and foo_post_() respectively, and should be called in the test harness at the same test-points as these contracts. 请注意,它们分别与foo_pre_()foo_post_()几乎相同,并且应该在与这些合同相同的测试点的测试工具中调用。

The pre- and post-conditions are defined for a method-family - the same conditions apply to all overloaded variants of a method. 为方法族定义了前置条件和后置条件 - 相同的条件适用于方法的所有重载变体。 The input and output contracts apply to a specific method signature; 输入和输出合同适用于特定方法签名; however, to use these safely and predictably, we must understand the signature-lookup rules for our language and implementation ( cf. C++ using ). 但是,为了安全且可预测地使用这些,我们必须理解我们的语言和实现的签名查找规则( 参见 C ++ using )。

Note that in the above, I use the expression Parameters... p as short-hand for any set of parameter types and names; 请注意,在上面,我使用表达式Parameters... p作为任何一组参数类型和名称的简写; it is not ment to imply a varatic method! 暗示一种varatic方法并不意味着什么!

Just use assert to code the preconditions. 只需使用assert来编写前置条件。 For example: 例如:

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

A precondition is a condition that has to hold at given point in the execution of a program, if the execution of the program is to continue correctly. 如果程序的执行要正确继续,前提条件是必须在程序执行的给定点保持的条件。 For example, the statement "x = A[i];" 例如,声明“x = A [i];” has two preconditions: that A is not null and that 0 <= i < A.length. 有两个先决条件:A不为空,0 <= i <A.length。 If either of these preconditions is violated, then the execution of the statement will generate an error. 如果违反了这些先决条件中的任何一个,那么语句的执行将产生错误。

Also, a precondition of a subroutine is a condition that has to be true when the subroutine is called in order for the subroutine to work correctly. 此外,子程序的前提条件是当调用子程序以使子程序正常工作时必须为真的条件。

Here is Detail & 这是细节

Here is example: preconditions-postconditions-invariants 这是一个例子: preconditions-postconditions-invariants

To apply preconditions and postconditions technique in Java you need to define and execute assertions at runtime. 要在Java中应用前置条件和后置条件技术,您需要在运行时定义和执行断言。 It actually allows to define runtime checks inside your code. 它实际上允许在代码中定义运行时检查。

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

Please follow details of the above code here 在此处关注上述代码的详细信息

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM