Strategies to reduce complexity
Software development is a craft and we can follow many routes to achieving a specific goal. Having this freedom allows us to create simple solutions for very complex problems. That same freedom has a flip side, where very complex solutions can be applied to very simple problems!
To be clear: I don't think anybody purposefully sets out to create complex solutions to simple problems (unless part of an artistic discovery). I think it is a sign of lack of understanding of a certain domain or technology. I also think there are several strategies you can apply to reduce complexity!
Recognizing that complexity originates from lack of understanding, means that reducing complexity is only possible once understanding is increased. Let's take a look at a couple of strategies you could apply.
Long road to success
First of all: it is not an easy journey. The first step is recognizing you have a problem and identifying the size of it. Even then, it can be difficult to convince other stakeholders to invest time on gradually resolving the issue. Doing so without a clear plan is even more difficult, so I'm listing a couple of strategies to possibly use when battling complexities.
Break it down
One of the first steps is to, before the work starts, you spend time to break down a feature into the smallest possible bits and pieces. It has two advantages: it forces you to spend some time on thinking about the feature and what the feature should look like. The second advantage is that smaller parts potentially can already be implemented, deployed and add value in iterative steps. Preferably, you involve multiple people into breaking it down, to get a conversation going on the topic. That in turn has the benefit of sharing knowledge across multiple contributors.
Ideally you would incorporate the "can it be smaller" mantra in your daily workflow and preferably before you start building. However, this can also be applied after the feature has been delivered, as part of the refactoring process.
Separation of concerns
In line with breaking a feature down into multiple smaller ones, it might result into a logically separation of concerns, which means that components or services have a very singular role and purpose. This means that these individual parts also set you up for future success, because small additions to a isolated component are much easier to oversee than inserting a feature into a spaghetti-ish code base. Predictability of the landscape increases, which again, reduces complexity.
Separating or isolating components can happen in several levels: it can be done on a component like level, or in more extreme cases, could also mean revisiting your architecture.
Refactor as a learning experience
Considering that complexity creeps in when we lack understanding, refactoring is not just a means of reducing complexity, it serves a double purpose as well, where it allows for getting more familiar with a landscape. Sometimes you just have to build something first before you can optimize (or simplify) it. That's perfectly fine! The risk in practice is that development is considered done when it's functional and deployed to production.
This can be the result of a MVP mindset, but consider that an MVP is not meant to remain in that state: it's meant to test out a concept or idea with the intent of learning from the process and applying those learnings.
Refactoring or revisiting a solution can help in being able to look at a problem in hindsight and from a different perspective. You will see new ways of solving issues and will recognize patterns that emerged organically while you were working on the solution. Having this perspective allows you to group them more logically.
I'd recommend refactoring as part of a shared effort, using multiple people to work on the refactoring process.
Pair, mob, extreme!
Working on a solution by yourself, could mean you become part of the problem. Sometimes you can lose the overview since you're zoomed in on getting a feature working. Pairing up with one or more additional developers serves a couple of purposes: you get the benefit of a hive mind of problem solving abilities. Individuals tend to have a certain area of expertise or interest, which leads to biased solutions. By collaborating on a feature at the same time, you'll be able to combine expertises which evens out a natural bias, adds multiple perspectives to a solution and obviously benefits the increased shared knowledge between contributors.
There are numerous advantages of forms of multiple people simultaneously working on an issue. If you're not experienced with these forms, I encourage you to experiment with fully adopting them for at least a three month period and see how you experience it.
Simplify landscape
This is a bit of an escape hatch, but could very well be a viable long term strategy. If you've attempted the above strategies, but experience continuing returning complexity, it might mean that the landscape simply isn't able to facilitate your goals. Obviously this is more difficult to change and highly depends on the influence and resources you have at your disposal.
If your reach is not big enough to control changes in the landscape or architecture, it might be worth considering adding specific new parts. Maybe the benefit of building and maintaining your own set of microservices is worth the effort if you're able to exert more control in smaller, more manageable areas.
Keep it Simple, Stupid!
The KISS acronym is very well known, but not widely adopted. In daily development, rabbit holes of spaghetti code can be very luring. Complexity can stem from lack of understanding or lack of following up on experiments.
The above strategies are no one size fits all, but hopefully serves as an inspiration for strategies to improve. Reducing complexity takes time and effort, but it is also an essential part of future-proofing your software, so it should be treated with the same (if not more) attention as crunching out new features.