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

陌路散爱 提交于 2019-12-05 05:57:26

Languages like Eiffel support "preconditions" and "postconditions" as a basic part of the language.

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.

Here's an excellent tech note on leveraging Java "assert":

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.

My understanding of contracts started with [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; 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.

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. 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.

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.

Update 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. 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:

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.

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. 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.

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).

Note that in the above, I use the expression Parameters... p as short-hand for any set of parameter types and names; it is not ment to imply a varatic method!

Just use assert to code the preconditions. 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];" has two preconditions: that A is not null and that 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

To apply preconditions and postconditions technique in Java you need to define and execute assertions at runtime. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!