Symptoms of Bad Software Design
4 signals that your software is badly designed and how to solve them
I’ve been working in software for a while. In all these years, I’ve created bad software… sometimes.
When you create the software, sometimes it’s difficult to find your blind spots. But time puts things in perspective, and so you can learn from your mistakes.
In today’s email, I want to share with you the 4 signals that your software is badly designed.
Rigidity
👉🏼 Rigidity is the tendency of software to be difficult to change, even in simple ways.
A system is rigid when a change in one module triggers a cascade of changes in other dependent modules.
The symptom: If you request a change estimated to take two days and it ends up taking two weeks because “one thing led to another,” you have a rigid system.
The cause: It’s usually excessive coupling. Everything is so tightly coupled that you can’t move one piece without it affecting the entire board.
🧐 Let’s see an example, and how you would solve it.
The Scenario: You have an OrderProcessor class with a massive switch statement for calculating shipping costs. If it’s “UPS,” it does one thing; if it’s “FedEx,” it does another. When you want to add “DHL,” you have to modify that class, forcing you to recompile and retest the entire order module.
The Solution: Strategy Pattern (or the Open/Closed Principle). Instead of an internal switch statement, you create a ShippingStrategy interface. Each carrier (UPS, FedEx, DHL) implements this interface. The OrderProcessor now only receives one strategy and calls calculate().
Result: To add a new carrier, you simply create a new class. You don’t touch the existing code.
Fragility
👉🏼 Fragility is the tendency of software to break in many places every time a change is made.
Unlike rigidity, the problem here isn’t the effort of the change itself, but the lack of control over the side effects.
The symptom: You fix a bug in the payments module, and mysteriously, the inventory report generator stops working.
The cause: This usually occurs when there are hidden dependencies or highly intertwined logic where modules know too much about each other’s inner workings.
🧐 Let’s see an example, and how you would solve it.
The Scenario: You have a global variable or a Singleton that stores the “System Configuration.” A developer changes the date format in that configuration for a specific report, and suddenly, the “Payroll” module stops processing payments because it expected the old format. The system broke in a place unrelated to the original change.
The Solution: Encapsulation and Interface Segregation.
Don’t allow everyone to access a global object. Divide the configuration into smaller interfaces (e.g., PayrollConfig, ReportConfig). Each module only sees what it needs.
Result: A change in the reports only affects the reporting interface, keeping payroll safe.
Immobility
👉🏼 Immobility is the inability to reuse software in other projects or even in other parts of the same project.
It occurs when the design is so entangled with its environment that extracting a feature is more costly than rewriting it.
The symptom: You need a validation function that you already implemented in another module, but when you try to copy it, you realize that it also includes the database, the user interface, and three external libraries.
The cause: A design that doesn’t separate business rules from implementation details (such as the database or the framework).
🧐 Yeah, let’s see an example, and how you would solve it.
The Scenario: You created a brilliant algorithm to validate ID numbers (national ID/tax ID). However, the algorithm is written within a UserRegistrationForm class that inherits from a UI library (like React or the Android SDK) and also directly calls the database to check for duplicates.
The Solution: Layered Architecture (Clean Architecture).
Extract the algorithm into a Pure Object Component (POJO) or Use Case. This code should not know anything about buttons or databases. The database is passed to it as an interface (Dependency Inversion).
Result: Now you can copy that validation file and paste it into any other project without carrying over the “garbage” from the interface or the database.
Viscosity
👉🏼 Viscosity refers to the resistance the system offers to doing things “right.”
It is divided into two types:
Software viscosity: When it is much easier to add a “hack” or a dirty patch that follows the original design and keeps the architecture clean.
Environment Viscosity: This occurs when the development environment is slow or inefficient (endless compilation times, tests that take hours). This tempts developers to resort to shortcuts to avoid the formal process.
🧐 Let’s see an example, and how you would solve it.
The Scenario: You need to add a field to a form. The “clean” design requires creating a database migration, updating the entity, updating the DTO, and updating the mapper. That takes 1 hour. But you can simply save that extra “hidden” data in a generic text field called “observations” in 5 minutes. If the system takes a long time to compile or testing is slow, you’ll choose the dirty way.
The Solution: Automation and Infrastructure Refactoring.
If the problem is with the environment (slow compilation), you need better machines or to modularize the project. If it’s with the software, you need tools like Lombok or automatic mappers (AutoMapper/MapStruct) that reduce repetitive code (boilerplate).
Result: Doing it right should be almost as fast as doing it wrong. If the right way is easy to follow, developers will follow it.
Alright! Let’s wrap up for today. These four signals (Rigidity, Fragility, Immobility, and Viscosity) are not just theoretical concepts; they are the “smells” that indicate your system is asking for help.
✨ Takeaways
Today’s email has been really software engineering-oriented. Long time since I dedicated one email to this, hope you like it from time to time.
Design for change: A rigid system is a slow system. Use patterns like Strategy to decouple logic from execution.
Protect your boundaries: Avoid global states and hidden dependencies to keep fragility under control.
Decouple from the framework: Your business logic should be able to live anywhere. If it’s stuck in a UI component, it’s immobile.
Make the “right way” the “easy way”: If doing it right takes too much effort, the system will eventually fill with hacks. Invest in your environment.
☝🏼 Remember: Bad design is not a life sentence. Identifying these signals is the first step toward refactoring and moving toward a more Incremental Architecture.
I’d love to hear from you. Have you identified any of these signals in your current project? Which one do you find the most difficult to fix?
Drop a message in the comments or reply to this email. I read and reply to all!
Best,
Marcos.



