The real problem with software isn't code.


We’ve all seen it. At work, personal hobby projects, or even open-source projects. What was once a nice, well-structured project becomes an untestable blob of mystery-spaghetti. Adding a new feature takes four meetings, two refactors, and a blood sacrifice to Leeroy Jenkins.
Touching anything outside your team's narrow lane is a no-no. Everyone wonders how the billing module works, and the only person who knows left last quarter.
Let’s Be Honest: Most Software Is Just Holding On
Most developers have worked on a system that technically works but feels fragile. You can’t move fast. You can’t reason about behavior. You’re afraid to touch things. And you have to talk to at least 3 different teams.
You’re constantly asking questions like:
“Is this logic duplicated somewhere else?”
“Is this actually what the business wants?”
“Why is this called a manager?”
“Can I safely change this... or will it break prod?”
And it’s not because you’re a bad developer. It could be because the system was never really designed and just kind of happened. Or maybe, it started off correctly but slowly deteriorated over time.
Most teams build just enough to get the job done. Few stop to ask: What will this look like in six months? In two years? How will it change? That’s not a code smell, it’s a thinking gap.
And that’s where software starts to become difficult to work with.
Code quality at the micro level doesn’t guarantee system clarity at the macro level
Plenty of codebases look clean, with neat functions, consistent naming, tests in place and yet it still feels impossible to change. You try to add a feature, and all of a sudden you're knee-deep in edge cases, half-duplicated logic, and unexpected coupling.
Readability doesn’t guarantee clarity.
Test coverage doesn’t guarantee understanding.
Clean code doesn’t mean the system makes sense.
We’ve been taught how to write nice-looking code. But not how to design code around real-world behavior that reflects how the business actually works.
So even when the code looks right, the system still drifts. Complexity builds. Context gets lost. Eventually, no one really knows what the system does, only how to keep it from breaking.
Code quality at the micro level doesn’t guarantee system clarity at the macro level.
If your code doesn’t reflect how the business thinks, then no amount of cleanliness will stop the system from drifting. Eventually, nobody can say with confidence what the system means. Only what not to touch.
Frameworks are supposed to support your design. Not define it.
Most systems today are designed around frameworks instead of business problems. You pick a stack, and it nudges you into a shape. That shape becomes your architecture and your app ends up structured around HTTP requests instead of user journeys. Your core logic is scattered across controllers, handlers, mappers, and services and each one is named generically, none of them reflecting the real problem you're solving. Eventually, your app isn’t modeling your problem. It’s modeling your stack.
You follow patterns because they’re familiar, not because they make the domain clearer. And the more you lean on your framework for structure, the harder it becomes to step back and ask:
what are we actually building?
Frameworks are scaffolding. Your architecture is what you build inside that scaffolding. When your design mirrors your tools instead of your domain, you’ve already lost control. (C# folk, you’ve seen this: private constructors added just to satisfy EF Core. That’s the foot in the door 😉🔫.)
Shipping is just the beginning. The real cost is paid in maintenance.
Most projects ship, that’s not the problem. The problem is what happens after. Features slow down, bugs resurface, confidence drops, and the cost of change becomes expensive. You add something in one place and silently break two others. You onboard a new developer, and they spend their first month just trying to understand where things are. You try to reuse logic that no one knows where it lives, or whether it’s safe to touch.
So you patch. You work around. You duplicate.
A system that no one wants to change is a system that’s already dying. Eventually, someone says the quiet part out loud: “It might be easier to just rewrite it.”
The moment your team stops wanting to change the system because its “too risky” or whatever, is the moment the system has failed.
The longer we go without a model, the harder it becomes to recover one.
Most systems fail for the same reasons:
No shared language between engineers and the business/stakeholders
No clear model of how the system is supposed to behave
No boundaries between things that change for different reasons
No architecture beyond “this is how the framework told us to do it”
This isn’t a tech failure. It’s a design/thinking failure. And it keeps happening (even to the best of us) because we’re taught to build software from the outside in:
Start with Routes → Add services → Plug in persistence → Push it through a queue → Make sure it works.
But when requirements change (and they will), we don’t know how to adapt, because the system isn’t structured around the domain. It’s structured around delivery. And that’s backwards.
If your architecture is defined by delivery paths instead of domain behavior, then every change becomes a guess, not a decision.
This isn’t about tools. It’s about thinking.
I’m not here to sell you the next new JavaScript framework (I think we’re on the 83rd?) or push another buzzword for fun. This is about a paradigm shift to align your philosophy and learn how to design systems that last, are clear, adaptable, understandable; systems that model the problem and not the platform.
That’s where lasting architecture comes from. Not from rules, diagrams, or layered folders but from clarity of intent.
Before you pick your tools, your patterns, or even your folder structure, you must understand what you’re actually building and why it exists. And right now, most teams can’t answer that with confidence.
Leave a comment below if you’ve ever come across a situation where the framework forced you to do something you didn’t want to do
Subscribe to my newsletter
Read articles from Andrew D. directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Andrew D.
Andrew D.
Currently learning how to build time machines with the critter stack; CQRS + ES. Also heavily betting on the Cloudflare stack to ship highly scalable services at the edge, quickly.