Sunday, December 6, 2009

Design By Contract

Wanted to post on this subject for some time. There is a general lack of awareness of this powerful concept which is one of most profound ideas the professional developer can embrace along with sound object oriented principles. Next time you see a really clean piece of code - look carefully and you will probably notice DbC in action.

To start off this post, I will attempt to briefly talk about defensive programming, the antithesis of professional software development. At college along with most other computer science folk, I was taught how important it was to program defensively. This means that if a parameter can be null, then it needs to be checked to ensure that condition otherwise the program could fail. In the case of the following example, if ToUpper message is invoked on the string object and the string is nil, then what happen to the code - typically it will crash but that depends on the language.

string Convert(String str) {
// This would crash if str is NULL
if (str.ToUpper() == "XYZ")
return;
...
}


So to counter this, developers typically do the following -
string Convert(String str) {
if (str == null)
throw Exception("bad string parameter");

if (str.ToUpper() == "XYZ")
return;
...
}


To the best of my knowledge, it was "Bertrand Meyer" who formally introduced the software development community to the idea of design by contract and published it in his book "Object-Oriented Software Construction". There is an entire lengthy chapter dedicated to the subject, so I will try my best to paraphrase.

Code written to perform run time checks on parameters is unnecessary and more dangerous than the very issues it attempts to prevent. It leads to duplication with the same values being checked multiple times in different methods, it adds to cyclomatic complexity to the program which in turn decreases reliability, readability and comprehension.

Meyer - In relations between people or companies, a contract is a written document that
serves to clarify the terms of a relationship. It is really surprising that in software, where
precision is so important and ambiguity so risky, this idea has taken so long to impose
itself.


To achieve reliability and hence quality, simplicity is crucial. If there are one or two of these 'null' checks across thousands of methods in a system, then the overall complexity of the system has been increased massively. with thousands of new potential failure points being introduced.

So, in essence, design by contract says, do not be concerned with performing validity checks at the beginning of a method (the most common example being to check an object reference for null), rather let the caller know that your method expects a valid object. If the caller sends the message to Convert with a null parameter, the client is at fault, not your program! Remember that this is a contract and the customer is involved just like any other type of contract. This may seem like a simple statement, but at the most basic level, it's pretty much all there is to know. Its implications though are huge.

Looking at the earlier example in a new light -
string Convert(String str) {
if (str.ToUpper() == "XYZ") // This will still crash if str is NULL
return; // - caller is the problem, our program
... // expects a valid string object.
}


Several points on this. Applying this style tends to push validation code as high up in the system as it can reach
- don't allow bad data into your system in the first place
- don't pass null values around
- don't allow users to select invalid values

If there is a case where processing can happen without that object being present, overload the method and write one that doesn't require that object.

Non-Redundancy Principle.


"Under no circumstances should the body of a routine ever test for the routine's precondition."

In the following example, the difficulty arises with the error handling step. What is it that needs to happen - only the client knows and typically the client would have to either handle an exception or check for an error result anyway. DbC says, why not just state the contract that 'p' is required to be valid and then the checking can be done one time, by the caller, before it even reaches our code.

if (p == NULL) then
Error handling, logging, exception ...
else
do something with p ...
end


Tolerant or Demanding Preconditions.


There are two ways to view preconditions, one way is tolerant of any customer calling on them, and will try to guess at the right way to behave when unexpected input occurs. The alternative demands that input criteria are met by the customer and therefore does not need to guess at what to do with incorrect input.
It is the client who knows what to do when there is not enough information to supply the correct inputs to us and what it really means. If we are trying to decide how to handle a null reference, do we log, raise an exception, ignore the issue or what?

Sidenote on complexity


In 1976 Thomas McCabe formalized a software metric called cyclomatic complexity. Basically, it counts the edges and nodes of a control flow graph through source code with any condition (if/then/else/switch) or iteration (for/do/while/iterate) counting as additional complexity. I don't want to go into the details of this so check out Wikipedia for a more complete explanation.

Unlike this post, Meyer's chapter on Design By Contract is very detailed and I recommend you read it with an open mind. Contrary to popular belief the DbC mindset makes complete sense if a little thought is applied. You have a choice - try to recover from bad things, or don't allow bad things to occur. Hope I have managed to invoke a little interest.

1 comment:

Cory Wheeler said...

Hey Andrew, thanks for the post, as well as the references for further reading.

I once saw Dave Thomas (of The Pragmattic Programmer) give a presentation to an ACM student group on the Eiffel programming language and the design by contract features that are in Eiffel. I recall being impressed at seeing how effective it is when baked into the language. At the time I didn't realize it was Bertrand Meyer who designed the language, but after reading this and looking a little closer I do now.

Take care,
Cory