|In Praise of Top Down Programming
|Written by Mike James
|Friday, 31 March 2023
These days, top-down modular programming is pushed aside by object-oriented programming when it comes to teaching how to program. But there is a place for both methodologies to co-exist and top-down programming solves the ever-present problem of how to begin.
Programming Is Hard
* Recently revised
How do you solve a problem?
For a programmer this is the same question as "how do you create a program?".
If you can build an algorithm to do something then you have a solution to a problem. Programming is not only the process of solving problems it also presents a number of important tools that help you solve problems. Over the years programmers have looked at what they do and have abstracted a number of principles and what used to be called "top-down modular design" is perhaps the most important.
Unfortunately with the introduction of object-oriented ideas, top-down has become less obviously an important principle. and this is a huge shame.
Let's first take a look at the basic idea.
Suppose I ask you to write a chess program. If you want this in a wider context I am asking you to solve the problem of chess.
All problems have programs as their solution all programs solve some problem.
So how do you begin your solution?
This is the "blank sheet of paper" challenge. The first word is always the most difficult, and so it is with programming. But not if you take top-down modular programming as your philosophy and approach. All you have to do in this case is call the procedure/function/subroutine that plays chess:
Of course it's a cheat in that no such PlayChess procedure exists at the moment, but if it did the problem would really be solved.
This simple, almost silly, step gets you over the blank page difficulty and gets you started. It transfers your attention to the task of implementing the PlayChess() procedure, which isn't so tough as long as you keep the same approach in operation:
Easy - job done and problem solved.
Well, no. It is clear we haven't actually got a solution but we have got closer to one. The point is that we are again using procedures that don't exist, but this in itself isn't wrong and it does move us towards the solution a layer at a time.
So we continue to implement the procedures that we assume exist at a higher level. At some point we reach a level of specification where it is possible to write some code that actually does something. If you are familiar with the jargon of trees, we reach a terminal node in the procedure hierarchy.
This is the whole principle of top-down modular programming.
It is also known as step-wise refinement and divide and conquer.
There are problems with this approach - of course there are.
The most obvious is that you could eventually reach a procedure that you have no idea how to implement. At least this focuses your mind on why you can't, at the moment, solve the problem.
Another potential problem is that without a global design in place your modular decomposition might be inefficient, or might not work at all. This is the sort of problem that refactoring can help with and at least you now have something to refactor.
There are deeper problems that top-down design doesn't solve, but who said it was the solution to everything. In particular it implies a hierarchical flow of information down and up the procedure dependency hierarchy without any guide as to how to organize the data. Interestingly, this is where objects come in useful as a way of organizing the data.
It is also said that the hierarchical design makes it difficult to maintain, because a change has to propagate, but this is probably not accurate. In a well designed system, layers in the hierarchy are isolated and only communicate via their parameters. Changes should honor the procedural boundaries, but this is an ideal and in practice procedural hierarchies get out of control just like inheritance hierarchies.
Of course top-down doesn't give you a way to find completely new algorithms. I doubt that you could stumble upon the quicksort algorithm, for example, by simply assuming that there exists a sort procedure as part of a top-down implementation. This is just another manifestation of the fact that top-down isn't a panacea, just a useful approach to generating large systems and making progress on solving problems.
So far so good.
Top-down modular programming was the methodology of choice until about 1980. What happened then is that object-oriented design became the dominant approach simply because objects were, and are, powerful.
The dominant flavor of object-oriented programming was, and is, class-based. strongly-typed, objects. You define a class and derive an object from it. The classes form a hierarchy, by way of inheritance, and this is the type hierarchy that is used to enforce compiler time checking of what you can and cannot do.
This seems to be something like the top-down design as it moves from the general - the base class - to the more specific - the derived class. This is sort of true in that you might design a vehicle class and then derive a car, bus and motorcycle class from it, but it fails on so many counts. It is much more complicated than the top-down principle as applied to constructing procedures. In fact, it is so fragile that many programmers think that inheritance is a mistake and should be avoided if possible.
Inheritance isn't a straightforward application of the top-down principle because it isn't an example of delegation. When you write playchess() as a procedure call you are assuming that it will do the job and it does that job by delegating the task to further procedures. When you create a vehicle class you aren't delegating anything. When you derive a car class from a vehicle class this again isn't delegation but specialization. It's all very different from the way procedures are used in top-down procedural programming.
Now we come to a good question.
Does top-down modular design still exist in an object oriented world?
You can argue that it does because it still applies to the design of methods. So you might solve the chess playing problem by defining a Chess object which had a play method which moved piece objects around the board object. The play method is essentially our original playchess() procedure and you can apply top-down methods to break it down by delegation into other method calls.
However, you can already see that the object approach imposes a completely different subdivision on the problem. Object-oriented design isn't top-down, even when it pretends to be. The decomposition provided by objects is a model of the real world, which is after all where the idea originated - i.e. in the Simula language and the practice of creating simulations.
It is usually said that top-down modular programming really only applies to procedural programming and once you move to objects it isn't useful. You can even go further and claim that the big problem with top-down is that it ignores the data completely, whereas decomposing the problem into objects puts the procedures where they belong, i.e. with the data they work with.
Today the idea of top-down modular design isn't taught as much as it used to be and the main reason seems to be the difficulty of making it fit in with the object-oriented approach and this is regrettable. After all you still have to write procedural code within an object-oriented world and top-down still works well.
Programming Is Hard
* Recently revised
or email your comment to: email@example.com
|Last Updated ( Friday, 31 March 2023 )