Overengineering
We all agree that overengineering is not good for a project. Yet, identifying overengineering is not straightforward. This is what I try to accomplish with this article.
How can we identify overengineering in our day-to-day?
There are two primary sources of overengineering:
Solving problems we don’t have.
Wrong abstractions.
Solving Problems We Don’t Have
Premature Optimisation
There is even a famous quote about it:
Premature optimization is the root of all evil.
For example, when we implement a solution for millions of users having only hundreds. Or when we spend days developing an O(log n) algorithm instead of using the easy O(n) when we know that “n” might be mostly a few hundred.
Unsolicited Features
Developing related functionality that nobody asked for just because it’s cool or we want to work on it.
For example, we want to determine whether a version is greater than another. Overengineering is supporting from the beginning the alpha and beta non-standards. Instead, assume the strings always follow “MAJOR.MINOR.PATCH” semantic versioning.
Automating Manual Tasks
There is a comic strip by xkcd that I love and helps us understand when it’s valid to work on automating manual tasks:
Spending days to save a few minutes per month is overengineering.
Wrong Abstractions
Thin Abstractions
A common overengineering flag is wrapping a library with a thin layer just in case we ever want to change the library. This wrapper is considered a sign of poor software design in A Philosophy Of Software Development.
In this book, a principle called “The Deep Abstraction Principle” suggests that abstractions must be more complex than the interface.
No Meaning
Sometimes, we take the DRY principle too seriously and develop abstractions only because we notice a similar code in multiple places.
For example, we use the following styles in two components:
padding-top: var(--spacing-2x);
margin-left: var(--spacing-3x);
word-break: break-all;
color: var(--primary);
To avoid repeating ourselves, we create a UI component with these rules. Does this component mean something in our component library?
Start With the Abstraction
Developers love to find patterns and solve generic problems. That’s why it’s easy for us to come up with a generic solution even though we are solving one specific problem.
For example, implementing a twelve-grid system when developing the first page of the application.
Hard-to-Pick a Name
Building an abstraction and then having a hard time naming it. This is a sign that the abstraction is incorrect. It either does too much, doesn’t do enough, or does different unrelated things.
For example, picking a name for the meaningless component cited above is hard because it doesn’t do only one thing. The component has spacing, word breaks, and color rules.
Non-Obvious Code
Last but not least, if the code of an abstraction is not obvious, then something is off with it. Abstractions should have one responsibility; if we can’t explain them, we should review and rewrite them.
Avoid Overengineering
Overengineering comes at a high cost. Learn to identify it and avoid falling into it.
Don’t solve problems you don’t have and think twice before implementing an abstraction.
Thanks to Yusef and Michal for reviewing this article 🙏