Search code examples
javafileexceptiondesign-by-contractpreconditions

When to add a precondition and when to (only) throw an exception?


I am learning about preconditions and when to use them. I have been told that the precondition

@pre fileName must be the name of a valid file

does not suit in the following code:

/**
Creates a new FileReader, given the name of file to read from.
@param fileName- the name of file to read from
@throw FileNotFoundException - if the named file does not exist,
is a directory rather than a regular file, or for some other reason cannot
be opened for reading.
*/
public FileReader readFile(String fileName) throws FileNotFoundException {
. . .
}//readFile

Why is this?

Edit: Another example

We are assuming that the following, as an example, is done the "right" way. Note the IllegalArgumentException and the precondition. Note how the behavior is well defined, and how the throws declaration is made even though a precondition is set. Most importantly, notice how it doesn't contain a precondition for the NullPointerException. Once again, why doesn't it?

/**
* @param start the beginning of the period
* @param end the end of the period; must not precede start
* @pre start <= end
* @post The time span of the returned period is positive.
* @throws IllegalArgumentException if start is after end
* @throws NullPointerException if start or end is null
*/
public Period(Date start, Date end) f

Are these examples avoiding use of extra preconditions? One could argue that if we are avoiding preconditions, then why have them at all? That is, why not replace all preconditions with @throws declarations (if avoiding them is what is done here)?


Solution

  • Ok, so this is what I've found out:

    Background

    Based on the following principles, as described in Bertrand Meyer's book Object Oriented Software Construction:

    "Non-Redundancy principle Under no circumstances shall the body of a routine ever test for the routine’s precondition." - Bertrand Meyer

    "Precondition Availability rule Every feature appearing in the precondition of a routine must be available to every client to which the routine is available." - Bertrand Meyer

    , these two points answer this question:

    1. For preconditions to be useful, the client (user of method) has to be able to test them.
    2. The server should never test the precondition, because this will add complexity to the system. Although, assertions are turned on to do this testing when debugging the system.

    More on when, why, and how to use preconditions:

    "Central to Design by Contract is the idea, expressed as the Non-Redundancy principle, that for any consistency condition that could jeopardize a routine’s proper functioning you should assign enforcement of this condition to only one of the two partners in the contract. Which one? In each case you have two possibilities: • Either you assign the responsibility to clients, in which case the condition will appear as part of the routine’s precondition. • Or you appoint the supplier, in which case the condition will appear in a conditional instruction of the form if condition then ..., or an equivalent control structure, in the routine’s body.

    We can call the first attitude demanding and the second one tolerant." - Bertrand Meyer

    So a precondition should only exist if it is decided that the client holds the responsibility. Since the server should not test the precondition, the behaviour becomes undefined (as also stated on Wikipedia).

    Answers

    • The first point answers the first example.
    • As for the second example, it is probably not done the right way. This is because the first @throws declaration implies that the method has (other than an assertion) tested the precondition. This violates the second point.

    As for the null pointer; this shows that the null pointer responsibility is assigned to the server. That is, using a "tolerant attitude", as opposed to a "demanding attitude". This is perfectly OK. If one chose to implement a demanding attitude, one would remove the throws declaration (but more importantly; not test for it), and add a precondition declaration (and perhaps an assertion).