Happy Monday, and welcome to the very first issue of The Coder Cafe! This week, our theme is code health, and we will start by discussing premature abstractions.
First, let’s define a concept that you have probably encountered many times: abstractions.
Abstraction is the process of hiding complex implementation details by exposing only essential features and behaviors that are necessary for understanding or interacting with it. In simpler terms, an abstraction is a simplified representation of a concept.
Abstractions can take various forms, including:
Interfaces: Hide the complexity of one or more implementations, providing a consistent way to interact with different underlying systems.
Modules: Encapsulate a coherent set of functions or classes, offering a clear and simplified interface for interaction while hiding internal details.
Functions: Abstract away the complexity within a block of code, allowing us to focus on what the function does rather than how it achieves it.
Premature Abstractions
You may have already heard the term premature optimization. It’s a concept highlighted by Donald Knuth:
Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.
While this statement is valid, an even more impactful problem exists in many codebases: premature abstractions.
A premature abstraction involves making abstractions on premature conjectures. A conjecture is an opinion or a conclusion based on incomplete facts. For example: “In the future, we may have to do X, so we need to make our function Y generic enough“ or “Let’s design a generic data structure to accommodate potential future requirements“.
Building upfront abstractions or designing while thinking too much about possible future needs is a waste of time, in general.
Why is a premature abstraction that bad? It can lead to various problems, such as wasted effort, increased complexity, reduced flexibility if the abstraction is wrongly designed, and slower development.
I recall an experience at a previous company where we spent an entire sprint making our codebase generic enough to accommodate future needs we anticipated but… never came, resulting in a significant waste of development time and effort.
I’m not suggesting here that all generic solutions or all the abstractions are bad. I’m simply pointing out that as software engineers, we have this tendency to do things too prematurely quite frequently. Generally, we should implement features when we need them rather than when we anticipate their potential need.
Remember: premature abstraction is the root of all evil.
One important principle to live by is YAGNI (You Aren’t Gonna Need It), which we will explore tomorrow.