Managing software complexity

Growing software systems generally tend to become more complex and more difficult to maintain over time.
As the complexity increases, developing new features takes longer, and introducing bugs becomes increasingly easier.

Obviously, our goal should therefore be to reduce complexity whenever we can and create software systems that are
easy to understand and maintain.

How can we approach this?

Complexity is usually not added with a single commit or feature but usually builds up through hundreds and thousands
of small changes and, especially, "good enough" or "temporary" solutions over time.

The developer team has to know when "good enough" solutions really are necessary and has to revise those solutions
as soon as possible in order to not create "technical debt" and increase the overall complexity of the system.

You may have already heard of the broken window theory.
It basically states that visible signs of crime create an environment that encourages further crime and disorder.

Although this may sound ridiculous in this context, I strongly believe that this theory also applies to code bases,
especially if chaotic solutions are introduced by the more experienced developers (or even mentors) of the development team.

From my experience, this is true because new colleagues tend to derive their quality standard justifiably from
their mentor, team, and the code base.

If the code base is already a mess and no one seems to actually care about it and take action against it...
Do you think new colleagues will be encouraged to change that and actually enjoy working with it?

So, what can we do about it?

First of all, we ourselves have to act accordingly and work towards a simpler and healthier code base.
After all, why should anyone bother if we also do not act how we want others to act?

Before we start, we should think about what complexity actually means. The simplest definition of complexity for
software systems that you can find on the internet is the following:

A complex software system is, per definition, difficult to change and understand.

Complexity itself can manifest in various ways.

If you ever had to perform a seemingly simple task in a system, just to find out that you not only had to change the
thing you actually intended to change but also had to change several other things along with it, this is probably one
indication of a complex system.

Complexity in software systems is mostly caused by dependencies and obscurities.
Minimizing those makes our systems easier to change and understand.

To achieve that, the most experienced developers on the team have to guide the rest of the team and provide support wherever they can.
This requires clear communication about why someone should even care about such practices and how they can be achieved
within your project and environment.

From my experience, showing anonymized real-world examples of what went wrong, followed by an open discussion and checking
if the suggested practices could have avoided this, works best and keeps things fact-oriented and open for feedback
and improvements.

Also, try to share your own mistakes and what you have learned from them whenever you can, and try to encourage an
attitude to leave the system in a better state than it previously was when modifying it.

This attitude will also make everyone a better developer at the end of the day, keep the code base enjoyable to work with,
and hopefully get you back some time for developing the interesting features that your company needs instead of spending
too much time on support and figuring out what the system does.

When striving to manage complexity, it is mostly a good idea to optimize code for reading instead of writing.

This can be as simple as using descriptive names for variables, enforcing formatting in the code base, documenting
your thought process instead of what the code does, or introducing shared standards and guidelines in general.

Those practices reduce the cognitive load of your colleagues when they are working with your code and help them reason
about what the code is intended to do more easily.

So... why isn't this how everyone approaches development tasks from the start?

Developers who are new to the industry might not know about that and might feel pressured to implement features
as quickly as possible to prove to their superiors that they are a good fit and can get things done.

Who doesn't want to deliver the newly requested features as fast as possible and get praise for doing so?

Unfortunately, those encouraging this behavior do not always know how those features are implemented this quickly,
since they might not have a technical background and only see that those features work on the surface for specific use cases.

It is also not their job to ensure quality standards in the code base in the first place. It's fair for them to expect
the feature to be implemented in a sustainable way so that the system can grow over time as new requirements arise.

Since the code quality is in our hands, we obviously have to factor in the required time to maintain it when making estimations.
This should not be taken too lightly, since we are most certainly the only ones that can actually know if the system
is in a healthy state and what its limitations are.

Clearly communicating the state and problems of the system to superiors, outlining the problems that could arise
in the future when implementing certain features or the defined product roadmap, and coming up with alternative
sustainable suggestions is also part of our job.

by Philipp Meier, Dec 2023