Software Design: Top-Down or Bottom-Up?
Recently, I was working on the design of a new software system. As the lone designer and programmer, I had the requirements in my head, and had made lists on my white board of some constraints and pitfalls that I knew I had to design around. But I was having trouble. Over a few days of working on the design on and off, I kept getting stuck. No fun at all.
I'm a top-down, big picture person. I like to work in the abstract, and I work best when I can form a high level structural picture of something in my head and then drill further down into details from that high level. For example, when I design a code module, such as a class, I like to build the structure first, making frequent use of stub procedures, holding off on committing to too many details until I am confident in how the overall structure is going to come out.
(Not all modules are complex enough to be so interesting, of course. I probably picked up this habit from Code Complete; in fact, one reason that Code Complete resonated so strongly with me (damn) almost ten years ago is that I get the sense that Steve is this kind of top-down thinker (not that I'm trying to compare myself), and his thinking about programming is very rooted in the the structured design and programming tradition, which I've always thought of as tending to be top-down in nature. Somehow it seems hard to visualize an elegant modularization of a system-level design problem--maximizing strong cohesion and loose coupling, with good data structures--without taking a high-level view. Ah, but can one evolve into an elegant system design (I'm talking *system* design, with multiple layers and components) by developing only one module at time? And would good refactoring and automated unit testing be essential tools in attempting such a feat? Man, I've digressed big time here.)
I think my affinity for top-down design has something to do with a pattern matching, generalization, and taxonimization (is that a word?) process that goes on in my head constantly. As I'm processing smaller bits of information or ideas, I generalize a level beyond the factotum itself and determine where it fits. Which category? Where in the big picture? Do I need a new category? Does this information invalidate current assumptions?...You get the idea. I'm not trying to say that this is better than someone else's way of thinking; it's just the way my brain works.
Because of this, when designing software I tend to start with the big picture, forming a mental framework into which I can build smaller mental models, and then I test these subsystem mental models against the higher level mental model of the system. If I encounter something at the lower levels that runs against the grain of the higher level, then I have to adjust the higher level model to fit. Through an iterative process of this I can end up with a system design that is self consistent and relatively complete. As I complete the design during the coding phase, I can refine things further--but I almost never find that I have to rethink the whole design once I've mentally gone through this exercise.
So last week I was having trouble. I couldn't come up with a high level design to accomodate my lower level concerns, so each iteration of the process described above would short-circuit. I was pacing in front of the white board, trying out various designs, working through the angles. Sometimes I would switch to researching some aspect of the requirements to settle a question or validate an assumption. After a few days the frustration and lack of progress was really bothering me, like some kind of personal failing, and it starting creating stress in other non-work aspects of my life.
As Robert Glass points out in his essay "The Cognitive View: A Different Look at Software Design," this type of process is normal for software designers (and probably other kinds of designers as well--a friend of mine wrote to me to say the essay really resonated with him as a mechanical engineer). Glass writes, "failure is an essential part of successful design." Having this knowledge in the back of my mind was some comfort, but only some. So I decided to try a change in technique.
I told myself to forget the high level, to abandon my comfortable and familiar top-down design process, and focus instead on a smaller part of the problem--namely one of the subsystems that I understood pretty well. I decided to jump right into building this lower level component, hoping that on the other side I'd not only have the component that I need, but also have a clearer idea of the overall system design.
It worked! Taking a bottom-up approach, moving from the abstract on the white board into some concrete subsystem coding, made the top-down view of things become clear, almost magically. By the time I had finished coding this one DLL, even though I didn't touch the white board or consciously run through any additional high-level-design attempts, the system design was clear to me. When I was done with the DLL, I walked to the white board and sketched out the whole system design. I'm still not sure how it happened.
This delayed epiphany was no doubt influenced by simply setting the problem aside for some time, letting my subconscious mind work on it a bit. But I have the feeling it was more than that. My subconscious had already had three nights to chew on this one, after all.
I think somehow my bottom-up view of things was more revealing than the top-down. It might have also been that working at the bottom levels of my problem for awhile allowed me to clear some clutter, to get rid of questions that were distracting my from the questions I really needed to focus on.
I'll be paying attention to this phenomenon going forward to see if I can find ways to improve my personal design process. What is your process?
Dan
Here be dragons!
My personal style seems to be a bit different from that of most people I've read. I start with a broad outline, fill in a few details where it's obvious and mark a few things as unknowns (those pesky dragons!).
Once I've got that done, I'll take a look at some of the processes and try to determine whether we really know what's going on or whether it's equivalent to magic (it happens and it usually works, but nobody can really explain how or why).
Now that I have my 'dragons' and 'magic' identified, I spend a little time with them so see if there is any obvious approach and figure out where in my 'map' they fit. Usually, I have pretty good success isolating them within the slightly less-broad outline I have by now, so I focus on getting something done.
I usually have my greatest success when I start with the easy stuff. Everytime I've got something useful done, I go back to my outline and make it slightly less broad. I'm sometimes still surprised at how often the 'dragons' and 'magic' just seem to disappear.
I'm now pretty much convinced that the hard problems only look hard because I don't have the right stuff in place yet.
Change of Perspective
I think it's cool how a conscious decision to shift your approach yielded such results, Dan. I'm going to keep this in mind for the future.
I typically work similarly, trying to establish a big-picture understanding/mental model of the system before I deal with many specifics. But I'm starting to see (and your entry here helps cement it) how the Agile approach of limited modelling/design and focus on real working software can be equally effective.
bottom-up -- OO
Meyer explicitly associates bottom-up with object-orientation. The basic motivation of structuring software around what changes least -- the data, rather than procedures -- suggests approaching the small scale first. And the easier re-arrangeability of a set of small parts suits making the design emerge gradually.
Bottom-Up, OO, and Systems
Thanks for this additional perspective on Meyer's association of OO and a bottom-up design approach. I am definitely going to need to bump that book up higher on my reading list. So keeping in mind that I have not read Meyer's work, where this bottom-up idea seems to break apart is when I try to apply it at the *system* level.
The systems I'm typically tasked to design and build are not "object-oriented systems," even though some of the components of the system may be coded in an object-oriented language using a bottom-up object-oriented approach. Rather they are just "systems," pulling together a heterogeneous mix of technologies--relational databases, web servers, application servers and object brokers, xslt transformation engines, report writing engines, data visualization tools, scripts, message queues, "fat client" user interfaces, etc. Some of the code (as in stored procedures) will be inherently procedural (or at least hybrid declarative-procedural), some of the code will be OO, some of the code will be XSLT files, some of the code will be in a proprietary report writer format--you get the idea. I suppose I have a hard time conceiving this kind of system-level design without taking a very top-down approach.
Upon further reflection after writing my original post above, I think what I did when I switched tasks was to stop concentration on my system design task and instead focused for awhile on a component design task. I suspect it was the change in context, getting my mind in a different mode, that made the system design come together so easily after I finished the component--but I think it's more complicated than that.
So my point is not to disagree with your insight about bottom-up and OO, but to wonder whether there are different parallel conversations.
Dan
Be flexible
I also have same dillemma regarding top-down or bottom up approach. Recently,I have decided to change my approach depending on requirement. So,I am not rigid now. The design is flexible enough to accomodate both.
Bottom up globally, top down locally
I guess that my rule of thumb is to design at the highest level I currently understand and implement it. Then I look at it, and try to generalise or refactor. Then I take onboard the next tranche of requirements and repeat.
So on a long timescale, I'm working bottom up, but in each iteration I'm working top down.
There's another set of problems when you are in a team. Programmers vary wildly in their toleration of uncertainty. Some people really can't make progress on details when general problems have been postponed. I don't know how to deal with that.
General Before Details Before General
These are great comments, Chris. I like the idea of top-down within an iteration.
There's another set of problems when you are in a team. Programmers vary wildly in their toleration of uncertainty. Some people really can't make progress on details when general problems have been postponed. I don't know how to deal with that.
This especially hits home with me. I'm probably one of those people who has a hard time working on details if at least certain general issues have not been solved--or if I don't have my mind wrapped around them yet. That is, I like to think in the abstract, and I tend to work on the most abstract parts of the problem first, and then as I drill into details I can test my abstractions by seeing if the details fit; if they don't, I have revisit and adjust the abstraction(s). In my earlier comment when I said that I switched to working on something low level when my high level efforts were stalled, I think it's safe to say that the low-level thing I switched to working on was either something that already fit into the generalizations that I *did* have solved already, and/or it was something that didn't have too much bearing on which way the as-yet-undetermined generalizations might break.
I like another aspect of your comment, also, and that's the suggestion that we can try to adjust our perferred ways of working to mesh better with other people we're working with who may not think in the same way we do. If you can figure out how to bring people who think in different ways into a truly complemenatary state, it seems like you'd have a powerful teamwork lever for knocking out a piece of work.
Best,
Dan


The little things
I try to work Top-Down too, but as you've written, sometimes I find it's best to write something - even just a few simple methods to get started with - and get a real feel for what is needed. The brain doesn't deal with total abstraction well, so I find a bit of meat can help you work out what the rest of the animal was! :)