Nonleaky Abstractions

Hello everyone,

Not thát long ago, I read Joel Spolsky’s article about Leaky Abstractions.

Tl;Dr: Most abstraction layers that are introduced only work some of the time, with the net result that you both need to know the new abstraction and what is actually happening behind the scenes.

I’m currently learning the C++ language, which is full of leaky abstractions and oddities that exist for historic reasons.

Lately, I ve been a little bit taken aback by the realization that so many abstractions we have (in life in general, but especially in computer development) are in fact (quite) leaky.

So, I have a question for you all: What kind of abstractions do know or use that you find awesome? That do it right? That do not feel leaky?

2 Likes

How about asp.net webforms, I’m pretty sure there wasn’t anything leaky about that :wink:

ps. sorry it’s not a real answer

1 Like

Well in C++ I love opaque types, so that the caller cannot even glean information about a structure except by going through the interface. ^.^
(The PIMPL Idiom)

1 Like

They simply don’t exist in the most general case. It is not a black/white, but more of how much grey can you stand before things fall apart.

Every abstraction eventually turns into a giant ball of mud once you exceed the design scale of that abstraction. People bag on Rails, but Rails is a great abstraction as long as you stay with it’s limits.

Assembler is a perfectly awesome abstraction, but the scale at which you can use it effectively is pretty small.

Where things get “leaky” is that an abstraction is actually two things; the concept itself and the larger domain in which it lives. Leaks occur when you move the boundaries of the domain w/o updating the abstraction.

BEAM messages are a great example of this, as long as the number of nodes is less than X, it works reasonably well and you don’t need to have detailed understanding of the mechanism to use it effectively. However, once you exceed X, details of the implementation become extremely important and you need to redesign a new abstraction to work in the larger domain.

5 Likes

Very true I think they key metric is how soon does it happen for BEAM example if you have a proper hardware solution say Xeon E7-8890 based 8 way boxes connected via EDR infiniband you will cover pretty much 99% of the use cases without breaking an abstraction. Us being software devs we often forget about hardware side :slight_smile:

1 Like

The small one that allow me to delete it all and recode it from scratch if i need.

The only abstraction that make sense are really high level one. I personally keep two of them.

Alan Kay and Carl Hewitt “it is independant interpreters all the way down!”

And Barbara Liskov ADT.

In my opinion abstractions work only in a small subset of use cases. For these they are perfect. However if you stray from these, things get difficult. I think abstractions should provide a way to get down into the lower levels to allow for unexpected customizations easily. Things get difficult when the layer of abstraction wants you to believe that there is nothing beneath it.

If an abstraction allows you to get down into the lower levels doesn’t that mean that the abstraction becomes even more leaky. Isn’t the solution to be very precise on what the abstraction does and when it is applicable?

1 Like

Yes. My point is, most abstractions have to be leaky, an abstraction may not work for your 100% of the time. However, just because it is difficult to work with in 10% of your code, we don’t abandon it. Take Ecto for instance, it is a nice ORM works beautifully most of the time. However, you do need to know SQL not to shoot yourself in the foot. Without the knowledge of the lower level your code will be inefficient. For instance, if I wanted to get a count of users using Ecto, I could 1. Do a Repo.all(User) |> length or 2. Repo.one(from u in User, select: fragment("count(1)"). To a guy who doesn’t know how databases work the first option may seem more elegant. Or for a GenServer abstraction we need to know how messages are sent to processes.

1 Like

Well actually in ecto you’d do:

from u in User, select: count(u.id)

^.^

Or even

Repo.aggregate(User, :count, :id)
1 Like

But that just gets back to my point that you really need to know what the abstract does which will tell you when to use it and when not to use it.

2 Likes

Yes, I think this is the proper approach.

When writing C++, I find it very hard to find out when it is better to use the high-level C++ features, and when I should just revert back to e.g. the C-function memcpy() because of efficiency.

The only way to know yourself when an abstraction layer is applicable, however, is by either knowing the stuff it abstracts, or by having indeed extremely clear documentation.

Maybe that might be what it boils down to in the end: Clear documentation. Hmm…

FYI (2017-Jan-05): DaedTech: Plugging Leaky Abstractions

1 Like