Journal Articles

CVu Journal Vol 13, #1 - Feb 2001 + Professionalism in Programming, from CVu journal
Browse in : All > Journals > CVu > 131 (16)
All > Journal Columns > Professionalism (40)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: Professionalism in Programming Part 6

Author: Administrator

Date: 06 February 2001 13:15:43 +00:00 or Tue, 06 February 2001 13:15:43 +00:00

Summary: 

Good design

Body: 

Sometimes you see some code and can do nothing but sigh.

Recently I wrote a device driver for an embedded product. The driver's interface to the OS was quite complex. The interface to the hardware I was using was also quite complex. In order to keep myself sane when writing the code, I split it into two sections. The first was an internal 'library' that accessed the hardware, performed some relevant data buffering and provided a simple API to access that buffered data. Then I wrote a second, distinct layer that implemented the fiddly OS driver interface, in terms of this internal library. The structure of this device driver was therefore like this:

Later I was sent, from the manufacturer of the hardware, a sample implementation of the same device driver. However, the author of this code had clearly not thought it out at all. The code was a sprawling mess, tightly intermingling the complex OS interface with the hardware logic in a completely incomprehensible manner. The structure was approximately that shown in the figure below.

Now, I am not trying to blow my own trumpet (any more than is necessary, anyway). The point of this illustration is clear. A two year old could tell you which of the software designs depicted above is the better. The first is clearly easier to understand because it is so much simpler. One of the signs of a mature programmer is the quality of design in their code. This design pervades more levels than the topmost 'architecture' of the implementation. (The 'architecture' is a structural design of the entire system. When we talk about highly distributed systems then this architecture encompasses the split of tasks between computers and what software components are bought in, as well as the shape of the implementation code's design. However, here we are not talking about this sort of high-level design in this article.).

There are two subtly different types of design. The first is the overall structural design which, depending on project size, may be the system architecture. This ends up written formally as a design specification [Goodliffe]. The other design stage, which we are discussing in this article, describes the decisions made in the implementation of this specification. If a programmer has a self-contained block of code to write, there will be more than one way they can write it. We all know that we should think before we type, that we should design before we code, and that we should specify before ploughing into something. But even so, how often is this observed? But what is our motivation for writing well designed code? Why not just quickly hack out what occurs to us as we type? We come back to the issue of the 'audience' for our code, which we discussed in the last article. Good design actually saves implementation time and makes it easier to detect (and therefore fix) bugs. The code is therefore easier and cheaper to maintain[1]. Getting the design right is really very important. The design of your code is the foundation upon which it is built. If you get it wrong then the code will be unstable, unsafe, not fit for purpose and possibly even dangerous. If the foundation design is not properly in place then the end result will be code that resembles the Leaning Tower of Pisa. It might even be novel if it manages to stand up under the strain of real use, however it will never be as good as it ought, and in time this cost will make itself felt. So as professional programmers we need to be able to design well. How are we going to get there? The factors that will answer this question are:

  1. What makes for good software design, and

  2. How do we learn to design well?

What makes for good software design?

First thing that we need to know in order to design well is to know the language of the target implementation. Maybe this is a choice left up to the programmer, in which case the choice of the language itself is in the design domain. However, more often than not the language is fixed and we have to come up with a design for the code structure in it.

It is important to know the language that will be used well, since this knowledge will greatly affect our design. Now, that may seem like a pretty obvious point to make, but it is frightening how much it may need to be said. I was shocked when I read the log message for some code an experienced C coder wrote: "Removed ';' after 'if' statements that meant the following blocks always executed." Now, he was not a bad software engineer, or even a bad C programmer, and it is possible that this elementary mistake was just that, a simple mistake. However, if you do not know how to use the target language properly, how can you design good code in it? This becomes more important for C++ than C (and to some extent Java) since it has such a richer set of tools for implementing particular functions.

But what is good design? It is very difficult to define; it has got a lot to do with balance and aesthetics. The Patterns movement has developed a lot of experience related to design.

Below is a list of some characteristics of well-designed code. The list is probably nowhere near exhaustive, and by itself will not guarantee a good design. However their converses are all sure indications of bad code design.

  • Well-designed code separates the client from the implementor

    Of course, you can only successfully separate the user and implementor if both parties have been successfully identified, and their individual needs are understood. The point of separation is well defined, be it function API or some middleware interface. It is both easy to find, and adequately specified, for example there are enough API functions to make the interface useable.

  • Well-designed code is elegant

    Elegant is a dangerous word. It does not mean that your code should be baroque, confusingly clever and overly complex. Possibly the most important characteristic of well-designed code is simplicity. In fact, that's so important I'll repeat it: the most important characteristic of well-designed code is simplicity.

    You should be able to look at well-designed code and see a beauty in its simplicity.

    Elegant code uses the correct constructs in the correct place. It makes use of language facilities or design patterns when and where they are needed, and avoids gratuitous use of them elsewhere. Clarity is key in the professional programmer's work. Clarity does not come from 1000 lines of comments - it comes from simple, elegantly structured code.

  • Well-designed code is as small as possible but no smaller

    This is the less is more principle, another aspect of simplicity. You should carefully work out how little code is actually necessary, and then write just that. Remember, you can always add more code later on in the product's life to add extra facilities, but you can usually never remove something that is already in the codebase.

  • Well-designed code is extensible

    It should allow extra functionality or data to be easily slotted in in the appropriate places in the future. This should not lead to over engineered code, though.

    This may be accommodated through carefully chosen class hierarchies, or callback functions, even just a fundamentally logical and extensible code structure.

  • Well-designed code associates like things

    In order to understand the code, all related things should be conceptually grouped. This can be just from being held in the same source file, to some syntactic grouping: all data held in a single structure rather than in randomly scattered global variables, for example. It is a clear aid to comprehension.

    A side affect of this is the prevention of errors when a change in one place actually affects something a million miles away. A clear sign of dense, complex code is when a single, simple change in one place leads to modifications of the code in many other places.

  • Well-designed code has a well-structured error handling convention

    In most cases, the choice of error handling mechanism will be specified by the choice of language and/or environment. However, a good design clearly understands the difference between expected errors and truly exceptional errors and clearly distinguishes between the two.

    The structure of the design will accommodate erroneous situations, and the handling will not have been hacked in at the end when the code was found not to work properly.

  • Well-designed code abstracts non-portable stuff

    Time and time again I have seen code that was never intended to be portable, so there was no requirement to design it as such. But when a new platform was used it was simpler to adapt the old code than rewrite some new code. This new platform need not be anything as radical as a different OS or hardware, just a subtle variation in a chip revision.

    Unless portability is born in mind, later in its life code can begin to look like, rather than being written by a professional programmer, it has been plumbed by a philosopher. Just making a few careful choices in how you structure OS-dependant, or hardware-dependant areas of code can pay great dividends in the future, and usually need not affect performance or clarity (sometimes it may even improve clarity).

  • Well designed code contains no duplication

    It should never have to repeat itself. Even repetition of program structure without an exact match (the sane algorithm but with different variable names, for example) is usually an indicator that the code could be altered. Subclasses that are strikingly similar indicate that some functionality could be shared in the superclass, for example.

    If there are two closely related pieces of code, you may find and fix a bug in one of them and then forget to fix the same bug in the other. This is clearly, therefore, a compromise in code safety.

  • Well-designed code does not have methods/functions that are too big

    This is an oft-cited fact. If a function is really big it is hard to understand. It is also probably trying to do too much, and should be split into a number of smaller self-contained functions. On the other hand, in some situations the converse may be true: if a function is too small is it really needed? (Note, though, that the answer may often be 'yes').

    Similarly, classes should not have too much or too little code, or too many/few methods, or too many/too few instance variables. These are warning signs of poorly structured object oriented code.

  • Well-designed code avoids the use of global variables

    If some data needs to be shared between different parts of a program, global variables are not the way to do it. They create dependencies between parts of code that are very hard to track. They build a brick wall into the extensibility of the code. Public global variables are very nearly downright evil.

    There are other ways of doing it, for example hiding the shared data behind a shared API; this can also aid code extensibility.

  • Well-designed code is documented

    Last, but by no means least. A good design should be documented. The documentation should be up to date. The documentation should be small, because the design is so simple.

In summary, good code design is a complex balance between performance, elegance, and maintenance. Simplicity is the key. Keeping the design as simple as possible will really pay off in the long run.

How do we learn to design well?

Are good designers born or made? Can we really learn to design well? I believe the answer is (to a large extent) yes. Certainly, absorbing all the points made above goes some way towards understanding how to design well. But more than this, good design largely comes from experience - seeing how particular problems have been solved in the Real World, and gaining insights into the pros and cons of different approaches to implementation. This information should be stored in your software developer's toolbox, from where the appropriate piece of code design can be later pulled out for a given problem.

One of the best methodologies for accomplishing software design is the divide and conquer approach. If you cannot see how to structure something in its entirety, break it up into smaller pieces (or abstractions). Then create a strategy for how to glue them together. In doing so you will probably be defining some of the internal API. You have done some design! Then go on to 'design' each of those components as if it were a self-contained unit. You may end up changing where your abstractions sit by the final design, but this method provides a clear route into the design problem. One of the biggest design challenges is identifying the appropriate abstractions. Again, this largely comes with experience.

Although performance is often a factor in the design of code, and may be a specified constraint, the design first, optimise later rule is important. You should ensure that the design is simple, elegant and coherent before complicating it with only the necessary optimisations. How will you know which are the 'necessary' optimisations? By inspecting the running code, profiling it for run-time information and checking the memory footprint. Similar to the fact that the initially code should be designed carefully, any optimisations applied should also be carefully thought through. This is, however, very much a secondary activity to the good initial design.

If you are not sure how best to design something, you will learn a lot by discussing the problem with your peers. This will provide opportunity for you all to learn, work together, and ensure the design is the best possible one you can come up with.

Conclusion

Good code is well designed. It has a certain aesthetic appeal that makes it 'feel good'. There are a number of things you should be considering in the design before beginning to write code: the structure, the possible future extensions, error detection and handling, specifying the correct interfaces and abstractions, the correct use of the language, and portability. The design should be documented as it is developed.

We should constantly be learning about the design of other pieces of software, building upon our knowledge of the successes and failures of other designs.

References

[Goodliffe] Pete Goodliffe. Professionalism in Programming: Being Specific. In: C Vu, Volume 12, No 4. England: ACCU, July 2000. ISSN: 1354-3164.



[1] It is a (perhaps unfortunate) fact that good professional programming practice is necessarily tied up with the economics behind it.

Notes: 

More fields may be available via dynamicdata ..