We rely on abstractions to make sense of the world. Your dog is not really a "dog." There's no such thing. The word is a generalization for a group of entities that share a similar genetic make-up. But it's easier to just say, "dog," and we all understand because we generally agree on the things the word stands for.

There are more problematic abstractions in natural language, of course. For example, "caucasian." What if a person identified as such has one parent whose ancestors were from Africa, but the person looked "white"? The very idea of race, in fact, is an abstraction, but is a bit more complex than "dog," and so it is rather leaky. A leaky abstraction is an abstraction that fails to sufficiently account for the specifics it purports to generalize. An abstraction tends to leak as it becomes more complex and attempts to cover more details and edge-cases.

Software development is full of abstractions. We couldn't do without them. Yet leaky abstractions are particularly problematic because they often directly and negatively affect productivity. Joel Spolsky has famously discussed this years ago.

One of the keys to a successful software project is appropriate levels of abstraction. We must know when to make an abstraction and when not to. This skill is related to knowing when not to optimize. I'm not saying not to abstract or optimize. That would be silly. I'm saying to do it at the right time, and only when you need to. Knowing the right time and when is the rub.

Writing the same code over and over again may be a sign that an abstraction is called for, whether a function, class, module, or whatever. But, first, make sure doing it the ugly way hurts first. Until it hurts you don't enough about the problem to effectively solve it with an abstraction.

Even simple abstractions don't always cover edge cases, and you'll likely need to modify your abstraction or create yet another one to salvage your original one. This happens most often when parameterizing functions. It's not always a bad thing to add parameters to a function to cover edge-cases, but do think about whether your original function is really an appropriate abstraction in the first place. Why? Because as a function's parameter list grows it becomes more complex for the caller and harder to debug. It's a sign that the original details it was supposed to hide are more complex than you thought.

Software abstractions can also be problematic when the person reading your code wants to know the implementation details without following multiple levels of function calls, especially if the abstraction doesn't do that much. Abstractions that are too shallow simply hide reality for no good reason.

On the other hand, think twice if the abstraction is complex. Unless you're careful, you are hiding complexity by making another complex thing, which in turn makes the whole project more complex. That's not necessarily a good thing. You're kicking the can down the road. Again, creating a complex abstraction may be called-for. Perhaps the can belongs down the road. This is a judgment call, and no blog post or class can teach you when to make abstractions as you code. But do be aware that nothing is free in software development.

Finally, dependencies are a form of abstraction. A dependency that doesn't pull its weight is a too shallow abstraction. Relying on these types of dependencies is penny-wise and pound foolish because you've just introduced a possible catastrophic vulnerability in order to save yourself a few minutes, or because you're under the illusion that copying and pasting code is always bad. It's not.

Also, dependencies that abstract over details you are completely ignorant about can be problematic. Of course, we can't know everything. We don't code websites with binary. However, within your domain you should at least understand what the dependency is doing for you.

Our tools of the trade are there for a reason, and we shouldn't be shy about using them when appropriate. But stop for a moment before reaching into the toolbox, and think about whether you need the tool you're about to use, or whether it's simpler, faster and more effective to use your hands (at least for now).