Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

This is for a java class I'm taking. 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.

like image 753
Jay Rohrssen Avatar asked Nov 18 '12 18:11

Jay Rohrssen


People also ask

What is a precondition of a method in Java?

A precondition is a condition that must be true for your method code to work, for example the assumption that the parameters have values and are not null. The methods could check for these preconditions, but they do not have to. The precondition is what the method expects in order to do its job properly.

What is a precondition of a method?

A precondition is a condition, or a predicate, that must be true before a method runs for it to work. In other words, the method tells clients, “this is what I expect from you”. So, the method we are calling is expecting something to be in place before or at the point of the method being called.

What happens if a precondition is not met?

If a precondition is violated, the effect of the section of code becomes undefined and thus may or may not carry out its intended work. Security problems can arise due to incorrect preconditions. Often, preconditions are simply included in the documentation of the affected section of code.

Is a precondition an assertion?

assert is for sanity checks during testing, whereas precondition is for guarding against things that, if they happen, would mean your program just could not reasonably proceed.


2 Answers

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":

  • http://docs.oracle.com/javase/8/docs/technotes/guides/language/assert.html
like image 103
paulsm4 Avatar answered Sep 24 '22 21:09

paulsm4


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 validation 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 Liskov 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 meant to imply a variadic method!

like image 32
simon.watts Avatar answered Sep 21 '22 21:09

simon.watts