When defining pre- and post-conditions, an important decision is how weak or strong you want them to be. In the previous example, we handle the pre-condition very strongly: if a negative value comes in, it violates the pre-condition of the method, so we halt the program.
One way to avoid halting the program due to negative numbers would be to weaken the pre-condition. In other words, instead of accepting only values that are greater than zero, the method could accept any value, positive or negative. We could do this by removing the if
statement, as shown in the following listing (the developer would have to find a way to take negative numbers into account and handle them).
Listing 4.4 TaxCalculator
with a weaker pre-condition
public double calculateTax(double value) {
❶
// method continues ...
}
❶ No pre-conditions check; any value is valid.
Weaker pre-conditions make it easier for other classes to invoke the method. After all, regardless of the value you pass to calculateTax
, the program will return something. This is in contrast to the previous version, where a negative number throws an error.
There is no single answer for whether to use weaker or stronger pre-conditions. It depends on the type of system you are developing as well as what you expect from the consumers of the class you are modeling. I prefer stronger conditions, as I believe they reduce the range of mistakes that may happen in the code. However, this means I spend more time encoding these conditions as assertions, so my code becomes more complex.
Can you apply the same reasoning to post-conditions?
You may find a reason to return a value instead of throwing an exception. To be honest, I cannot recall a single time I’ve done that. In the TaxCalculator
example, a negative number would mean there was a bug in the implementation, and you probably do not want someone to pay zero taxes.
In some cases, you cannot weaken the pre-condition. For the tax calculation, there is no way to accept negative values, and the pre-condition should be strong. Pragmatically speaking, another way of handling such a case is to return an error value. For example, if a negative number comes in, the program can return 0 instead of halting, as in the following listing.
Listing 4.5 TaxCalculator
returning an error code instead of an exception
public double calculateTax(double value) {
// pre-condition check
if(value < 0) { ❶
return 0;
}
// method continues ...
}
❶ If the pre-condition does not hold, the method returns 0. The client of this method does not need to worry about exceptions.
While this approach simplifies the clients’ lives, they now have to be aware that if they receive a 0, it might be because of invalid input. Perhaps the method could return –1 to differentiate from zero taxes. Deciding between a weaker pre-condition or an error value is another decision to make after considering all the possibilities.
For those that know the original theory of design-by-contracts: we do not weaken the pre-condition here to make it easier for clients to handle the outcomes of the method. We decided to return an error code instead of throwing an exception. You see that my perspective on contracts is more pragmatic than that in the original design-by-contract paper by Meyer in 1992. What matters to me is reflecting on what classes and methods can and cannot handle and what they should do in case a violation happens.
Leave a Reply