32

As I understand, top-down design is by refining the abstract high level concept into smaller concrete and comprehensible parts, until the smallest building block is defined. On the other hand, bottom up defines low level parts, then gradually build up higher level blocks until the whole system is formed.

In practice, it is said best to combine the two methods: starts with high level specification to fully specify the domain knowledge, its relationship and constraints. Once the problem is well understood, smallest building blocks are created to build up the system.

The process of:

  • Creating requirement spec
  • Create a design spec (with diagrams)
  • Implement
  • Deliver
  • Repeat (in iterative development, rather than doing a whole chunk in each phase, we do a little bit each repeatedly, and got daily meeting to adapt to customer's dynamic requirement)

looks perfectly normal to me (with specs as plans). It has its flaws but that's why we got iterative development: instead of spending time on one phase, says, requirement analysis to study every possible thing in domain knowledge which is subjected to changes (possibly daily), we do a little bit of analysis, a little bit of design and then implement it.

Another way is that each iteration is a mini-waterfall fashion, where analysis is done in a few days (or a week). The same applies for design. The rest of time is spent for implementation. Is there something inherently wrong with top-down approach in combination with iterative development?

In his essay Programming Bottom Up, Paul Graham seems to encourage build from bottom up completely, or program it from bottom up, but not the requirement analysis/design phase:

Experienced Lisp programmers divide up their programs differently. As well as top-down design, they follow a principle which could be called bottom-up design-- changing the language to suit the problem.

As far as I get, what he meant is that Lisper still performs top-down design, but program bottom up, is that true? Another point he wrote:

It's worth emphasizing that bottom-up design doesn't mean just writing the same program in a different order. When you work bottom-up, you usually end up with a different program. Instead of a single, monolithic program, you will get a larger language with more abstract operators, and a smaller program written in it. Instead of a lintel, you'll get an arch.

Does this means that during the period of writing a program in Lisp, you end up with a generic tool?

yannis
  • 39,547
  • 40
  • 183
  • 216
Amumu
  • 824
  • 2
  • 8
  • 14
  • Perhaps if you provide a link to the book, paper, or article that you are referring to, others might be able to make a more reasoned statement with regards to your question. Also, it's unclear what your question is specifically about. Are you asking for advice about some specifics with regards to a design you wish to implement, or asking questions about the article you are referencing. Perhaps this would be easier to read and answer if you broke it down into a couple of questions asked separately. – S.Robins Feb 12 '12 at 12:19
  • @S.Robins To sum up, I demonstrated my usual top-down approach to develop software. However, some people prefer bottom up, and one of them is Paul Graham, which is a famous person. I ask to understand how bottom up design helps in general, and specifically in Lisp as it has special features to encourage (as Paul suggested). – Amumu Feb 12 '12 at 13:08
  • 1
    Amumu I've edited out the example, as both answers seems to have skipped it anyway. Please try to keep your questions concise, and ask only _one question per question_. – yannis Feb 12 '12 at 15:40
  • top down is better and more natural. – Nasser May 07 '23 at 22:10

5 Answers5

38

Top-down is a great way to describe things you know, or to re-build things that you've already built.

Top-down biggest problem is that quite often simply there is no "top". You will change your mind about what the system should do while developing the system and while exploring the domain. How can be your starting point something that you don't know (i.e. what you want the system to do)?

A "local" top down is a good thing... some thinking ahead of coding is clearly good. But thinking and planning too much is not, because what you are envisioning is not the real scenario (unless you've already been there before, i.e. if you are not building, but re-building). Global top-down when building new things is just nonsense.

Bottom-up should be (globally) the approach unless you know 100% of the problem, you need just the known solution to be coded and you don't care about looking for possible alternative solutions.

Lisp approach is the distilled bottom-up. You not only build bottom up but you can also shape the bricks the way you need them to be. Nothing is fixed, freedom is total. Of course freedom takes responsibility and you can make horrible things by misusing this power.

But horrible code can be written in any language. Even in languages that are shaped as cages for the mind, designed with the hope that with those languages even monkeys could get good programs up and running (an idea so wrong on so many levels that it hurts even just thinking about it).

Your example is about a web server. Now in 2012 this is a well-defined problem, you have specs to be followed. A web server is just an implementation problem. Especially if you are aiming at writing a web server substantially identical to the other gajillion of web servers that are out there then nothing is really unclear, except some minutiae. Even your comment about RSA is still talking about a clearly defined problem, with formal specifications.

With a well defined problem, with formal specifications and already known solutions then coding is just connecting in the dots. Top down is ok for that. This is the project manager heaven.

In many cases however there is no proven well-known approach to be used to connect the dots. Actually very often is hard to say even what are the dots.

Suppose for example you are asked to instruct an automatic cutting machine to align the parts to be cut to a printed material that is not perfectly conforming to the theoretic repetitive logo. You are given the parts and pictures of the material as taken by the machine.

What is an alignment rule? You decide. What is a pattern, how to represent it? You decide. How to align the parts? You decide. Can parts be "bent"? It depends, some not and some yes, but of course not too much. What to do if the material is just too deformed for a part to cut it acceptably? You decide. Are all the material rolls identical? Of course not, but you cannot bug the user to adapt alignment rules for every roll... that would be impractical. What pictures are seeing the cameras? The material, whatever that may mean... it can be color, it can be black over black where just the light reflex makes the pattern evident. What does it mean to recognize a pattern? You decide.

Now try to design the general structure of a solution for this problem and give a quote, in money and time. My bet is that even your system architecture... (yes, the architecture) will be wrong. Cost and time estimation will be random numbers.

We implemented it and now it's a working system, but changed our mind about the very shape of the system a big number of times. We added entire sub-systems that now cannot even be reached from the menus. We switched master/slave roles in protocols more than once. Probably now we've enough knowledge to attempt re-building it better.

Other companies of course did solve the same problem... but unless you are in one of these companies most probably your top-down detailed project will be a joke. We can design it top-down. You cannot because you never did it before.

You can probably solve the same problem too. Working bottom-up however. Starting with what you know, learning what you don't and adding up.

New complex software systems are grown, not designed. Every now and then someone starts designing a big new complex ill-specified software system from scratch (note that with a big complex software project there are only three possibilities: a] the specification is fuzzy, b] the specification is wrong and self-contradictory or c] both... and most often [c] is the case).

These are the typical huge-company projects with thousands and thousands of hours thrown into powerpoint slides and UML diagrams alone. They invariably fail completely after burning embarrassing amounts of resources... or in some very exceptional case they finally deliver an overpriced piece of software that implements only a tiny part of the initial specs. And that software invariably is deeply hated by users... not the kind of software you would buy, but the kind of software you use because you're forced to.

Does this mean that I think that you should think only to code? Of course not. But in my opinion the construction should start from bottom (bricks, concrete code) and should go up... and your focus and attention to detail should in a sense "fade" as you are getting farther from what you have. Top-down is often presented as if you should put the same level of detail to the whole system at once: just keep it splitting every node until everything is just obvious... in reality modules, subsystem are "grown" from subroutines. If you do not have a previous experience in the specific problem your top down design of a subsystem, module or library will be horrible. You can design a good library once you know what functions to put in, not the other way around.

Many of the Lisp ideas are getting more popular (first class functions, closures, dynamic typing as default, garbage collection, metaprogramming, interactive development) but Lisp is still today (among the languages I know) quite unique in how easy is to shape code for what you need.

Keyword parameters for example are already present, but if they were not present they could be added. I did it (including keyword verification at compile time) for a toy Lisp compiler I am experimenting with and it doesn't take much code.

With C++ instead the most you can get is a bunch of C++ experts telling you that keyword parameters are not that useful, or an incredibly complex, broken, half backed template implementation that indeed is not that useful. Are C++ classes first-class objects? No and there's nothing you can do about it. Can you have introspection at runtime or at compile time? No and there's nothing you can do about it.

This language flexibility of Lisp is what makes it great for bottom-up building. You can build not only subroutines, but also the syntax and the semantic of the language. And in a sense Lisp itself is bottom-up.

6502
  • 731
  • 6
  • 8
  • Yes, what I meant was a local top down. You keep refining the requirement and evolve it through the iterations. The point of top-down is that we want to get the code structure as correct as possible. Continuously refactoring. For example, at first you want to pass a string to a function, but later you need a whole class to satisfy the changes, and refactoring is time consuming if the function is already used everywhere. – Amumu Feb 12 '12 at 10:57
  • To re-build something clearly known in a clearly known way then top-down is ok. Your web browser is an example of this. Even using a language like C++ that forces you to anticipate and formally specify (using a terrible syntax) all the types for the values held in all variables of your program is still a viable option. – 6502 Feb 12 '12 at 15:30
  • I don't think my example is a clearly known way, because I assume it is made by someone with basic knowledge of data communication, and the C++ code is the result of very basic design from the domain knowledge (in this case, networking). It is an example of evolution design. In the next iteration, the developer learns more about the domain knowledge, so he extends his design to fit the evolved knowledge (such as added security aspects in every layer of the web server). Then he refines the implementation according to the new design. – Amumu Feb 12 '12 at 17:16
  • The point here is that the actual solution derived from a domain independent language (such as natural language, math language... depends on domain). The act of coding is just a detailed mapping from the generated solution to code. – Amumu Feb 12 '12 at 17:18
  • For example, in order to implement an algorithm such as RSA, a developer must fully understand it. After understanding it, the developer designs a solution (on paper or tools, which later can be generated), then fill the code in the body. The point here is that the domain knowledge should be comprehensible before coding starts. How can one code without understanding clearly what he's going to make? – Amumu Feb 12 '12 at 17:24
  • Thanks for your detailed edited answer. I can see how bottom up helps to experiment, make us feel more secured and generate a deliverable artifact. However, wheter top or bottom, a developer still has to understand the domain, such as the cutting machine you referred to. If it was me, I would still learn the domain knowledge, then generate an initial design to identify the relationships between related components, then implement. – Amumu Feb 13 '12 at 02:43
  • Of course, since domain is unclear, more time is needed for bottom-up experiment. However, planning ahead a little bit to know what to do bottom-up is still my preferred method. The design and the implementation will evolve together, rather than leaving it completely bottom-up which later I would get into integration problems between components. I guess it depends on the language. I will try Lisp to see for myself soon. – Amumu Feb 13 '12 at 02:47
  • I think that what is wrong in the top-down model is the idea that you can think to details of what you don't know. – 6502 Feb 13 '12 at 07:57
  • Wow, look at how your post evolves in response my comments. I get your point . It's just like book writing. Some people love to follow their thoughts, and restructure after they reach a certain point (like 30 pages or so), while some other people prefer to define the outline of the document first, then fill the empty section later. But I agree on using bottom up to build up a little bit of domain understanding is better than plan for the unknown. – Amumu Feb 13 '12 at 08:20
  • 1
    Hi Mr.6502. After a year, as I learned more things, I start seeing what you said become truer. I made another thread: http://programmers.stackexchange.com/questions/179000/is-this-how-dynamic-language-copes-with-dynamic-requirement . If you have time, I hope you would clarify my thinking again :D . – Amumu Dec 11 '12 at 08:10
7

I'm not sure how this answer would apply to Lisp, but I just finished reading Agile Principles, Patterns and Practices, and the author, Uncle Bob, strongly advocates top-down approach for C# (also applicable to C++) with which I fully agree.

However, unlike some other answers that drew a conclusion that top-down approach means that in the first interaction you only deliver documents and overall design, the book points to another method: TDD combined with evolutionary design.

The idea is that you start from the top and define your highest abstraction levels (or local highest) and as soon as they are defined you make them do useful work so that feature #1 is immediately working. Then as you add more and more features you refactor your code and evolve design as needed while always staying aware of SOLID principles. This way you will not end up with too many abstraction layers and you won't end up low-level design that doesn't fit the overall architecture. If you are not sure what this means, the book I mentioned above has a whole chapter with an example where developers take concepts and start with UML diagrams and low level classes, only to realize half those classes are not needed once coding actually begins. In essence with this approach, low-level code is naturally pushed down as more high-level details are defined in the project.

And lastly, if you practice SOLID, you shouldn't run into a situation where you have high-level abstractions defined and then get into details and all of a sudden find that there's no OOD or abstractions. That's not really the fault of top-down design, but lazy engineering.

If you are interested to read more about XP and evolutionary design, here's a good read from Martin Fowler, another great author: "Is Design Dead?"

DXM
  • 19,932
  • 4
  • 55
  • 85
  • Perfect. This is what I'm talking about. Also it's nice to know about SOLID principle, and Agile supports top-down approach (I thought that with TDD and XP, people jump to code asap and avoid documentation). – Amumu Feb 13 '12 at 02:23
  • @Amumu: I added a link to another article written by Fowler. You can read more about the difference between cowboy coding with no design/documentation vs. what XP/Agile is actually promoting. While personally, I strongly believe documentation has value and I've been actively promoting my team to keep up our design docs, I've been gradually shifting my views. Now our "design tasks" are all whiteboard/napkin type of things, while actual docs are updated only after story is written. We've also been scaling back on what is actually documented so docs only cover high-level/architecture stuff – DXM Feb 13 '12 at 02:44
3

To me, the most important remarks Paul Graham makes in his article are these:

[...] Lisp programmers [...] follow a principle which could be called bottom-up design-- changing the language to suit the problem. In Lisp, you don't just write your program down toward the language, you also build the language up toward your program.

Or, as it is known in C++ circles: Library Design is Language Design (Bjarne Stroustrup)

The main idea of top down design is: first you plan, then you code. Beanow is correct when he writes, that there are problems when executable code comes late in the process. In bottom up design you always have code, and that code that can be tested.

Also, your code is not flat. By that I mean, it tends to have more levels of smaller abstractions. In top down design people often end up with big abstrations down to some arbitrary level and below that no abstractions at all. Code designed bottom up OTOH often contains less low level control and data structures since they are likely to be abstracted away.

pillmuncher
  • 853
  • 1
  • 6
  • 7
2

Ideally, writing a program in any language, not just Lisp, lets you write a whole set of generic tools that can speed you along on your next program or speed enhancements to your current one.

In practice, keeping track of these tools can be difficult. Documentation is poor and disorganized and people who know about them leave. In practice, reusing code is often more trouble than it's worth. But if the code is documented and organized properly, and programmers stick around (or keep their own supply of useful code) a vast amount of work can be saved by grabbing code from the warehouse rather than rebuilding it.

All design has to be top down or you wouldn't know what you were doing. Are you building a shed or a car? You can't figure out that one with bottom-up design. But if you are going to build a left front wheel, you might think that you might need more wheels later, both for this project and for others. And if you build a reusable wheel you'll have four for the price of one. (And 18 for that tractor-trailer you're building next, all for free.)

Note that unlike with real cars and real wheels, if you've built one software "wheel" you've built an infinite number of them.

More on top-down design: while you have to start out with this, I feel obligated to point out that if you cannot build a wheel, you need to find that out before you do much work on your car. So you do need to work bottom-up almost in parallel with working top-down. Also, knowing you can build a wheel might suggest a lot of projects you would not have thought of before, like the tractor-trailer. I do think the top-down approach has to dominate, but with a very light touch.

So, to continue paraphrasing Paul Graham, ideally when you write a program you end up with lots of reusable parts that can save lots of time on both the original program and in others. To distance myself slightly from Paul Graham, this works in any language (though some encourage the process more than others).

The enemy of this almost magical process are programmers with very short-term outlooks. This may result from personality defects, but more commonly from switching jobs and responsibilies too quickly, both inside and between employing companies.

RalphChapin
  • 3,270
  • 1
  • 14
  • 16
  • Wow I got replied from 16 Feb and the inbox didn't show anything. I agree with you, it's always needed to have a plan, even a very light one, and then build up a little, and then refine the plan. It's perfectly reasonable. However, I don't seem to understand why many developers love to plan everything in their head while writing code. A fair amount of time can be saved and they can focus to work more on the real problem rather than continuous refactor the code architecture along the implementation process. – Amumu Feb 24 '12 at 02:46
  • @Amumu One reason for planning while writing: Some programmers know so much about what they are doing the keyboard and mouse are their coding bottleneck, not the planning process. (Reusable code would enable them to do new, creative work that would let thinking be the hard part again.) – RalphChapin Feb 24 '12 at 15:25
  • @Amumu Another reason for planning while writing: with some language/programmer/problem combinations, the language is the best way to think about the problem. The language is the easiest way to put your thoughts on "paper". I've only read little bits here and there about Lisp, but from that I suspect it is especially good for this. (Some languages were first created as a way to express a problem rather than get a machine to solve it.) If it's as easy to rearrange your code as your thoughts, may as well code. But the programmer, language, and problem must blend nicely for this to work. – RalphChapin Feb 24 '12 at 15:30
  • It's dependable. The idea of planning is to separate the domain knowledge and relationship identifications between domain concepts to the actual programming process. Imagine if a class is used by dozen of other classes, without a well plan on creating the interface to communicate with other classes, we can easily get into integration and refactoring hell. In this case, the time wasted for typing and restructure the code greatly exceeds the time spent for identifying on paper. – Amumu Feb 24 '12 at 20:18
  • Let's take an example: Suppose we are writing a client/server application. The first thing we have to identify the protocol by defining the exchange messages, how the message is laid out in memory (i.e. header 16 bytes, id 4 bytes....). Diagram is not equal to modelling/planning: http://abstratt.com/blog/2009/05/03/on-code-being-model/ . We do not need to use UML or diagram to model, since UML pretty much focuses only on OOP, and the time spent typing in the UML class diagram is not less than typing the actual code, if not more. – Amumu Feb 24 '12 at 20:24
  • Planing/modeling is for concept checking, identify what to do, what's the relationships, what's the constraints in the present/future, what's the alternative flows. It seems that languages like Lisp operates on high level view which enables us to explore the concepts quite natural while writing code. Unlike structured language, because it's structured, if it's unstructured, it's bad (I almost finish the series of the article "How to tell stuffs to computer" here: http://lisperati.com/tellstuff/scientist.html , so I can see how it helps in this case, to model without diagrams) – Amumu Feb 24 '12 at 20:29
  • That's my rationale behind the planning process. – Amumu Feb 24 '12 at 20:29
  • Don't know who said it but it rings true to me. "Weeks of programming can save you hours of planning." – Dunk Mar 14 '14 at 18:17
1

What's wrong with top-down approach in combination with iterative development?

Using a top-down approach with iterative development does not deliver any working code in the first iterations. It delivers design documents and such which the customer will have a hard time with giving feedback on. Responses like, "yeah I guess (this is too abstract for me)" will not help you along in further specifying the specifics of the desired system by the customer.

Instead you only create a general overview of what is requested. (Requirements) to use as a general guideline for what defines a finished product and what deserves priority to implement. From there on you create working products for every iteration which the customer can actually toy with to see if this was what they had in mind. If not, then it won't be an issue as there has not been spent hundreds of hours into designing a system that works different from what the customer asks now.

did Paul Graham encourage build from bottom up completely? Or just program it from bottom up, but not the requirement analysis/desing phase?

I will not speak for another person. Also I didn't read his essay. The answer I'm giving you comes from my education as well as experiences.

This means, during the period of writing a program in Lisp, you end up with a generic tool which can be used to write similar programs of the original one, doesn't it?

Using this approach means that you will end up with more abstract building blocks. Simply because you will have to build standalone subsystems that can be presented straight away you can't create a top-down design that is heavily intertwined and has to be implemented all at once. It improves re-usability, maintainability but most of all allows more flexible response to change.

asthasr
  • 3,439
  • 3
  • 17
  • 24
Beanow
  • 149
  • 1
  • 6