Preconditions and postconditions constitute a contract between a method and its caller. Pre are the obligations of the caller, whereas post are those of the method. The heavier preconditions are the more burden they put on the caller, whereas more postconditions means more cases that the method must handle; hence the question of balancing pre and post. There is, of course, no general answer and the right choices to make depend on the context of use and other constraints.
The preconditions of a method state the conditions that must hold before the method is called. Thus the caller must ensure that the preconditions are satisfied when calling the method. This is practical for the programmer implementing the method, as she can ignore situations that fall beyond what is permitted by the preconditions.
/** * @requires element not in this * @modifies this * @effects Adds the given element to this */ void add(Object element)
In the above example, the
add method makes the assumption that the element to add is not already in the set. This allows the implementation to ignore duplicated elements. Accordingly, the postconditions don’t mention this case.
Methods with preconditions are said to be partial, as the behavior of the method is defined for only a subset of the possible inputs.
The drawback of partial methods is that the method may have inappropriate behavior if the caller, for any reason, does not satisfy the preconditions. This can lead to nasty bugs if undetected. Moreover, the caller must have access to any means to verify the preconditions before calling the method, e.g. through the use of other methods. In our example, we expect the class to provide a method to check the existence of a given element in the set.
The temptation to favor partial methods to reduce implementation time is strong. However, one must be concerned with the risk that the caller does not satisfy the preconditions. When preconditions are not too complex, it is good practice to do defensive programming, i.e. use assertions to defensively check preconditions. Nonetheless, the responsibility of satisfying preconditions still falls upon the caller, which should never assume that a method does defensive checks.
As opposed to partial methods, one finds total methods, that is, methods without preconditions, which accept any combination of inputs.
/** * @modifies this * @effects Adds the given element to this * @throws DuplicateException if the given element is already in this */ void add(Object element) throws DuplicateException
This alternative specification of the
add method is total. Indeed, it throws an exception when the caller attempts to add a duplicate element, an illicit case that was rejected by the precondition of the previous specification. The method is safer to use, as the caller hasn’t to perform any verification prior to the call.
Total methods do come at some costs, though. First, the implementation is more complex given that it has to handle more cases. Second, more test cases are needed to achieve maximal test coverage (whereas it is not needed to test partial methods on illicit input cases, regardless of whether these methods perform defensive checks). This may sum up to a substantial addition of work, although many input cases could be unlikely to occur.
When should I use partial / total methods?
Now that you are aware of the pros and cons of total and partial methods, it’s up to you to decide when to use them. There’s no hard and fast rule; common sense prevails. In our next post, we’ll discuss practical cases where total and partial methods are more suitable and give the maximal return on investment. Stay tuned!
How do you handle special input cases? When do you use partial methods and total methods?