Control flow

Control flow is one of those things that may seem simple at first, but later one may see the messy bits. Or not, because pretty much everything in programming is controversial.

Error handling

Newbie (or simply less careful) programmers tend to write the code that would only work in perfect conditions at best, since covering all the possibilities seems to be overwhelming and excessive. And then things don't work as they should.

Programming languages and libraries tend to encourage this: usually the most straightforward way to define a program is to define its behaviour in the most desired/expected/perfect conditions, optionally covering the others. In dependently typed languages, there are attempts to assist verification, though not necessarily to make it easier to define correct programs than incorrect ones.

Some methods to handle the situations that are different from the most desirable path in a flowchart:

Built-in (unchecked) exceptions (e.g., Control.Exception)
Whole new control flow, but implicit; no guarantee that exceptions you throw will be handled, no reliable indication of whether a function throws exceptions at all. Rather dangerous, yet handy sometimes, particularly for applications and if an approach is taken to handle errors outside of a program. Although an exit call with an appropriate code and a message in stderr would be more suitable for that.
Checked exceptions, Except monad (Control.Monad.Except, Control.Monad.Trans.Except)
Less magic versions of exceptions, where it is clear which functions throw exceptions, and which exceptions they throw.
Return values
… Such as NULL, -1, Maybe a, Either a b, (err, a), Bool. They may force a programmer to do something meaningful with the result, but that only works as long as a return value can't be ignored altogether. When it comes to I/O actions, which are the most error-prone, many of them are executed solely for side effects, and not to get any return value – so a programmer could still easily ignore misbehaviour, though Rust would warn if a Result value is not handled, and GHC with -Wall would warn if it's not discarded explicitly. And as with the Except monad, no way to throw them to a different thread, or do other magic things (though maybe it's rather good than bad).
assert, panic
As seen in C, Rust, Go. Perhaps useful for unrecoverable errors, but normally those shouldn't be common, and it's unnecessary to introduce an additional construct just for those.
Similar to exceptions, and criticised for similar reasons. Though as everything else listed here, it's not universally accepted to be harmful.
Premature return
Some compare premature returns to goto. I think it can be quite similar if it's sprinkled inside nested branches, or in some branches and not in others. It can help to reduce the code, but the same can be said about goto. It is unnecessary and can be confusing, so perhaps better to avoid.
Event-driven approaches
With these, error conditions are not treated differently from regular ones, and they can lead to rather nice and correct code: it encourages to consider a graph, not just a sequence. These are also useful for software extensibility.

An unpleasant thing in languages with built-in exceptions is that those exceptions could pop up just anywhere. Even if one prefers a different approach (such as ExceptT), chances are that other approaches are used in the used libraries, so one ends up dealing with every available kind of branching, possibly wrapping it into something unified along the way. There even are libraries to assist that, such as errors.

Probably one of the reasons of this rather unpleasant situation is that a correct control flow can be represented with a directed graph, a flowchart, while it is tempting/easier to write and see programs as mere sequences of actions; even in dot it's not that handy to describe flowcharts. State machines (and Mealy machines in particular) may be more suitable to describe control flow precisely, while being relatively handy to edit as text. Event-driven architectures seem to be nicer in general, though they may lead to relatively verbose and/or error-prone code.


Flowcharts and state diagrams are nice tools for control flow visualisation; I rather like plain ASCII ones (similar to those used in RFCs), which can be easily embedded in comments and any other common kind of software documentation. Unfortunately the resulting graphs for non-trivial programs are not planar, so those diagrams become tricky to follow.