Skip to main content

Avoid complexity

Complexity is a root of much evil in programming. Complex code is difficult to understand and reason about. You may feel good about yourself when you can wrap your head around a complex piece of code, but in most cases this is pointless, vain pride, and little good will come out of it. What you should be feeling instead is annoyance at the author of the code for making it complex in the first place. Developing such an intuitive feeling of annoyance with complex code will let you write your own code in readable, straight-forward ways, provided you learn to switch perspectives, because in context it can be hard to be aware of complexity.

Complexity is often divided into two classes.

Inherent complexity is complexity that is inherent to the problem at hand, it is essential, inseparable, embedded in the problem. There is little you can do to avoid it[1]. Say you need to build software to simulate the global trade market. No matter how elegantly you write your simulation code, the problem that you’re simulating is complex, and as long as you want to simulate it closely, you cannot avoid all that complexity[2].

Incidental complexity is complexity introduced by the process of modelling or implementation itself. Say you need to build software to simulate inelastic ball collisions. The problem has low inherent complexity, it’s governed by a simple equation. You hack together a first rough version, and then the requirements get updated — you need to animate the simulation on the screen. Unexpected change, but sure, we can tack that on in a few hours, and be done with the project. Not so fast — again requirements change, and now you need to take initial ball positions from a file instead of having them hardcoded in code. You tack that on, and then are told that in some environments, you don’t have access to the file system, so need to take the data in through command line params. You tack that on. Now you have this large, monolithic function, that pulls data from a number of sources, has overrides scattered throughout, and uses abbreviated names that made sense “at the time” to boot. The process it models is simple — one equation, and one of a few possible input sources. Yet the code is unreadable, immensely complex, you touch one line, and suddenly end up in the nightmare territory of “works from command line args, but not from file”. This is what typical consequences of too much incidental complexity look like.

The worst thing about complexity is that it induces fear. When code is difficult to understand fully without a serious time commitment, making small changes becomes a scary exercise, because you don’t know what you may break in the process. Instead of making the necessarily structural changes to accommodate the desired small change in behaviour, you tend to “not mess with the existing code” much, and instead tack on the changes in the safest perceived way. This further increases complexity, resulting in a vicious feedback loop. It also often results in solving the problem at the wrong level of abstraction.

Of the two classes of complexity, incidental is by far the most common. This is great news, because it means that much complexity can be avoided or at least minimized.

Next: Remove Incidental Complexity ⇒

[1] Provided you made sure you are solving the right problem

[2] Though you could, for example, reduce the complexity of your model by modelling some of the minute details as probabilities, instead of actually precisely modelling the underlying processes.

Comments