if you ask a programmer "what is an abstraction", you'll hear words like "hiding details from programmers" or maybe something about "indirection". ask "why" and you might hear terms like "code reuse" or "deduplication", but probably not much more than that.
now i'm not saying that's the wrong answer, i'm not sure i could be so kind.
what i am saying is that these answers are probably the least helpful ways to think about abstractions. these answers only talk about what and how, and they barely touch on why. only justifying things in terms of "lines of code saved" or "information hiding" misses out on possibly the most important thing about an abstraction:
an abstraction can make code easier to write
let's take the humble iterator as an example, and then i'll talk about why this framing matters.
(the answer is code review, but let's stay on track for now.)
a humble iterator
cursor = iter([1,2,3])
while cursor.next() {
print(cursor.value)
}
you might point and yell out "see, detail hiding", then "code reuse too!", but there's a little more going on, and it's worth stopping to talk about it
the abstraction is separating details
the code that prints out the data isn't intermingled with the code that navigates through the structure. i can change what i'm printing out without having to touch the list handling code. i can change the list handling without worrying about formatting text.
although you can say "that's hiding details", you might be better off saying that it's "keeping unrelated details apart from each other." it doesn't need to be reusable. sometimes it's worth using an abstraction to break application-specific and application-agnostic code apart, even if nothing else touches it.
that said,
an abstraction is about sharing details, too
with an iterator, you only have to write one function to handle a variety of different structures instead of one per type. you only have to implement one iterator too.
the iterator is sharing the same details across the codebase, not hiding it. you can call it code reuse, if you like, but it's not always about repackaging something for more general use.
an abstraction is about making bugs harder to write
from personal experience, i've rarely fucked up a for x in y loop, but every time i type i++ i know i'm rolling the dice. simply put, using an iterator makes it a lot harder to get off-by-one errors.
an abstraction doesn't need to be reusable, or present a reduced interface to be useful. it needs to present an interface that's less effort to use, and although smaller interfaces are often better, sometimes a big and nasty interface makes the rest of the code easier to write.
an abstraction is about lowering the cognitive overhead
it's not just "keeping details apart" in the parnas style, it's not just "keeping details together" out of fervent deduplication, and it's not just "make bugs harder to write". it's all of these things, and more: it's a means to reduce cognitive overhead.
in a complex system, all local changes can have global effects, and that's why we use abstractions. they're a means of compartmentalizing changes in a codebase, letting you fix the bug without having to understand everything about the rest of the code. an abstraction can let you work on code in isolation.
it's a fancy way of saying "abstractions can help you avoid thinking", but the problem with "avoiding thinking" is that engineers like to think that means "what if you could write yaml instead of code" and we all know how that ends up.
either way, if an abstraction lets you make changes without total system knowledge, it's doing its job.
abstractions don't leak, either.
leaky abstractions are just another way of talking about "information hiding"—abstractions can share details, abstractions can partition details, but sure enough, they don't really hide anything from the programmer.
despite people yelling "all abstractions leak detail" no-one stops to think "maybe we shouldn't be using them to hide things in the first place." alas
abstraction isn't just about "lowering line count" or "can i make a library out of this?"
sure enough "having less software" and "having software I can use for other things" are reasonable excuses for an abstraction, but they miss out on all the other opportunities to lower cognitive load. framing things in this way just leads to the usual over-engineering mess that we call our industry.
an abstraction needs to be re-usable, so for some reason we have 40% of a generic implementation of a tool. an abstraction also needs to hide details, so although we promised we'd make it reusable, we've also peppered the codebase with business logic like a mobster shooting lead on valentines day.
it's easy to forget the point was "making your life easier"
but why does any of this matter?
when you look at abstractions as primarily tools for deduplication, you're not actively using them to make your life easier. you're hoping that "less code is less problems" and for the most part, that does work on smaller code bases.
in larger code bases, building something complex out of simple parts doesn't just mean applying thick layers of glue code, it means building something where all the complexity is in how the parts talk to each other .
then when it comes to cleaning up the code base, it can be hard to justify using an abstraction, after the all the pain and suffering the last guy inflicted. there's no code to reuse, there's no hope of hiding anything in the spaghetti apocalypse, abandon all hope all ye who enter here.
in other words, it matters because we keep making the wrong decisions about when and how to use abstractions.
that's why the answer is code review.
you want to be asking yourself:
- does it make it harder to write bugs?
- does it make it easier to write correct code?
- does it isolate code from changes in the rest of the code base?
before you even begin to think about "code reuse" or "indirection."
i can't say it'll make your code any better, but it might stop things from getting worse.
coda: abstractions are often showboating
as much as I beg you to use abstractions to make your life easier, it does feel like we're only good at using them to show off. one example is university students. they learn abstraction for one purpose above all else, passing exams, and it's a hard habit to break.
all the examples of abstractions are toy examples, over-engineered by default to demonstrate an idea, and every abstraction they implement exists for the same purpose: demonstrating knowledge.
when it comes to writing a feature, a handful of if statements just doesn't feel as good as an abstract class, two concrete instances, and a visitor pattern—and there's nothing quite like a fresh graduate writing their first plugin system, and i hope you never have to experience it.
that's why "code reuse" and "indirection" are "not even wrong" answers. that's just how we justify our little flourishes in our day jobs, they're absolutely nothing to do with the code that's been written.
