Top-down and bottom-up development
Most readers will be familiar with the terms “top-down” and “bottom-up”. A while ago, I did an observation that put these terms in new light. It gave me new insights in how a code base can become unmaintainable, but also gives an approach to get to a better maintainable code base.
For those who are unfamiliar with the terms bottom-up and top-down, let me explain them first.
Top-down development
Top-down development, or rather top-down thinking, is done when you first take the most high-level view of the product, problem, whatever you’re working with.
For software, that would be a problem statement, or the most high-level specification. From that level, you create the design of the main components of the product. Then, you work out the each of these components and their interactions. The main components can again be subdivided into smaller components, until you reach the code level. That will result in a complete specification tree.
Top-down is all about decomposing into smaller parts. That is why the example of the specification tree works so well: the very nature of requirements and designs imposes that they are developed in a top-down approach; specifications are ideas to which details can be added. You can’t actually build software by decomposing a big ball of mud into clear parts.
Bottom-up development
Bottom-up is exactly the other way around. You start with what you have: the individual parts. You assemble these parts into a functioning component. Multiple of these components can in turn be combined into higher-level components, until you have a complete product.
Bottom-up is all about assembling smaller parts into a greater whole, where the sum of the whole is greater than its parts.
Bottom-up is about assembling what you have to compose a bigger whole. The very nature of building software is that parts are combined into a bigger whole; source code is all details to be combined. You can’t assemble specifications of loose components and expect to have a working software system.
Software redesign
A while ago, I spoke a colleague that had done a redesign of a fairly large software component to make it reusable. He had developed a nice approach which came down to the following steps:
- List all functions/methods of the component by looking at the source code.
- Group those functions by responsibility. This will result in a tree of responsibilities. Using this tree of responsibilities, it becomes clear what the component has to do.
- Use these responsibilities to define a vision for the desired design.
- Work towards the design in small increments be moving and separating responsibilities one at a time.
The observation
What has this to do with bottom-up or top-down? When we spoke about his solution, we observed that most changes to the software in the past were made in a bottom-up fashion. In other words, changes were made straight to the code without thinking about broader concepts. This resulted in many responsibilities in the wrong place and many interrelated dependencies. The approach of my colleague did actually reverse this: he used the responsibilities in the source code to develop a top-down view of the system, which he then used as input for his refactoring strategy. The bottom-up steps had already been done in the past, now he was doing the top-down part.
When developing software the “right” way, code is only written when required by a specification. This specification is the design of a module together with its requirements, and those requirements are derived from higher-level specifications, all the way up to the definition of the problem that the product must solve. The software modules are then integrated into components and further into a working system. So here the top-down steps are taken first when specifying, then the bottom-up steps follow when building the product. Also note that there is some cycle visible here: both the top-down and bottom-up approaches are required in order to have maintainable software.