Some time ago, Joy Clark already wrote about simplicity. I’ve also approached this topic once before. I’d like to follow up on both articles and show how simplicity can boost our understanding of software. I will also propose that we look at other disciplines to see if and how simplicity affects them. Maybe we can benefit from their experience.
What’s all the fuss about?
Why do we even talk about simplicity? Why is it such a hot topic? Why is nobody praising complexity? Why do things like the KISS principle exist?
As it turns out, keeping things simple has some profound effects. In science, the Occam’s razor principle has been commonly used for a few hundred years. In short, it proposes that, if there are two or more hypotheses to choose from, the one with the fewest assumptions should be selected. Obviously, the “razor” is not a law, it cannot be proven, it’s just a heuristics. The most interesting thing about it is the reasoning behind it. It states that the simplest hypothesis should be selected, because it’s the easiest to test or falsify. If we apply this principle to software engineering, we could say that we should prefer simpler solutions, because it’s easier to find defects or prove the solution to be correct. Indeed, it feels intuitive. We could also reverse it and say that if we’re having trouble while writing tests, it’s probably because our solution is not as simple as it could be. But remember, it’s only a heuristics, it does not necessarily need to be true. Maybe the problem you’re trying to solve is indeed complex and your solution cannot be simplified. Einstein is credited with saying “Everything should be kept as simple as possible, but not simpler”, maybe that’s your case.
What does “simple” look like?
But what is “simple”? How can we tell if something is simple, or select the simpler solution out of two? By definition, simple means “having or composed of only one thing, element, or part” (by Wordnik), “free of secondary complications, unmixed” (by Merriam-Webster) or “having few parts or features; not complicated or elaborate” (by The Free Dictionary). The meaning is similar to what Rich Hickey proposes in his video “simple made easy” (it’s one of those I strongly recommend watching). So “simple” is about cardinality, the less the simpler. It also plays very well with the idea of cognitive load. The human brain is said to be able to process only a few items at the same time. The more different concepts – like variables or state – influence any given piece of code, the less simple it is. As the number of moving parts increases, we need to use more energy and concentrate harder to reason about and change the code, so it takes more time and it’s easier to make a mistake.
How “simple” works
No wonder, then, that there exist so many principles suggesting to reduce the number of concepts present or affecting a particular piece of code, starting with high cohesion and low coupling, through single responsibility principle and worse is better, all the way to TDD. TDD is actually a very interesting example of how simplicity can help us on many different levels. It was developed as a technique that helps us focus on a single thing: the feature to implement. But not only are we avoiding the violation of the YAGNI principle, it also has an additional psychological effect. Every single step focuses on a different aspect, so it frees us from thinking about too many things at the same time. When writing a test that fails, we don’t need to think about the implementation. The only important thing is: what am I expecting the system to do. When making a test pass, we focus on just that, doing the simplest thing that will make the test green (and not make any other red). At this time, we don’t need to worry about, for example, code quality, coding conventions, etc. We can worry about that in the refactoring step, but then the tests we wrote so far free us from thinking about functionality.
Is it easy?
And again, it feels intuitive: less things at the same time are easier to handle, not only in software development. There’s a reason why it’s forbidden to drive a car and use a mobile phone at the same time. Of course, we can get better and be able to “juggle” more items at the same time, but there will always be a toll to pay and the limits can’t be pushed indefinitely. You can surely recall a time, when coding something was really difficult, because you didn’t know the API nor the syntax nor common pitfalls; your programs were buggy and crashed often. Because everything was new, it took an enormous amount of energy and focus to put a few things together. Now, as you’re already familiar with your tools, standard library, etc., all those basic problems seem to disappear, and leave you to focus on your actual problems. But that is only because it’s getting easier for you to write code, it has nothing to do with how simple your language or tools are. They may be complicated, but after some practice, they become easy. Indeed, Wordink defines easy as “Capable of being accomplished or acquired with ease; posing no difficulty: an easy victory; an easy problem”.
It’s a trap!
Making things easy might seem like a good idea. If you know the tool that can help you solve a problem at hand, you’ll be done fast and probably without any major issues. The catch is: not everyone knows the tool. If a new developer joins the team they might have a hard time understanding what you did. They would need to familiarize themselves with the tool first before they can deep dive into your solution. Easy is subjective, simple is objective. Making things easier will always be making them easier for you or your current team, not for everybody. Making things simpler, on the other hand, will be making them simpler for everyone. Of course, simple doesn’t automatically make things easy, it might still be complicated (though not complex). For example, this “simple” could mean, that the code implements some quite sophisticated algorithm, which is difficult to understand by itself, but it’s free of anything that is not necessary to solve the problem, such as usage of Spring or Hibernate.
But even if all those multiple things coupled together are easy for you, working with such code will not be easy. You’d constantly need to switch contexts in your head while reading the code, each time focusing on a different concept. This not only takes time, but also leads to all kinds of problems. The most important one is that you cannot easily understand the logic. There’s no way to be able to see only the code you want to see, for example business logic. There is a constant noise, code that does something else, not related to what interests you at the moment. Once you cannot understand what your application is doing and why it is doing it, you will introduce bugs and deliver features that do not fully match the requirements. Only if you can fully understand what is currently happening in the code, you will be able to change it in a predictable way and be aware of all consequences of your changes. Such understanding cannot be replaced by, for example, presence of a test suite. This can only verify that you’re not breaking current behaviour (covered by tests, that is). Simple code, containing just one concept, makes changes to the code much easier. The scope is limited and even if you are introducing issues, they’ll be easy to find.
The Power of simplicity
Simplicity has also a different effect that we might want to pursue: elegance. It’s this feeling, when you’re looking at your solution, that you’ve done the job right. The code is exactly to the point, does what it needs to do, it doesn’t do more than that, it’s efficient, succinct, you just look at it and know what it does, you cannot really improve it. Antoine de Saint Exupéry once said that “It seems that perfection is reached not when there is nothing left to add, but when there is nothing left to take away”. Also, mathematicians and physicists are looking for “elegant” solutions and theories. They’ve learned over the centuries, that if their solution is not elegant enough, it probably can be improved as it most likely is not complete yet. That is not to say that it’s wrong. It’s just that it’s not touching the very core of the problem yet, it might be just a step towards “the” solution. This can be very well seen in a pursuit of the Grand Unified Theory in particle physics, where scientists know which parts need to be improved, just by looking at the pieces of the Standard Model that are not elegant enough. It goes very well with, already mentioned, Occam’s razor, the more elegant, the more simple, the easier to falsify. So maybe it should also be a sign for us, that, whenever we see not-so-elegant code, we’re probably not solving the problem the simplest way possible.
Easy is not evil
Having said all that, I want to clarify, that “easy” is not a bad thing, it’s not contradicting “simple” by definition – they can support each other very well. If “simple” is “easy” for you, even better, strive for it! Just make sure, that when you’re trying to improve things, try to first make them simple. Once concerns are separated from one another, you can learn and familiarize yourself with those, which are not easy for you yet –– the same will be possible for all other people working with your code. It will be also possible for you and your team to announce that your code is assuming knowledge of, for example, set theory. When a new developer joins the team and they don’t know some pieces yet, they can start working on those concerns they already know and, in parallel, learn the pieces they are not familiar with just yet. And even if they needed to go directly for the difficult parts of the system, they would at least be struggling with one problem only.
So please, keep it simple.