The Case for Code Simplicity in the Age of AI

⚠️ This blog is opinionated. Please read with an open mind. You don’t have to agree, but if it gets you to pause and think, it has done its job. We buy jewellery to show off. It makes sense because jewellery is valuable. It shines, is admired, and can even increase in value over time. But code? Code is not an asset. It’s a liability. It gets outdated, collects bugs, and needs constant maintenance. But we still write it like it's something to be displayed — fancy, complicated, and a true legacy. Complexity is Expensive I’ve seen code that could have been done in just a few lines, but instead, it’s spread across hundreds of lines and many packages. We create interfaces for no reason, factories for objects that are only used once, and layers of abstraction that make the code hard to follow. It often starts with simple ideas: What if we need to use different data sources in the future? Let's put this in an abstraction just in case we need to change it later. This could be useful as a general-purpose class later. Let’s make it reusable across projects with its own settings. Maybe we’ll need a base class in the future, even though we only have one subclass now. This logic feels like it could go in a helper… or maybe in a service, or even as an interface for a domain service. A few weeks later, what started as a simple function now requires navigating through nine files, three interfaces, and helpers that just call other helpers. What could have been a simple task is now a tangled mess of services, abstractions, and Java classes. A simple task becomes a complicated web of strategies, wrappers, decorators, and configurations. All this extra complexity is added, not because it’s needed, but because we think it’s the right way to do things. When Things Go Wrong Everything is fine until something breaks in production. Suddenly, your phone is buzzing. Slack is full of angry messages. Stakeholders want answers fast. You need to fix the issue, but where do you even start? You open the handler, which calls a mapper, which talks to a strategy, which calls a service, which delegates to a factory, and only then do you get the data. The person who wrote this has probably moved to another team, another country, or even another job. Documentation is missing or outdated. Comments are useless. The tests pass, but they don’t cover what’s actually broken. Every time you open a new file, you’re losing more time. Instead of solving the issue quickly, you’re searching through a confusing system built for flexibility that now feels like a burden. The Real Cost of Complexity Overcomplicating code doesn’t just slow you down a little. It costs time and money. It makes it harder for new developers to get up to speed. It takes longer to fix bugs. It makes it scary to make changes to the code. Simple feature requests now take forever because you have to dig through so many layers of code. You end up asking, “What does this class do, and why does it exist?” As you add more layers, your codebase becomes harder to work with. It starts to look like a puzzle no one wants to solve. It feels more like a fragile house of cards than a solid foundation. The problem is that in trying to prepare for every possible future need, we forget to just solve the problem at hand. The cost of maintaining overengineered code grows quickly. As team members leave, the knowledge of how the code works disappears. Fixing bugs or adding features becomes harder and slower, and production issues take days to fix instead of hours. Complex code takes longer to fix, not because the problem itself is complex, but because the code is hard to understand and work with. The Human Side of Simplicity I’ve often faced criticism for choosing simple solutions, writing five lines of code where others might introduce factories or interfaces. But this preference for simplicity does not come from a lack of understanding of complexity. On the contrary, it comes from understanding just how important clarity and maintainability are. Simplicity is not a lack of depth; it is the result of deliberate thinking. It is about solving problems in a way that others and your future self can easily grasp and build on. Through experience, I have learned the true value of principles like YAGNI, not just as a catchy acronym, but as a practical guide in fast-moving and constantly changing environments. That mindset taught me to solve problems in a way that avoids overengineering, focusing on clarity and durability instead. It is not about showing off. It is about building something that works well and lasts. Enterprise Doesn’t Always Mean Complexity I understand working in large companies, where the goal is to build systems that scale, are secure, and are reusable. But here's the truth: Most enterprise systems don't need complex solutions. They’re just handling data — fetching it, transforming it, and sending it somewhere e

May 14, 2025 - 15:28
 0
The Case for Code Simplicity in the Age of AI

⚠️ This blog is opinionated. Please read with an open mind. You don’t have to agree, but if it gets you to pause and think, it has done its job.

We buy jewellery to show off. It makes sense because jewellery is valuable. It shines, is admired, and can even increase in value over time.

But code?

Code is not an asset. It’s a liability. It gets outdated, collects bugs, and needs constant maintenance. But we still write it like it's something to be displayed — fancy, complicated, and a true legacy.

Complexity is Expensive

I’ve seen code that could have been done in just a few lines, but instead, it’s spread across hundreds of lines and many packages. We create interfaces for no reason, factories for objects that are only used once, and layers of abstraction that make the code hard to follow.

It often starts with simple ideas:

  • What if we need to use different data sources in the future?
  • Let's put this in an abstraction just in case we need to change it later.
  • This could be useful as a general-purpose class later.
  • Let’s make it reusable across projects with its own settings.
  • Maybe we’ll need a base class in the future, even though we only have one subclass now.
  • This logic feels like it could go in a helper… or maybe in a service, or even as an interface for a domain service.

A few weeks later, what started as a simple function now requires navigating through nine files, three interfaces, and helpers that just call other helpers.

What could have been a simple task is now a tangled mess of services, abstractions, and Java classes. A simple task becomes a complicated web of strategies, wrappers, decorators, and configurations. All this extra complexity is added, not because it’s needed, but because we think it’s the right way to do things.

When Things Go Wrong

Everything is fine until something breaks in production.

Suddenly, your phone is buzzing. Slack is full of angry messages. Stakeholders want answers fast. You need to fix the issue, but where do you even start?

You open the handler, which calls a mapper, which talks to a strategy, which calls a service, which delegates to a factory, and only then do you get the data.

The person who wrote this has probably moved to another team, another country, or even another job. Documentation is missing or outdated. Comments are useless. The tests pass, but they don’t cover what’s actually broken.

Every time you open a new file, you’re losing more time.

Instead of solving the issue quickly, you’re searching through a confusing system built for flexibility that now feels like a burden.

The Real Cost of Complexity

Overcomplicating code doesn’t just slow you down a little. It costs time and money.

  • It makes it harder for new developers to get up to speed.
  • It takes longer to fix bugs.
  • It makes it scary to make changes to the code.
  • Simple feature requests now take forever because you have to dig through so many layers of code.
  • You end up asking, “What does this class do, and why does it exist?”

As you add more layers, your codebase becomes harder to work with. It starts to look like a puzzle no one wants to solve. It feels more like a fragile house of cards than a solid foundation.

The problem is that in trying to prepare for every possible future need, we forget to just solve the problem at hand. The cost of maintaining overengineered code grows quickly. As team members leave, the knowledge of how the code works disappears. Fixing bugs or adding features becomes harder and slower, and production issues take days to fix instead of hours.

Complex code takes longer to fix, not because the problem itself is complex, but because the code is hard to understand and work with.

The Human Side of Simplicity

I’ve often faced criticism for choosing simple solutions, writing five lines of code where others might introduce factories or interfaces.

But this preference for simplicity does not come from a lack of understanding of complexity. On the contrary, it comes from understanding just how important clarity and maintainability are. Simplicity is not a lack of depth; it is the result of deliberate thinking. It is about solving problems in a way that others and your future self can easily grasp and build on.

Through experience, I have learned the true value of principles like YAGNI, not just as a catchy acronym, but as a practical guide in fast-moving and constantly changing environments. That mindset taught me to solve problems in a way that avoids overengineering, focusing on clarity and durability instead. It is not about showing off. It is about building something that works well and lasts.

Enterprise Doesn’t Always Mean Complexity

I understand working in large companies, where the goal is to build systems that scale, are secure, and are reusable. But here's the truth: Most enterprise systems don't need complex solutions. They’re just handling data — fetching it, transforming it, and sending it somewhere else.

Yet there’s a sad irony in how the enterprise world often rewards complexity. A pull request with intricate abstractions and layered patterns gets applause. Meanwhile, a simple, straightforward solution can be perceived as incompetence and is met with condescension. When we celebrate complexity and question simplicity, we not only discourage maintainable code, but we also make it harder for tools like Copilot to support us. The cost of this culture isn’t just technical debt. It’s wasted time, missed opportunities, and avoidable pain.

We tend to overcomplicate systems, treating basic tasks like engineering a rocket. In reality, it’s more like plumbing. Important, yes, but not rocket science.

AI Struggles with Overengineering

AI tools such as Copilot and ChatGPT can help us write code faster, suggest improvements, and even debug problems. But the power of AI only comes when the code is clear and well-structured. If the code is an overengineered mess, even the most advanced AI tools struggle to help.

While Copilot can generate code quickly, it still relies on patterns, simplicity, and clarity to be truly effective. When your code is hard to follow, AI can’t work its magic. It can’t refactor your spaghetti code, nor can it suggest improvements for systems that are poorly designed. When code lacks clarity, even AI becomes confused.

But here's the twist. AI can generate code, but it can't understand it. It doesn’t have the context you do. It can’t debug issues with the same depth of understanding, nor can it fix architectural problems. So, the faster we generate complexity, the more we’re pushing that burden onto ourselves and our teams.

Moreover, while AI can help with repetitive tasks or boilerplate code, it doesn’t help you when things go wrong. In production, when the system breaks, you need to trace back to the root cause. That’s a task that requires human understanding, and if the code is too complex, even that will take much longer. AI can speed up development, but it doesn’t clean up after you.

This is where simplicity becomes a key asset. By writing clear, concise, and well-structured code, you not only improve your workflow but also ensure that when AI steps in, it actually helps. Copilot and ChatGPT are tools to aid the process, not fix problems caused by poor code design. They cannot rescue you from complexity.

Simple Code Is Not Basic, It Is Brave

It’s easy to make code complicated. Just keep adding layers until your IDE can’t handle it.

But it takes real discipline to keep things simple. It’s hard to resist the urge to overbuild for the future. It’s easier to say, "Let’s solve the problem at hand first."

Simplicity is not about being basic. It’s about being intentional. It’s about being the kind of developer who thinks not just about architecture, but about maintainability over time.

So, the next time you want to add another layer or abstraction, ask yourself one question:

Am I doing this for clarity, or just to look smart?

You’re not writing a novel. You’re not creating an art project. You’re writing instructions for a machine and for humans who will need to read and understand them.

Write for them. Write for the person who will be on call when something breaks.

Write for your future self, who will have to revisit this code on a stressful Friday afternoon.

Write less. Think more. And remember.

The best code is the one that doesn’t need to be explained.