---
title: "Over-abstraction"
date: "2026-05"
category: "Design Patterns & Architecture"
tags: ["Design Patterns & Architecture"]
description: "Abstraction is good, no argument about that, it hides the details behind a complex implementation and exposes only the result, but all of this has a cost. For the user, what we normally want is convenience, solving the problem with one call without having to understand everything behind it..."
source: "https://enriquemark.com/en/posts/%E9%81%8E%E5%BA%A6%E6%8A%BD%E8%B1%A1"
---

Abstraction is good, no argument about that, it hides the details behind a complex implementation and exposes only the result, but all of this has a cost. For the user, what we normally want is convenience, solving the problem with one call without having to understand everything behind it. But the moment something goes wrong, the user is in fact still forced to go dig into the abstraction I was hoping to hide. In other words, abstraction is like a curtain. I've only covered the thing up, which doesn't mean it will never be pulled open. What we want is for it to go wrong as little as possible, so nobody has a motive to open it, plus one other thing: if it really does have to be opened, I'd like what's there to be in good order rather than trash everywhere. I'm only hiding the complexity backstage, not "sweeping the garbage somewhere you can't see it".

Garbage, and what I mean is over-abstraction. Maybe that's putting it a bit strongly, but the damage this kind of abstraction does is like garbage, rotting and stinking and turning your stomach. Over-abstraction (or premature abstraction) is optimizing for the sake of optimizing, and it's the sort of thing that often happens to mid-level engineers stuck in between, like me. I feel it pretty often myself, that judging whether a piece of code needs abstracting really is where ability gets tested.

On whether something needs abstracting, my own most instinctive test is the count. Crude and simple, and easy to understand. A count: showing up many times means repetition, and since it can be reused, why not wrap it up? That's right most of the time, but not always. Think that instinct over carefully and it's easy to notice that when I focus on the count, I'm not only seeing how many times it appeared, there's another thing hidden in there, which is the payoff. Why do I want to wrap it? Because a repeatedly called block appearing many times is not only bad for reading, it's painful to maintain. Yes, cutting that repetition is an obvious payoff as far as I'm concerned.

So whether something needs abstracting has to take in the payoff dimension on top of the count. If a chunk of code clearly shows up many times, but abstracting it has no obvious payoff, I need to consider whether wrapping it is necessary at all. Say it repeats three or five times, but the logic is complicated, abstracting it would burn a day of mine, and it'll never get used again in the future. Then there's no need for me to bother. This is weighed as investment and return rather than as beauty. As an engineer I'm not an artist; my purpose is to keep the product sound and usable, not to produce aesthetic value. Lovely code does make you feel good, but the cost has to be weighed clearly, otherwise it turns into a vanity project that wastes people's time and money.

Extend the thinking further and you find there's a third hidden criterion in here. When I think about an obvious payoff like "will it be used in the future", what I'm really considering is a stable capability. Like a wrench or a hammer, tools I need steadily and continuously, used often and in roughly the same situations. Code is the same. If this code needs reuse and the need and the capability are similar every time, abstracting it is a completely natural decision. Otherwise the requirements shift, today it's A and tomorrow it's B, today you're fitting water pipes and tomorrow you're digging a tunnel, and even if you've wrapped up that wrench you won't necessarily get to keep using it. Abstraction in that situation is pointless.

Carrying on from that, we can extend it further. The situations change, but what if the changes are stable too? Say my company's business runs across fitting water pipes and digging tunnels, but all of it is continuous and stable, then I can go develop several tools. Right, this is the boundary. Abstraction is good, but it shouldn't be boundless. What did I hide? You don't need to know how the wrench and the hammer are made, only how to use them, with clear responsibility and a definite purpose. What I expose to you is a capability that works out of the box, not "to use a hammer you first have to fell a tree and make the handle". The second one is a failed abstraction. For the situation of "using a hammer", there's no reason to make the user fit the handle himself, and a call that convoluted is an unclear abstraction, the result of complexity that should have been hidden not being hidden.

We can still keep extending. Suppose I made an abstraction, the hammer again, and it's convenient to use, no more picking up a rock to knock nails in with. It's a perfectly good hammer, it drives nails. It's just a little...... too heavy. Oh, it's a sledgehammer, ten kilos. Hold on, what the hell are you doing? I'm knocking in one nail and you hand me this monster? Right, that's the last thing I wanted to get to, which is performance. If I only meant to solve a small problem, and the abstraction in the end does solve it but comes with a huge performance bill, then whether that's worth it has to be weighed. We talked about payoff earlier, and everything after that really counts as an extension of it. Whether the performance bill is acceptable obviously has to be examined together with the situation. In a highly sensitive situation, even if your abstraction is beautiful, reasonable, and a textbook application of a design pattern, if it brings a performance loss you can't accept, then it's still a bad abstraction. It's like a swimming race where you run from one end of the pool to the other; even if you break the world record you'll score nothing, and you'll be shown off the premises. The wrong thing at the wrong time is exactly this kind of behavior.

Turn all of the above around and you get the bad smells. Not much reuse to be had, requirements still shifting (don't try to predict them unless you have solid evidence), payoff uncertain, boundary and responsibility unclear, and so on. All of these are signals of over-abstraction. Before every abstraction you can ask yourself, does my abstraction meet the things above? In my situation, is the payoff of the current abstraction clear? It won't get you the excellent work of a senior engineer, but at minimum it avoids a collapse and keeps the abstraction acceptable at the basic level. For a mid-level engineer, doing it best isn't the requirement. Being able to produce acceptable work consistently is the first step toward senior. That's enough.

---

**Translation note.** I wrote this in Chinese. This English version is an LLM translation, so the wording is not mine even though the thinking is. Original: [過度抽象](</zh-hant/posts/過度抽象>).
