I’ve been having a think about top-down (a.k.a. outside-in) design during my recent iterative development exercise. In the series I’ve been putting off testing from the client layer down, primarily because GUIs have a reputation for being hard to test and harder to test-drive, and I wanted to make some early, easy progress on the core logic of the game.
I started to think that this approach might be a mistake. I’m dealing with the model that I think we’ll need, not one demanded from the primary client of the model – the GUI. Chad and Ben mentioned in their recent screencast that bottom-up implementation tended to lead to mistaken assumptions about infrastructure required by the top layers. I saw a similar point made on the BDD mailing list by Pat Maddox. Pat wrote (emphasis mine):
“I find an outside-in style of development to be very helpful… It forces you to think of your objects at a high level, so your design is driven by real need, and then you apply your design skills as you go on. When I use a pure bottom-up style, I write more speculative code and go down the wrong path far more often than I’d like. That’s not to say that it’s a problem inherent with that style, but rather a problem that I’ve personally experienced, and have more or less solved by using an outside-in approach.”
This is opinion is echoed in an unrelated post to the TDD mailing list by Olof Bjarnason:
"I’ve been using TDD [bottom-up] for 2 years now, and it’s been mostly a _great_ experience. The thing that bothers me most with "classic TDD" is that sometimes I build too much functionality in my classes, which isn’t used in the end application after all. Even whole objects are wasted in the worst case."
The view here is that bottom-up design can lead to speculation and waste. By having a design driven directly by the overall, required behaviour, you only implement (and test) things that directly serve that behaviour. This can help eliminate speculative implementations of lower-level behaviour based on what you think the overall required behaviour will be.
Not so fast…
Sounds great! So what about inside-out / bottom-up / middle-out design? Ron Jeffries recently stated on the TDD mailing list that he generally prefers to start with the model (unless the project is simply to build a viewer). Maybe there’s a bit more to it?
Digging further into that thread on the TDD list, there are a number of great points of view on the topic. Some TDD-ists argue that bottom-up design lets you build in small, easy steps, and refactor your way to the required behaviour that you would otherwise start with in top-down design. Others state that this leads to waste – writing code and tests that just get refactored away. Which resulted in a couple of great quotes on the difference between refactored code and waste:
"I suppose we could also call the scaffold we use when constructing a large building as waste, or the safety harnesses as waste." – John Roth to TDD list
"The analogy with scaffolding for a house is an excellent one - there is a lot of "stuff" constructed when building a house, *just* to support the construction - it is then discarded." – Casey Charlton to TDD list
Top-down design can also lead to a "mockist" approach to TDD, where you need to mock all the required dependencies to implement the high level behaviour. This isn’t necessarily a bad thing, but over-reliance on mocking can result in fragile tests. Martin Fowler has a great article on the pros and cons of "classic" and "mockist" TDD.
Enough rambling already!
While planning for part 3 of my recent development exercise I was coming to the conclusion that top-down was the way to go. After looking into it some more I was reminded of a whole host of advantages of bottom-up design. Even more importantly it reminded me that there is no silver bullet, and there are times when either, or a mix of both, approaches are fine. All this started to sound familiar, so firing up Google I noticed that I had read something to this effect in Jeremy Miller’s excellent (as usual) post on the topic (search for “Bottom Up versus Top Down”, although the whole post is worth reading).
I think the most important conclusion I’ve reached during this ramble is that if you are working in iterations to deliver a complete slice of the application (top and bottom) then you’re never going to go too far wrong. Any “waste” from a bottom-up approach will be minimal as you’ll be working toward and implementing the top almost immediately. And you’ll still end up with higher-level behaviour specified with unit tests. Likewise starting top-down you’ll still get the advantages of designing in small steps, particularly as you drive down into the design.