Some time ago within the confines of getting back to basics, I read “Design Principles and Design Patterns.” by Uncle Bob. It is a publication that is almost 20 years old, and mostly you can find there a few design patterns called SOLDI at that time. Today those patterns are probably known to all of us as SOLID principles, but that’s a different story. What got my attention there was an idea of rotting design.
When we start a project, we usually spend a significant amount of time designing it within the team. Usually, we end up with a clear vision of how it should work. We fix the boundaries – allowing or disallowing some behaviors in our brand new system. As a result, we get a design that is state-of-the-art – at least to our current knowledge. All team members ritually put their signatures in blood on it and agree to respect it till the end of its days.
In the beginning, everything looks great. We develop with ease, first versions get to production, time goes by, and we are receiving first feature requests. Next to them appear little exceptions to our original agreements. Some tiny “hacks” here and there. Just because they are “temporary solutions” or because we “need to fix a bug quickly.” Thus our design starts to rot, and it is not the bleeding edge anymore. At some point, it has nothing in common with our first approach, and someone says “we need to rewrite it.” However, it can happen that not everyone would be so enthusiastic about doing so. Especially people that are not close to the code but have to approve such a decision and justify its business viability. The worst case scenario is that we stay with rotting design without the chance to redesign it. If so, any changes, even the smallest ones, are hard to implement. The situation becomes very frustrating for developers as well as product owners, managers and so on, up to the top of the food chain hierarchy.
That’s usually the moment when there starts a witch-hunt and the temptation to blame the whole evil thing on constant changes or blurry requirements, customer’s lack of business knowledge, lack of documentation or user stories, you name it. There are situations when it’s true, but still, we are required to write code that is resilient and easily maintainable. Why? Because in most cases we work in agile environments, and by default, requirements will change. Our goal is to be one step ahead of customers. This is the world we live in.
Let’s chill out a bit for a moment and play BINGO:
So? BINGO? If you have won, I have some good and bad news. The bad news is that probably you are struggling with the rotting of the design in your application. The good news is that I’m going to shed some more light on its disease. However, before that, I would like to ask you to take a closer look at the examples on the bingo board. Each of them shows evidence of one out of four symptoms of rotting design.
Symptoms of rotting design
Symptom no. 1 – rigidity
We can notice it when a seemingly simple change in the code triggers a chain reaction of unexpected changes in the entire application. As a result, it becomes tough to introduce any changes as it is very time-consuming.
Real life example could be a situation when a product owner comes to a team and asks about the estimation of adding just this one, tiny checkbox to the form. After a while, the team estimates that two devs are going to do it in a two week period. Unbelievable? Oh, I assure you it’s true ;).
As a result, non-critical issues are not being fixed, because Product Owner or manager are afraid of spending the team’s time on fixing them, as they don’t know how long it could take.
Symptom no. 2 – fragility
When after a change in one place, you get bugs in totally different parts of the system that are logically not connected with the original place, you are probably dealing with fragility.
You fixed the tax policy, but you also introduced three new bugs related to adding products to the cart, customers filtering in admin panel and error logging.
Once again fear appears because no one knows where and how many business functionalities break after bugfix. There is also a suspicion that developers have lost control over the code.
Symptom no. 3 – immobility
Now I want you to imagine a situation when you implement a new feature, and you recall a piece of code that does exactly what you need. You find it, analyze it and leave it there, rewriting it in your functionality. But why? Why can’t you reuse it? Because the overhead of extracting it and satisfying all its dependencies is so huge that it simply doesn’t make any sense.
Symptom no. 4 – viscosity
When we develop our application, we can do it in one of two ways: according to the current design or against it. In the case when it is easier to follow the latter approach, it probably means that viscosity is in attendance. We can easily link it to all those situations when we knew how something should be coded, but instead we did some little hack to deliver our feature quickly. To sum up, it is easier to do those tiny hacks than follow the design rules.
What the hell is going on here?
Now it’s time to finally get to know what disease our design is suffering from. Once we have found out that it is not because of changing requirements, what else can it be? Well, the most common reason for that is the wrong dependency management in our code. If so, two old concepts come to the rescue: coupling and cohesion.
Coupling can be described as a strength of the relationship between classes or modules. Typically we talk about its two types: tight and loose coupling. However, as we all know, between two extreme values, there is always this gray area where we can place a lot of different cases, which is the case here, too. More about it later on.
Cohesion, on the other hand, is a degree of how elements of a specific class/module go together, how they match the class and each other. They all should effectively work towards a common goal. They should all naturally mesh with each other, without any “glue” used to connect them. When it comes to cohesion, we also usually describe it as low or high, but guess what: there is more than that.
Generally, we consider good design as one that it is easily maintainable, resilient to changing requirements and having loosely coupled, independent classes, that do exactly what they were created for. In other words, such a design is marked by loose coupling and high cohesion.
Now you can do two things. Either you can gain some more knowledge about the origins of coupling and cohesion and why developers usually have issues with understanding those concepts. For that, I encourage you to read A brief history of coupling and cohesion. Alternatively, you can go to the posts dedicated to coupling or cohesion. Have a good reading!
P.S. If you have enjoyed this article and would like to always have it with you offline or you would like to have some super sketch notes with types of coupling and cohesion to print out and stick them above your bed, don’t hesitate and download an ebook, that I have for you. You will find there a few great posts about rotting design, coupling and cohesion put together into PDF, EPUB and MOBI formats.