On Software Complexity
Published: 2023-02-17 08:27:45

Complexity is the main showstopper in most software projects. It adds overhead to adding new features and fixing bugs. It steals resources from the important things. It puts mental load on the team members and can lead to decay in team morale.

An astronomical amount of effort is spent into trying to understand complexity and how reduce it and maintain it low.

In this post, we are going to build a foundational understanding of complexity and build on top of it in the next posts down the line.

We reduce complexity by introducing automation and processes. Automation and processes offer less risk and less cognitive load.

Complexity can't be measured precisely. It is a relative, sometimes opinionated metric. Realistically, what you can build is an intuition that can tell you if something is complex or not. You can't normalize complexity - there isn't a specific number or scale that you can use that is universal.

Some very basic aspects of complexity, such as cyclomatic complexity, can be measured, but complexity on a larger scale cannot.

We need to agree that code is costly. Complexity for code that doesn't exist is nonexistent. When you have code, you have complexity. This means that you should avoid writing code and reinventing the wheel whenever possible. 

Do not misunderstand - this doesn't invite you to write unmaintainable code because it has fewer lines of code. The idea is to reuse and extend existing functionality and systems instead of writing code.

If possible, avoid writing new code.

One of the most intuitive and natural ways to look at complexity is '**Does X fit in my head?**' 
There is a different granularity to this. 
For example, a single flow should fit in our head. A flow may include several systems, but each of the involved systems should also fit in your head. 

The second there is a detail of the given granularity that you can't remember or grasp, it doesn't fit in your head. This is a strong implication that the complexity is high and a split or refactoring should be made.

**Familiarity** does not mean **simplicity**. 
Familiarity is a temporary, in-the-moment knowledge. It can be transferred in the form of documentation, but this treats the symptom, not the cause - the complexity will remain. 
You may have docs to help you, but you need to maintain them too, and if you don't, they work against you.

Try to distinguish between familiarity and simplicity. An exercise you can do is to imagine that you do not know anything about the system, and you gain knowledge only by reading the code. 
If the code is readable and simple, you should understand the structure and implementation with relative ease. If it is complicated, you will have to 'think hard'.

Combining those concepts, we can derive the following principle: when you open a non-familiar piece of the code base, it should fit in your head just by reading the code. If it doesn't, you should consider refactoring it.

Contextual consistency leads to simplicity - try to have fewer different types of objects in your architecture and implementation. 
For example, use a single pattern for database access, regardless of whether it is a repository, active record, DAO, etc. When you have contextual simplicity, the main difference will be the data.

Lastly, **not all complexity is created equal**. We can do a simple binary classification - complexity can be accidental and essential. 
Accidental complexity is what these patterns mostly deal with, as it is parts of the system that can be improved, refactored, etc. 
Every project has essential complexity to it - complexity that, if reduced, will also reduce the value of the component. Reducing essential complexity usually damages the project. Distinguishing between both types of complexity is crucial when attempting to improve a system.