Continuous Thinking: Essay: Ease and Simplicity in Software Architecture
“Almost all quality improvement comes via simplification of design, manufacturing… layout, processes, and procedures.” - Tom Peters
As I was tinkering around with Erlang/OTP and some other stuff, I suddenly experienced yet another “aha-erlebnis”: there is a huge difference between something that is simple and something that is easy, but a lot of people tend to miss this rather important distinction.
What do you mean?
When I told people I was considering writing a blog post about the difference between simple and easy; Mathias Verraes pointed me to a presentation on InfoQ named “Simple Made Easy” by the creator of Clojure.
This is a great video, and I suggest you to take a look at it. As I can not include infoQ videos on this page, I will embed another video by the same speaker about the same subject.(But to be honest, I think the one on infoQ is better.)
So how is this related to software architecture?
There is a very well known concept called the 2nd system effect which basically describes a typical evolution of application development:
System 1: “I do not have a clue yet”
You develop a system, without really knowing all the requirements, so you spike a minimal viable product/prototype, which is really simple and deploy it.
The users start using it, and start asking for extra features.
As you start implementing these features, you notice that your current software architecture is lacking a bit, but the easy way is to add yet another condition, another prerequisite, include another loop/code to your existing code until it turns into a big ball of mud; it loses the simplicity and becomes more complex. (The black swan theory has a big part in this.)
Implementation 1: maintaining existing and implementing new/extra business features gets harder and more complex on every single step.
System 2: “Let’s make sure we can use it for everything”
Knowing what you did wrong in system 1, you provide an over the top architecture, which makes it easy to add numerous extensions and adjustements.
However, the extension points and adjustements are adding an extra layer of abstraction, and thus make your code more complex. The business implementation is simple, but the extensibility is complex.
Implementation 2: Maintaining existing and implementing new/extra business features is easy, but the whole extensibility thing adds a lot of complexity.
This code is usually a PITA when you are trying to figure out what happened or are trying to debug the code.
For the record: I actually suffered from this as well doing a project for a customer of mine, so I will take this occasion to apologize for my mistake: sorry Arnold!
System 3: “Let’s cut the crap”
Finally, one gets to version 3: we understand the necessities and evolution of the project, and are able to build a simple system which only abstracts the things that mather, so it avoids complexity whenever possible.
Implementation 3: Maintaining existing and implementing new/extra business features is easy and simple
Theory is nice, but how about practice?
In my personal experience, the best way to avoid this, is by not making assumptions in the beginning, as one is typically not able to predict the future - unless your last name is Kurzweill or Titor, but that is another discussion - and refactoring code as you notice some smells.
Here are some typical smells:
- intertangling code, a lot of dependencies
- code going several levels deep
- copy/paste coding
- building for something one might need in the future
- Too generic abstractions
If you want to refactor code, you need to have some confidence that your application requirements are still met after refactoring, which leads us to the following tool: Behaviour Driven Development(BDD)
By using BDD one can find some black swans by exploring the specs with BAs before actually implementing them, and make sure the expected outcomes are still achieved after refactoring.
There is a lot more to BDD then this, but this post is already long enough.
Well, I was in dubio whether I would write this post or not, but writing it all down somehow made me more confident about what to look for when implementing a system: do not create a BDUF, but have small incremental changes to your system. Only implement arhitectural patterns when you actually need them, not a minute later or sooner.
I would like to end this post with another one of my favorite quotes
“Make everything as simple as possible, but not simpler” - A. Einstein