Low level design

In the software business we tend to speak of design as though it only exists at the highest level, and precedes all construction not to be revisited. How often have you heard "The design phase is done."? Not coincidentally, we speak of implementation in the opposite voice, and "high level implementation" is not a commonly heard phrase.

What might we mean by low level design and be able to distinguish the meaning from low level implementation?

What's the problem?

As is probably obvious from the content of this site, I spend a lot of my time working fearlessly near the bare metal of Linux and UNIX. My code calls a lot of operating system functions, and for the most part they are quite regular and predictable in their behavior. For example, the read function, described this way in section 2 of the man pages:

ssize_t read(int fd, void *buf, size_t count);

The description of normal operation is described as "On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number. On error, -1 is returned, and errno is set appropriately."

read represents the type of low level design principle that might be described as functions return the number of successful operations, and provide negative values to indicate errors. I am comfortable with this, and it is easy to remember and logical.

You do not have to look very far in the documentation to see that this principle is hardly observed with any consistency. Yes, write works exactly the same way as read, but if you are reading and writing you probably have the good sense to call close when you are done.

How does close work? "close() returns zero on success. On error, -1 is returned, and errno is set appropriately." The opposite low level design principle of return zero for OK is even more widespread within the worlds where X is the final letter.

Guidelines for a solution

Here are my suggestions:

  1. For functions that return an integer of some type, decide on what is meant by positive numbers, zero, and negative numbers. My suggestion implies that where it is reasonable to do so, all return values should have sign.
  2. Decide on value semantics or reference semantics, and loudly call out the exceptions to your chosen principle.
  3. It does not matter how the functions in a source code file are organized, but order them the same way in all files. Alphabetic can be good or bad, and there are alternatives.
  4. Either freeze on a version of the language you are using, or roll all the code to each new version as it comes out.
  5. Order the parameters of a function consistently: for example sources come before destinations, and optional or defaulted parameters are only at the end.
  6. Use the entire language, and express the programming in the most direct and unambiguous way possible. There is a lot to be said for brevity.

Unfortunately, the above priniciples are incomplete, and each one could easily be expanded into a longer article. Additionally, there is very little evidence that anyone agrees with me, at least not if the code written by other programmers is a guide to any sense of intuitive adoption by the hoi polloi. But ... failure to have some operating principles of low level design turns every programming exercise into an Easter egg hunt; if you are writing the code, then you are hiding the eggs, and if you are maintaining the code you are doomed to crush them with your shoes.