KISS With Essential Complexity

Accidental complexity, in a broader sense, is the complexity that developers add to their code and that is not necessary for the code to work. That may include overengineering, overuse of design patterns, poor choice of tools, frameworks and paradigms, writing snippets of code in a hard to read way. For example, if you can do a project with a simple layered architecture, doing it with microservices (and having to decide on granularity, coordinate them, etc.) or message-driven architecture (with setting up the broker and its queues’ all sorts of configurations) increases the complexity of the software unnecessarily, and is therefore accidental complexity. If you want to parse XML and covert it to objects, using SAX adds a lot of accidental complexity, compared to xml-to-object mapper (e.g. JAXB), where you just add a few annotations (hopefully). If your logic can be expressed with a few lambda expressions, but instead you do several nested for-loops with if clauses inside, then that’s accidental complexity. For me at least, accidental complexity is about making it hard to read, maintain and deploy with no good reason, apart from not knowing better.

Essential complexity on the other hand comes from the world you are trying to model with your software. It’s about the inevitable edge cases you have to handle if you want your software to be fit for actual use. Essential complexity can and does make your code harder to read and maintain. It makes it look like “legacy” code, but as Spolsky points out, that’s the way of things, and that’s the way it should be. Unexpected API calls, classes that exist for some bizarre edge-case that you discovered after half a year of actual use, ifs and fors that you think you can just remove – these are the marks of real software.

(I’m aware of another view of accidental complexity – that it still adds value, but is not the problem that you are solving. That’s a long discussion, but I think that anything that is inherently complex and needs to be done (e.g. rolling updates) is essential complexity, i.e. you can’t do without it.)

If the business process you are modeling has a lot of branches and even loops, and it can’t be optimized, then the code that handled that business process has to be “hairy”. When you have to run your software on a device that can lose connectivity, or have poor connectivity, can be restarted at any moment, then the code for retrying, for re-applying offline steps, and the likes, is necessary, even if it’s huge and hard to follow.

But is that it? Is it that we can’t do anything about our essential complexity, and we can only leave the ugly bits of code there, shrugging and saying “well, I know it’s bad, man, but what can you do – essential complexity”.

Well, we can’t get rid of it. But we can make it slightly friendlier. I have two specific approaches.

Document the scenarios that require the complexity. Either directly in the code, or linked in the code. Most of the code that looks “WTF” can look completely logical if you know why it’s there. If we make sure all the bizarre code makes sense to everyone, by explaining the business reason behind it, then we have solved part of the problem.

But that’s just on the surface. Can we actually follow the “Keep it simple, stupid” (KISS) principle when it comes to essential complexity? Yes, to an extent. You can’t make complexity simple, but you can present it in a simpler way. What we want to achieve is reduce the perceived complexity, to make it easier to follow and reason about.

The first thing to look for is any accidental complexity that you have introduced around the essential one. It usually happens that essential complexity makes accidental complexity more likely to appear, probably because all the focus of the developer is on grasping every aspect of the scenario he’s working on, that he forgets about good practices. But eliminating that is not enough either.

Ironically, here is where common (design) patterns and specific frameworks come handy. You need to represent a complex sequence of states of your application? Use a finite state machine implementation, rather than bits and pieces here and there. You need to represent a complex business process? Use a business process management framework, rather than just flow control structures. You have a lot of dependencies in your classes (even though your classes are designed and packaged well)? Use a dependency injection framework. Or in many cases – just refactor, I know this answer of mine is the most obvious thing, but we’ve all seen complex methods that just do a lot of stuff and do not follow that approach. Because it grew with time, so nobody realized it has become that big.

But apart from a couple of example, I cannot give a general rule. Reducing the perceived complexity is (obviously) highly dependent on the perception of the one reducing it. But as a one-line advice – always think of how you can rearrange the code around the inherent, essential complexity of your application to make it look less complex.

Leave a Reply

Your email address will not be published.