bennettscash
bennettscash
DBC vs. TDD Part 3 - Conclusion?
Monday, 27 October 2008
Finally - Part three of my internal debate about the differences between Design by Contract and Test-Driven Development.
This is a week late because I’ve been on training for the past week. In fact, by the time I manage to find internet access and publish this it’ll probably be a week and a half late because I’m STILL on training, but this time in a different city - which just makes things even more difficult.
This week I’m going to (attempt to) tie together the last two articles and draw some sort of conclusion about where I think DbC and TDD should live.
OK, now that we’ve explored the definitions of DbC and TDD where does that leave us?
Well, we have a clear understanding that DbC is a methodology to ensure that a given unit of a system will produce the output expected of it, provided that the information passed to it is fit for purpose. We also know that it is a tool to encourage developers to step back and look at the forest before they begin coding - That is, it requires the person responsible for the unit of work to clearly define what the unit will do, and in which circumstances it will do it.
With regard to TDD, we know that it is a methodology to ensure that a given project artefact is fit for purpose - that is, it has been verified by testing a sufficiently diverse sample of use cases to cover all possibilities.
Again, TDD is also a tool that encourages taking a broader perspective - Before you start working on something you think about what criteria define success for that piece of work, and you write tests to verify those criteria.
So far TDD and DbC are neck and neck - They both sound like they’re offering exactly the same benefits.
But there are a couple of less-obvious differences...
1. When you’re following DbC and you think you’re finished you probably still need to write and execute tests (and then iterate). When you’re following TDD you only think you’re finished when all of your tests pass.
2. The paragraph on TDD isn’t talking specifically about units of a system, although it is including them. It is also including collections of units within a system, the entire system, and a collection of systems - TDD should be followed just as closely at every level within a project, beginning by defining the acceptance criteria for the project and ending by defining tests to verify the correctness of individual units within the project.
This really means - I believe - that DbC is a great thing. It’s a really great thing, and it’s probably not unlikely that it was a significant contributor to the development of TDD (I may be wildly incorrect in that statement, and I probably am, but you have to admit it’s possible!).
Just like all really great things, eventually something better comes along. Usually the something better is excommunicated or put to death as well, but eventually it comes to be accepted as better.
DbC offers so much benefit at the unit level within a software project, and TDD offers all of these benefits at all levels within a software project, from the unit level right up to the UAT level.
There are other arguments that are often used to justify DbC’s continued existence, and I want to run through them now because, frankly, I think they’re ridiculous.
1. Runtime assertion checking
Do you really want to do this? If you’re learning how to program at school then go for it, but checking your preconditions is just making work for yourself that you really don’t need to do. It’s also breaking the fundamental precept of DbC (or at least one of them) - That your unit has undefined behaviour if the starting contract is not met.
Throwing an exception isn’t undefined.
2. Contracts are general whereas tests are specific
True. How do you test your contracts?
Regardless of whether we’re DbC’ing, TDD’ing, or ABC’ing, we can only ever test specific cases. Our contracts might validate that our code works for the general case, but we only know that our contracts work for specific cases.
And while we’re on this point, once you’re happy with the specific cases in which you’ve tested your contracts you will disable any runtime, assertion-throwing contract checks for the reasons outlined in (1), won’t you?
3. Contracts discourage defensive programming
The thinking behind this point eluded me for awhile because the people who make the claim usually claim (1) first (which certainly IS defensive). But then a great colleague explained that defensive programming is discouraged because you’ve documented everything you expect to be true when your method is called, and you only write enough code to cater for this.
While I don’t believe that DbC discourages defensive programming, I do believe that it tries to, so I’ll put the difference between theory and practice aside for now.
This argument is 100% correct. Defensive programming means you’re doing more work than you need to, which is a bad thing.
However, I don’t think people need the reminder to be lazy anymore. We already have:
(a) Larry Wall’s three virtues of a programmer: Laziness, Impatience and Hubris.
(b) TDD encouraging people to stop coding as soon as their tests pass.
(c) Agile methodologies becoming increasingly-common and resulting in people running around saying “YAGNI”* rather than doing any exception checking at all - which is a much better thing to worry about than whether they’re doing enough.
4. Contracts encourage defensive programming
Yeah, go figure. I think this argument has something to do with runtime assertion checking so you can be sure that (a) your code has valid inputs, and (b) it does the right thing with them.
See (1).
5. DbC and TDD are complementary, damnit!
Maybe you disagree with my definitions of one or both of them, in which case that’s fine. But if not, this statement is right only insofar as it’s correct to say that lumberjacking and building timber furniture are complementary (I know that’s a terrible example - I’m sitting in a hotel without enough stimulus to come up with anything better. And I suppose it would be quite a convenient way to cut out the middleman if you were a furniture builder and a lumberjack. Damn. Let’s try again).
But if not, this statement is right only insofar as it’s correct to say that putting air into your car tyres and getting new tyres fitted are complementary - With one you get air-filled tyres; with the other you get air-filled tyres, new tread, a wheel balance, and you get it all done by Joe your local mechanic without lifting a finger yourself.
Anyway, if you haven’t gotten my point by now those two examples are certainly not going to help you.
In a phrase: The advantages realised by DbC are a subset of those realised by TDD, and a small subset at that.
* “You Aren’t Gonna Need It”. Like “TODO” but more agile, and hence more cool.
How do you like my completely unrelated image that looks slightly relevant because it contains two lists??