Slow and expensive - The Madness of software development: Overbuilt, overbudget and over it

Updated: 2025-10-22

When I studied computer science, my goal was simple: use technology to solve real-world problems. Fast forward to today—working as a software engineering consultant—I often find myself not solving business problems, but fixing/fighting the damage caused by complexity.

Let’s explore some of the recurring patterns that contribute to this mess. As crazy they can seem, I had the privilege to see them myself.

burning money

The Rise of Technical Debt

desperate developer

Overengineering a CRUD App

Why build something simple when you can make it unnecessarily complex?

These days, building a basic CRUD web app often turns into this stack:

Frontend (JS framework) → GraphQL → BFF in Node.js → REST API → Java Backend → Database

  • Do we really need a BFF for one backend service? BFFs are designed to orchestrate data across microservices. If your app is a monolith with some external dependencies, this is pointless.
  • Why convert GraphQL to REST? Pick one. Java supports both well. Adding layers doesn’t make it elegant—it just makes debugging a nightmare.

Result: More complexity, more bugs, more cost.

But hey, at least you’ll have a conference talk: 'How We Built a ToDo App with Fewer Engineers Than Netflix'.

Try this:

  • start with a simple architecture well-structured and modular and add complexity when needed

Use The Wrong Tool: Example Using MongoDB for Relational Data

MongoDB is great for document-based storage—but not for relational data. Once you start needing joins or nested relationships, performance tanks and your codebase gets bloated with glue logic.

Try this:

  • If your data is relational, use a relational DB. It’s that simple.
  • If you mix relational and non-relational data, try to use DBs that handle these cases (e.g. PostgreSQL) or use 2 databases.

CQRS, DDD, Onion, Event Sourcing (for a CRUD?) - How Idealism Adds Useless Complexity to Your Codebase

Yes, these architectural patterns are powerful—but only when used appropriately in particular use cases. If you're building a basic internal tool or a CRUD webapp, these patterns will cause more pain than gain.

They introduce complexity, increase onboarding time, and after two years, the new team rewrites it all... if they’re lucky enough to get the budget.

Kubernetes For Hello World?

Deploying your "Hello World" app to Kubernetes might feel cool... until you’re spending $300/month and half of your time maintaining YAML files.

For almost every application with less than millions of concurrent users, Docker Compose or even a basic VPS is more than enough (if your code is at least decent).

Homegrown Frameworks: The Anti-Productivity Tool

Internal frameworks can boost productivity—if done right.

But I’ve seen a project where they built four (4!) nested frontend internal frameworks, none documented, each with its quirks. Debugging was a treasure hunt.

Frameworks without documentation or purpose become liabilities, not assets.

Bikeshedding: Interfaces, Optionals, and Folder Structures

Some developers spend hours debating:

  • Optional vs null
  • var vs explicit types
  • Feature-based vs technical folder structure

These debates rarely impact the final product and they create tensions between the developers.
If only management knew how much time and energy is lost on these trivial disputes...

Developer Behaviors That Hurt Productivity And Quality

annoying developer

Overoptimization

Yes, octal literals might be faster. But your teammates will hate you when they can't understand what’s going on. Maintainability > micro-performance (and yeah there are devs that really do that).

Reinventing JSON.parse?

When someone rewrites JSON.parse because they think it’s not performant enough... it’s a sign something’s gone very wrong. In particular if (s)he is the architect of the company.

Code Length Extremes

  • Shortest Code: Some devs (usually Kotlin/Scala fans) write cryptic one-liners that only they can read. Good luck maintaining that. Less code = less bugs, they say. These are the same guys that have to annotate their code avoid the cyclomatic complexity flag.
  • Longest Code: Others (hello Java) seem to get paid by lines of code. They avoid Spring annotations and Lombok, proudly implementing everything by hand before to move everything in abstract classes and interfacing every single service that has only 1 implementation by design.

Balance matters. Write code for humans and nice colleagues, not machines or yourself. Size can matter, but not in this context.

60+ Fields In A Class?

It remembers me the 10'000 lines of code in a function mentioned in some talks. For our bigger joy this happened in the constructor and the class was immutable.

'Weak' Immutability

"Our code needs to be immutable for safety." ... and in every method you have a .copy() call.

We Don't Care If You Think Your Style Is The Best, Just Use What The Team Decided. This Is Not TikTok

50% of the developers think to be in the 1% best in the world, everybody will have an opinion about how the code of the others is total sh*t.
Most of the developers won't read the code of the others and won't care about the common style and pattern used.
It's awful to read 3 classes of existing code and see 15 different styles. It's like to read a book in which every page was wrote by a different author (not that anybody reads anymore).

This is like TikTok, every video is similar but different. Please read and write books and reduce your TikTok time.

Don't Be Creative Naming Things

In the Database we have a table called 'Widget' ... what could be ... clear, it's a Task type. Why? It's just to annoy the developers that will work on the project in the future?
This is a present from a BA that decided that this was the best name for the table. One moment of craziness a long pain for the development and the maintenance.

Try this:

  • Use a naming convention (and follow it)
  • Be boring with the names
  • Don't abuse substances before naming objects

Managing Sprint Tasks and the Backlog

We use a task board and maintain a backlog to keep our work organized and transparent. However, this system only works if it reflects reality.

Too often, developers say, "I'm working on something that’s not on the board." When that happens, we lose visibility and alignment, and the backlog turns into a mix of a "Bucket List" and a "Memory Bin." This undermines sprint planning, daily stand-ups, and ultimately, delivery predictability.

Try this:

  • Treat the board as the single source of truth. If you're working on it, it should be on the board.
  • Before starting any task, check in: Is it already tracked? If not, add it and update the status.
  • During stand-ups, make sure each developer references their active tasks directly from the board.

Don’t Commit and Merge 100+ Classes Right Before the Code Freeze

Yes, this still happens—and it creates major headaches.

Last-minute merges introduce:

  • Dependency hell for other developers
  • Delays in QA, which now must revalidate significant parts of the app
  • Risk of missed bugs slipping into production
  • Potential postponement of the release

Try this:

  • Submit large or complex changes early in the sprint or release cycle.
  • Communicate clearly before merging changes that affect shared modules or APIs.
  • Follow a code freeze buffer: No major merges in the last 24–48 hours before release unless agreed upon by the team.

The Repository Is Not Your Bedroom — Keep It Clean

Nothing is funnier (or more painful) than navigating through 50+ abandoned branches, trying to decipher what your now-sick colleague was working on.

Worse still: a teammate leaves the project and leaves behind a legacy of half-baked, committed-but-never-merged branches. It seems like everyone loves creating branches to tweak a couple of files... then forgets they exist.

And let’s not even talk about naming. If you're lucky, there’s a task number in the branch name. More often than not, you're stuck with gems like required-changes, initial-commit-new-feature, or final_test_merge_fix_temp—floating somewhere in a spaghetti tree of chaos.

Try This Instead:

  • Review and delete stale branches regularly. If a branch can’t be justified, it shouldn’t exist. You shouldn't need a meeting to decide that.
  • Use a clear and consistent branching structure. Separate release/, feature/, hotfix/, and test/ branches cleanly. No one wants to find test-foo next to main.

Repositories are collaborative tools, not personal junk drawers. Keep them organized so future-you—and your teammates—don’t suffer.

Management Mistakes That Wreck Projects

manager

No, You Don't Need Microservices To Show In The Web The Data From The Database

When investors and managers ask for microservices, because they want 'scalability' ... they are asking an F1 car to drive in the mountain roads.
Management should seriously stop to read shiny brochures from big tech companies.

Developers Aren’t Interchangeable Players

You can’t just move a backend dev to frontend and expect magic. Specialization exists for a reason.
Moving a backend dev to frontend without support is like asking a dentist to do heart surgery because ‘they both are Doctors and work with tools.’

Not All Developers Code at the Same Speed, Few Good Are Better Than Many Average

Management sometimes assumes that 3 devs = 3x faster. That’s not how it works.

Some devs are 3–5x more productive than others, especially in quality and autonomy. These use cost more, the management should understand that sometimes 2 good developers that works well together are much more productive than 7 random developers put together.

Talent Moves Where It’s Valued. Short-term savings, long-term expenses.

Great developers—like great athletes—seek growth and compensation. Moving your team to a cheaper country might save money on paper, but costs quality and retention.

When a talented dev from a "low-cost" region finds a better opportunity, they’ll take it. Smart companies build teams where they can attract and keep talent, not where they earn less.
Ironically, this ‘saving strategy’ often leads to lower quality, higher turnover, and eventually... higher costs.”

KISS for your safety

Developing software isn’t just about writing code. It’s about making thoughtful decisions—when to be pragmatic, when to follow best practices, and when to say no to complexity.


WebApp built by Marco using Java 24 - Hosted in Switzerland