Seven Design Smells of Rotting Software
Structural rot, one level above code smells
A code smell is a hint that something is off in one snippet — a long method, a confusing name, a tangled conditional. Design smells sit a level above that. They describe rot in the overall structure of a system, the way the modules depend on one another, rather than in any single piece of code. The term comes out of the work associated with Robert C. Martin, and naming these patterns gives you a shared language for "this system is getting harder to live in."
Before the list, one quick definition. An antipattern is a commonly used approach to a recurring problem that looks reasonable but is usually ineffective — a popular wrong answer. Several of the smells below show up as antipatterns in the wild.
The seven smells#
Rigidity is when the system is hard to change because a single change forces a cascade of changes through dependent modules. You set out to adjust one thing and discover you have to touch a dozen files in three layers. Estimates balloon, and "small" changes stop being small.
Fragility is when a change breaks things in many places — and often in places that have nothing to do with what you touched. You fix the invoice screen and the login page stops working. Fragility erodes trust faster than almost anything else, because no change ever feels safe.
Immobility is when a part that could clearly be reused elsewhere is too entangled to extract. The logic you want is welded to the database, the framework, and three unrelated concerns, so instead of lifting it out you rewrite it. The reuse you paid for never arrives.
Viscosity comes in two flavors. Design viscosity is when the structure makes the wrong, hacky change easier than the right, design-preserving one — so under pressure, people take the hack and the design decays a little more each time. Environment viscosity is when the surrounding tooling is slow — long builds, slow tests, painful deploys — which also pushes people toward shortcuts just to avoid the wait.
Needless complexity is machinery built for changes that may never arrive. Speculative generality: the extra abstraction layer, the plugin system with one plugin, the configuration nobody configures. It is complexity you are paying carrying costs on for a future that has not shown up.
Needless repetition is the same idea copy-pasted in slightly different forms across the codebase. Each copy drifts a little, bugs get fixed in some and not others, and the duplication is really a signal that an abstraction is missing and waiting to be named.
Opacity is code that is hard to understand. Unlike the others, opacity tends to get worse with age on its own — every change made by someone who did not fully grasp the code muddies it further, unless someone deliberately keeps it clear as they pass through.
Principles exist to remove smells#
Here is the part people skip. Design principles — the SOLID family and the rest — exist to remove these smells. They are not decorations to sprinkle uniformly across every class.
Apply a principle only when a smell actually calls for it. If a module is rigid because of a dependency direction, invert that dependency. If logic is duplicated, factor out the abstraction. But if there is no smell, leave the code alone. Over-applying principles is itself one of the smells: chase dependency inversion and open-closed everywhere and you manufacture needless complexity, trading one form of rot for another.
So the loop is: smell first, principle second. Notice the rot, name it, then reach for the specific principle that dissolves that specific rot — and stop there. That discipline is what keeps a codebase healthy without burying it under speculative machinery.