Chris over at bennettscash.id.au recently wrote a series of 3 articles on Design by Contract and Test Driven Development and came to the conclusion that TDD essentially replaces and displaces DbC, offering all its benefits plus more.
Like Chris, I left uni with the idea the DbC was A Good Thing. This was reinforced buy OOP courses I did at work and general experience. And like Chris, I've been drawn to and sold on TDD as a methodology for vastly improving the quality of systems. However there's a vital point that I think Chris has missed in his analysis: ease of reuse and maintainability.
If you're like me, you write in a DbC style even if you don't write the contacts first. That is (for trusted consumers) you don't check pre-conditions, and you take no responsibility for your actions if those pre-conditions aren't met. That's all well and good, except when someone else needs to use or maintain your code.
By using a DbC style without specifying contracts you create implicit contracts between modules. This is fine when you're coding, because you know roughly what each module is doing anyway - it's rare that I've read contracts that I've written to find out what something does. But if someone else wants to use that module they need to either read the code to find out what it does, treat it as untrustworthy and program defensively, or just pray that it does what they think it should.
You may argue that they could just look at the tests, but the tests are not a definition - they're specific executions of the definition. Plus they're rarely stored with the code itself and are not particularly easy to read. Definitely not as easy as a simple contract.
As with Chris, the idea of programatically checking pre-conditions boggles me. It flies in the face of DbC, and to this day I cannot work out what Meyer was thinking when, in the same chapter of OOSC, he goes from warning of the evils of useless input checking code to outlining how pre-conditions should be checked.
So what do I think should be done? Well, both, but in a specific way.
Defining contracts in a simple (and I mean simple!) Pre/Post/Invariant way is a useful way to describe a module, because it gives more rigor than the usual couple of line block comment. It's also something you do, in your head, before you start writing your TDD tests anyway. So, when I'm working on 'real' systems, I like to:
- Define the pre and post-conditions in a block at the start of the module
- Go through the TDD process, building up tests that exercise the "contract"
- Do not check pre or post-conditions - leave that to the tests