Avoiding Over-engineering
and Unnecessary Dependencies
Understanding and Defining the Purpose
When we create software, its purpose is to solve a problem. Every piece of software not written as an end in itself does this. Some do it with focus, others with a lot of distraction—because they try to handle all sorts of additional things. The more clearly you define this purpose, the more purposeful the solution becomes.
Demarcating the Problem Context First
Right at the beginning, it’s crucial for me to understand and define the use cases. Even before heading towards code or infrastructure, the environment must be demarcated. Important decisions can already be made at this level—for instance, whether the outcome will be one single solution or several interacting ones. Each concrete problem context can then be considered and evaluated on its own.
Once this clear framework is established, the subsequent steps can run straightforwardly towards the goal.
The Importance of Small, Tangible Steps
Take small steps! If you lose your path, it’s not a great loss. Simply walk back a few steps and continue. Therefore, every small step should have something tangible as a result—something to judge the direction by. However, small steps are often prevented by complexity.
Avoiding Over-Engineering with Core Principles
Avoid the temptation to tackle every challenge with standard solutions. Don’t immediately search for a collection of libraries or additional services for every small task. Concentrate on the problem—and try to solve it in an absolutely simple way. The following rules are essential for me in this:
YAGNI (You Aren’t Gonna Need It): It’s not uncommon for a developer to try and solve future problems directly by making the current solution more comprehensive. They invent artificial edge cases to create the “perfect” solution. But very often, the problem does not get more complex than what we see today.
KISS (Keep It Simple, Stupid): If you think your solution might be laughed at for being too simple—it’s probably just right. It should be so easy to understand that absolutely anyone can grasp it. And the irony: Simple solutions are often harder to create than complex ones.
DTSTTCPW (Do The Simplest Thing That Could Possibly Work): An urging to think no more than necessary—and to always pursue the most straightforward solution. Straightforward is maintainable. Straightforward is transparent. Simplicity is fun!
Worse is Better: To be taken with a grain of salt—it can be easily misinterpreted. At its core, it’s about delivering, not about academic embellishments. But: Do not confuse this with lazy shortcuts or sloppy work. Pragmatism is sometimes “worse” than the fully formulated ideal—but that can be the way forward: to simplify the implementation for the specific case. Sometimes a small shell script does the trick—instead of monstrous automation tools.
Rule of Three: When I start with a function, I don’t worry about generalization for a long time. I want to give my idea a face. I want to experience very early how it behaves. Abstractions come later. This rule says: Repetition is okay. Generalization only happens with the third variant.
The Hidden Cost of Dependencies
It’s not just about simplicity, straightforwardness, and focus—but also about reducing dependencies.
A few examples:
- Does it need a comprehensive policy engine, or is a user role check sufficient?
- Does it need Kubernetes with Helm charts, or is Docker Compose enough?
- Do we have to implement Event-Sourcing with CQRS + an Event Store—or are SQL updates and a few triggers sufficient?
- Do we need GraphQL, or is a RESTful implementation enough?
From small libraries and frameworks to standalone services—the open-source world offers an unmanageable number of building blocks. “Standards” have been established in many areas from this. And yet: All of these are dependencies—that you might be able to avoid.
Even if open source appears free at first glance. Even if the prevailing opinion is that open-source projects “can’t disappear.” Ask yourself:
What happens if this piece of code—that I’ve taken as a dependency—is no longer developed by anyone tomorrow? Do I have a problem then?
Open source creates the illusion that everyone is engaged with all projects. The fact is: the majority only consumes. It’s likely that most—especially with larger projects—couldn’t even understand them without significant effort. Be aware of this the next time you pull a “standard” dependency into your project. (Tip: Search for “FFmpeg” and “Google”—an interesting context.)
The Core Philosophy
Start dumb. Stay with the problem. Because over-engineering is the arrogance tax.
References & Further Reading
- YAGNI – Martin Fowler’s Definition
- KISS – Wikipedia
- DTSTTCPW – c2.com
- Worse is Better – The Original Essay by Richard P. Gabriel
- Rule of Three – c2.com
- OSS Risks & The FFmpeg Example