Overengineering in Software: How to Complicate Simple Things Unnecessarily

Overengineering in Software: How to Complicate Simple Things Unnecessarily

Víctor Manuel Cañada, Mobile Development - Expert

Víctor Manuel Cañada

Mobile Development - Expert

October 21, 2024

We've all been there. You sit down in front of your brand-new MacBook Pro with an M3 processor and 32GB of RAM (yes, that beast that SNGULAR gives us and that we so deserve), ready to design the next big thing in software. You’ve got a coffee in hand, a million ideas in your head, and an arsenal of design patterns you just learned. What could go wrong? Well... overengineering!

What is Overengineering?

Imagine you want to build a treehouse for your nieces and nephews. The most logical thing would be to build something simple: a few boards, some nails, and done! But suddenly, you get excited and decide to install a pulley system, automatic doors, alarm systems, solar panels, and, why not, an elevator. The result? Your nieces and nephews now need a 50-page manual just to enter their treehouse.

In software, overengineering is exactly that: adding layers and layers of complexity that are not necessary. It's like deciding to implement your own data structure with multiple inheritance, factory patterns, and an event system that even you don’t fully understand, instead of using a simple list to store a few numbers. Why? Because you can.

The Effect of Overengineering When Learning Software Architecture

As we progress in the world of software development, it's easy to fall into the overengineering trap. Why? Because it's exciting, it sounds sophisticated, and you want to show that you can handle design patterns like a real pro. You’re learning about clean architectures, microservices, dependency injection, and all that "cool stuff that gets you all excited," and you ask yourself: "Why not use all of them in my project?"

The reality is that, while design patterns and complex architectures are powerful tools, they are not always necessary. It’s completely normal to want to try everything you learn. It’s part of the growth process. The problem comes when you realize that, after all, maybe you didn’t need that abstract factory to create a single configuration object in your application.

developers-working-on-code-2024-06-21-23-36-28-utc.webp

The Big Problem with Base Classes That Aren’t So Basic

Speaking of abstract factories and other "nonsense," let's talk about base classes. Yes, those that are supposed to be the cornerstone of your architecture but end up becoming a death trap for simplicity.

The use of base classes might seem like a brilliant idea. You think: "I'll group all this repeated code into a parent class, and done, all the children will inherit this masterpiece." But what happens when that base class turns into a Frankenstein monster with more methods than anyone can remember? Suddenly, your base class becomes a maze that hides the normal behavior of the child classes, like, for example, the lifecycle methods in Android. There’s nothing like trying to debug a bug and discovering it’s buried in some overloaded function of your base class, hidden by the fog of override!

Moreover, it often happens that not all the functionalities you group into your base class are necessary for all the classes that inherit from it. Imagine you decide that all instances of your base class "Bird" in your program must know how to swim because, well, why not? Now every time someone creates an instance of an object that inherits from your base class "Bird" (for example, the enigmatic "LandCamperoChicken"), they have to deal with swimming methods they don't need, all because someone (you, in a moment of excessive inspiration) decided it would be useful... just in case.

Pre-built Architecture? No, Thanks!

Let’s continue with the excesses and talk about those pre-built architectures that come with the whole combo: libraries for requests, event handlers, caching, and who knows how many more things. Yes, it sounds practical and tempting. Everything is ready to use, packaged with a pretty bow. But… spoiler alert: what you gain in convenience, you lose in flexibility.

Imagine you move into a fully furnished house. It might seem like a great deal, but then you realize you don’t like the couch, you hate the curtains, and that pineapple-shaped lamp gives you the creeps. But, since everything came in the package, now you're stuck with things you don’t need and can’t change without making a big effort.

In software, using a pre-built architecture can lead to a similar scenario. You end up using libraries that might not be the best for your project, simply because they come with the architecture. And worst of all, it forces you to design your software around those pre-made decisions, instead of building something that really suits your needs.

Ideally, the architectures, libraries, and tools you choose should be a tailor-made suit for your project, not a generic Halloween costume. Don’t be afraid to carve your own path and choose what you really need, rather than sticking to what’s already made.

team-discussing-mobile-application-architecture-2024-01-16-19-05-03-utc.webp

Conclusion

Overengineering is like that voice in your head that tells you that you need more complexity to be a better developer. But in reality, being a good developer means knowing when to stop adding layers and when to simplify things. Fewer layers, more clarity. That’s the real skill. And speaking of clarity, be careful with those base classes that end up hiding more than they reveal! Remember, a base class should be just that, a base, not a junk drawer where everything ends up piled up.

Lastly, don’t get seduced by pre-built architectures that tie you to decisions that aren’t ideal for your project. Build your architecture with the tools and libraries you really need, and no more. At the end of the day, your goal is to solve a problem in the most efficient and clear way possible, not create a puzzle that even you can’t solve.

So next time you’re tempted to add a new layer of abstraction "just in case," “because it’s cool and I saw Uncle Bob explain it in his latest great book,” remember: sometimes the best solution is the simplest one. Don’t complicate what doesn’t need to be complicated!

See you in the next article, where we'll continue to unravel the mysteries of development! Until then….

May the force of Rustic Chicken be with you 🐔!

Víctor Manuel Cañada, Mobile Development - Expert

Víctor Manuel Cañada

Mobile Development - Expert

I discovered my passion for programming at age 8 with an 8-bit MSX that used Basic. The computer manual helped me learn programming logic, shaping my life since then. I'm motivated by learning and sharing knowledge. I enjoy fantasy epics and psychological thrillers. I'm also passionate about motorcycle rides, seeking remote landscapes and winding roads. My programming trick: If something goes wrong, use "Pollo Campero" as the key in your debug logs.


Our latest news

Interested in learning more about how we are constantly adapting to the new digital frontier?

Contract Testing as a Service: Support your clients
Contract Testing as a Service: Support your clients

Tech Insight

November 21, 2024

Contract Testing as a Service: Support your clients

Impact Innovation: How to evaluate and design sustainable digital products
Impact Innovation: How to evaluate and design sustainable digital products

Tech Insight

November 5, 2024

Impact Innovation: How to evaluate and design sustainable digital products

PactFlow & Contract Testing: A Business Case Study
PactFlow & Contract Testing: A Business Case Study

Tech Insight

October 14, 2024

PactFlow & Contract Testing: A Business Case Study

Custom Lint Task Configuration in Gradle with Kotlin DSL
Custom Lint Task Configuration in Gradle with Kotlin DSL

Tech Insight

October 7, 2024

Custom Lint Task Configuration in Gradle with Kotlin DSL