The Elements of Style

Go with the flow

Kevlin Henney

The best documentation for a computer program is a clean structure.

Kernighan and Plauger

Discussion of structured programming will still, almost without fail, raise the ambient temperature in a room of programmers. It is uncommon to find software engineers that suffer from rampant goto-itis, but interpretations of what structured programming is and what it is for differ sufficiently that people will draw a line and pick a side.

Structured programming is not about avoiding the goto. The absence of gotos is merely a side effect, and their presence can invariably be used as a barometer — Dijkstra observed that the comprehensibility of a program was inversely proportional to the number of gotos it contained.

In many respects software engineering is about the management of complexity. By this definition any introduction of complexity is questionable. A unit of code — regardless of whether it is a class, a function, a loop, a block or a simple statement — should have a single simple responsibility: it should do one thing well. Complexity is handled by breaking complex responsibilities into simpler ones, and regrouping vertically or horizontally. This is the root of all modular approaches: it is not the exclusive domain of top-down programming. For control flow, the basic premise is that any program can be expressed in terms of three structure types:

Readers familiar with the concept of patterns — Alexander, rather than knitting — might like to consider structured programming as a pattern language for control flow. The forces and problems to be resolved are those of complexity manifested as readability, reliability, testability and maintainability. These three primitives provide the essential means to do this. Structured programming is not a handle-turning exercise, so using these primitives does not automatically endow your code with style and elegance. There is no escaping the need for reasoned thought in the process of program development.

Jumpy code has, by definition, non-smooth control flow and often implies turbulent or chaotic understanding on the part of the programmer. Indeed, one entry in my dictionary states that flow is continuous by definition. The classic jump statement is the goto, but it also has more restrained relatives in the form of continue, return and break (in a loop).

Taligent's Guide to Designing Programs is filled with sound, concise advice on building programs. They take no prisoners when considering the goto:

Should you recoil from the strength of this assertion, some rationale is also provided: There's not really a lot you can say in this area that has not already been said before. However, every now and then someone seeks to defend the goto saying that it has its place. This place seems to be somewhat shy and elusive. All of the production code I have ever seen using gotos could — and I would argue, should — have been made clearer and simpler with another structure. I know one thing I have yet to see: defenders of the limited-strike goto scenario practising what they preach. The "only" in the phrase "the only time I ever use it is when..." seems to have some very liberal interpretations.

Taking some examples from real code is a good way to illustrate two simple rules: forward jumps should be expressed using selection and backward jumps using loops. More specific refinement rules become apparent on inspection:

The context of this code is not important, except to say that unfortunately this fragment is about par for the course. This is a loop by any other name: C provides the programmer with three well-defined looping structures — what more do you want! A literal translation into something tidier gives us Simplifying further eliminates any kind of jumpiness in the code: If you prefer side-effect-free conditions and wish to avoid unnecessary assignments, the following has the same effect: The program the original was drawn from also exhibits another goto syndrome: jumping for code reuse. In particular, the usage message in main is accessed by jumping to it from any number of points. One is sorely tempted to take the original author to one side and explain, kindly and patiently, that this is what functions are for. Such wrong-headedness about code reuse led to a main that was a few hundred lines long in this instance.

So is there a valid use for the goto? Yes, three that I can think of: in machine-generated code not intended for human consumption; in real optimisation, by which I mean speeding up genuine performance-critical compute-bound loops with complex internal behaviour on a specific platform — relatively few programmers come across many of these in their careers; and, taking the fifth, as witness against itself in the form of code counter-examples. Thus, the need for a goto is a symptom and not a cure.

What about other jumps? Taligent's guide finishes the small section on the goto with

It stands to reason that if a block should have a single entry point at the top, it should also have a single exit point at the bottom. This reduces the path complexity of the code, easing debugging, testing and maintenance. The single- versus multiple-return-point issue probably attracts more division than the goto. The theory is certainly there to back it up, but I will also make an observation based on experience: code that does not follow the single return rule is more likely to be incorrect.

Standard block ifs can be used to direct flow around code rather than bombing out prematurely with returns, e.g. instead of

use Premature exits from within a loop can often be eliminated by the "do one thing well" rule. A typical find algorithm using multiple returns: A more general approach reallocates the responsibilities, so that the loop advances the program to a well-defined state, and the subsequent code acts on this state: The while, used here in preference to a null-bodied for, does the 'travelling' and the result is evaluated in the 'slip-stream' of the control flow. Likewise the following cascade pattern for error handling avoids multiple returns, allowing a single point for any resource reclamation or rollback before exiting: In C++ much of this can be tidied using exceptions and the resource acquisition is initialisation idiom. In this an object is given responsibility for owning access to a resource, automatically closing or deallocating on its destruction. This is exception safe and can do a lot to simplify code.

So how do exceptions fit into the structured scheme of things? They are not truly part of the structured manifesto, and some have tried likening them to a form of goto. Truth be known, the C++ model of exceptions is so unlike the goto that the lack of similarity is quite startling. A thrown exception results in a transfer of control (unwinding the call stack in an orderly fashion) and information (such as a parameterised indication of what occurred) to a point where the calling code can handle it. Exception handling is a pragmatic tool for managing complexity, respecting a program's modularity, extensibility and original structure.

Large functions with a lot of jumps are obviously harder to comprehend. For these it is probably worth taking a step back, reconsidering the problem, breaking the function up into smaller units and then seeing where you stand. If it looks pretty much the same you can still perform a noble service to your fellow programmer by inserting clear, brief comments. Sure, compared to the goto the other jumps are well behaved, but they still have their dark side. When you see them, get their alibi.

© Kevlin Henney
First published in CVu 7(6), September, 1995
Converted to HTML, March 2001