CSC/ECE 517 Fall 2009/wiki3 9 rp
The Non-Redundancy Principle
In Bertrand Meyer's Object Oriented Software Construction, he outlines many principles to increase reusability by managing complexity in software design. Some of these apply primarily to object oriented programming, primarily the principles dedicated to abstract data types. Most of the principles, however, apply to programming generally. The first five discussed in the book are often discussed in articles (the "Open-Close Principle" perhaps being the most popular), but equally important principles are discussed later in the book as well. One such principle, presented in the section on Contracting For Software Reliability, is called The Non-Redundancy Principle.
Definition
As with all principles presented in Object Oriented Software Construction, the principle itself is quite terse:
Under no circumstances shall the body of a routine ever test for the routine's precondition.
As simple as this may seem on first blush, it has extensive implications.
Before we discuss the implications, it is proper to provide a bit of context regarding preconditions, postconditions and programming by contract.
Context
Meyer outlined the notion of programming by contract, which boils down to a methodology to enforce software modularity by clearly defining the relationships between various components, much in the same way relationships between individuals and/or companies are formalized in contracts.
In Chapter 11, Meyer indicates that for each party in the contract, the aspects of the contract include:
- Rights
- Obligations
- Preconditions
- Postconditions
In short, these indicate what is expected of the supplier and the client (terms describing the roles of objects that are party to the contract). Specifically, a precondition is a minimum criteria that must be satisfied on the part of the client for the contract to be valid. The heart of the Non-Redundancy Principle is who checks that the preconditions are satisfied, and when are those checks made.
It must be decided whether the client or the supplier of a service should enforce the preconditions. Once that is made clear, the software designer must decide when the precondition will be enforced, and once it is enforced, it should never be checked.
Meyer goes to some length to justify this position, which flies in the face of the defensive programming mantra, which generally encourages as much checking as possible, ostensibly to help eliminate errors "just in case". As we will see in the following sections, and as Meyer describes, it is difficult to justify this approach, purly on the grounds that such redundant checking inordinately increases the complexity of the software.
Implications
There are a variety of implications that emerge from the Non-Redundancy Principle.
DRY Principle
A common mantra in programming is "Don't Repeat Yourself" or simply "DRY" for short. In it's simplest form, the Non-Redundancy Principle is a specialization of DRY, in that it prohibits developers from repeating themselves.
Consider the example that Meyer uses of a function responsible for returning a real positive square root of a number. In Java, such a function might look like:
/** * Returns the postive real square root of the input, d. */ public double sqrt(double d) { return Math.sqrt(d); }
What happens if the number passed in is negative? Will the method return a real square root? We have specified in the supplier (in this case, the method called "sqrt") is obligated to return a positive real square root. By specifying a precondition that the input must be greater than or equal to zero, we now also qualify that obligation by imposing it only if the precondition is satisfied.
/** * Returns the postive real square root of the input, d. * Precondition: input d >= 0 */ public double sqrt(double d) { return Math.sqrt(d); }
Now we have a precondition. But what does all this have to do with the Non-Redundancy Principle?
It might be tempting to then write this:
/** * Returns the postive real square root of the input, d. * Precondition: input d >= 0 */ public double sqrt(double d) { if (d < 0) throw new IllegalArgumentException("Input must be greater than or equal to zero."); return Math.sqrt(d); }
But, according to the Non-Redundancy Principle, this is prohibited! Why? Well, what does Math.sqrt do? Does it also check for a value less than zero? It turns out that it does. But the point here is that if we use the code directly above this paragraph, our system now checkes for numbers less than zero twice. Not only have we repeated ourselves, but we have increased the complexity of the code, which brings us to how the Non-Redundancy Principle reduces code complexity.