← Lectures
Back

1 / 7

Next

Slide 1 of 7: An abstraction is a promise to forget

Arrow keys move through the slides while these controls have focus.

The theatre is not shown, because your system asks for reduced motion. The lecture itself is below, in full.

Lecture · 7 slides

What abstraction costs

Abstraction is the one technique in programming that is almost never argued against. Duplication is bad, coupling is bad, global state is bad; abstraction is simply good, and more of it is better. A junior engineer is taught to extract a function, then a class, then an interface, and at no point is told what any of that costs.

It costs something. Every abstraction is a promise that you can use a thing without understanding what is underneath it, and that promise is kept less often than the word implies. When it holds, it is the most powerful idea in the field: it is why you can write a program without knowing which processor will run it. When it fails, it fails in a particular and expensive way — the detail you were promised you could ignore surfaces in the middle of something unrelated, and now you must understand both the thing and the layer that was supposed to hide it.

This lecture is not an argument against abstraction, which would be silly. It is an attempt to price it, so the decision to add one can be made with both columns visible.

AssumesYou have at some point added a layer to a program and wondered afterwards whether it helped.

  1. Slide 1

    An abstraction is a promise to forget

    The definition worth carrying around is not about hiding detail. It is about which details you are being promised you will never need again.

    typescript

    Two methods. Behind them might be a hash map, a file, a database on another continent. The promise is that you may write your program against those two lines and never think about which — and when that promise holds, an enormous amount of code becomes possible to write and to test.

    Notice what has been promised away, though. Latency. Failure modes. Whether put is visible to a later get immediately or eventually. Whether the key may contain a slash. Whether two puts at once are safe. Every one of those is a real property of the thing underneath, and the interface has said nothing about any of them.

  2. Slide 2

    Every abstraction leaks

    Joel Spolsky's law, stated in 2002, is that all non-trivial abstractions leak to some degree, and the years since have not produced a counterexample.

    The leak is not a bug in the abstraction. It is what happens when a detail that was abstracted away turns out to affect something you can observe. An object-relational mapper lets you write a loop that looks like it touches memory and in fact issues four hundred queries. A network filesystem looks like a local one until the network is slow, and then it looks like a hung process. Virtual memory makes your program appear to have as much space as it wants, and the program that exceeds physical memory does not fail; it becomes a thousand times slower for reasons invisible in its source.

    The cost this imposes is precise: to debug a leaking abstraction you must understand both levels, plus the mapping between them, which is more than you would have needed to know without it.

  3. Slide 3

    Indirection is not abstraction

    The most common failure is not a leak. It is a layer that hides nothing, because it exposes exactly what it wraps.

    java

    There is a new file, a new interface, a new name to learn, and no detail has been forgotten: the caller still thinks in terms of finding a user by identifier, and the layer has passed the request through unchanged. If the repository grows a second argument, so does this. If it starts throwing, so does this.

    A real abstraction lets you stop thinking about something. Indirection just moves where you have to look. The test is simple and worth applying before adding a layer: name the thing a caller no longer needs to know. If you cannot, you are adding a hop, not an abstraction.

  4. Slide 4

    The cost you can measure

    Some of the price is countable, and it is the smaller half.

    Each layer of indirection is a call that may not be inlined, a virtual dispatch that may not be predicted, an allocation that would not otherwise have happened. On modern hardware this is usually irrelevant and occasionally decisive; the cases where it is decisive are inner loops, and they are exactly the cases where people reach for abstraction to tidy up the mess.

    The measurable cost also includes build time, binary size, and the number of files someone must open to follow one request from entry to answer. That last one is the bridge to the expensive half: it starts as an inconvenience and becomes, past a certain depth, the reason nobody on the team can hold the system in their head.

  5. Slide 5

    The cost you cannot measure

    The real price is paid in understanding, and it does not appear in any profile.

    An abstraction has to be learned. It has a name that means something slightly different from the ordinary meaning of that word, a set of things it does and does not handle, and a set of situations where it behaves surprisingly. That knowledge lives in one person's head until it lives in nobody's, and the comment that would have explained it was not written because the code was self-documenting.

    Worse, a wrong abstraction is harder to remove than duplicated code. Sandi Metz put it as a rule: duplication is far cheaper than the wrong abstraction. Copied code can be deleted in an afternoon by someone who has never seen it before. A layer with nine call sites, each of which has bent slightly to fit it, has to be understood before it can be undone.

  6. Slide 6

    Waiting is a strategy

    Because abstraction is expensive to remove and cheap to add later, the timing matters more than the design.

    The rule of three is crude and works: write it once, write it a second time and wince, and only on the third occurrence look at all three and ask what they have in common. Two examples are not enough to see the shape — the abstraction you would build from two is usually a description of the first one with the second one's differences bolted on, which is why so many configuration options exist.

    This is also the honest answer to why premature abstraction is worse than premature optimisation. A slow function can be made fast without anyone else noticing. A wrong interface has to be changed everywhere it is used, and by then it is used in places nobody remembers.

  7. Slide 7

    What makes one worth it

    Set against all that, the good ones are the foundation of everything, so it is worth being able to recognise them.

    A good abstraction is one you can use correctly without reading its implementation, and whose failures are described in its own vocabulary rather than in the vocabulary of what it hides. A file handle is a good abstraction: you can read and write without knowing about sectors, and when it fails it tells you about permissions and disk space, which are file concepts.

    The best of them let you forget something permanently. Nobody working today thinks about register allocation, and that is a compiler abstraction that has held for sixty years. Garbage collection removed an entire category of bug from most programs. These are not layers over existing ideas; they are places where somebody found a genuinely simpler way to talk about the problem.

    That is the bar, and it is much higher than "this code appeared twice". Most of what is called abstraction in a codebase is filing, and filing has its uses, but it should be recognised for what it is and priced accordingly.

Works in the collection

The arguments above are hanging on the walls of the museum, in one form or another. These are the ones worth looking at next.

The rest of the programme

  1. Compiled, interpreted, and the space betweenA distinction that stopped describing anything decades ago, why it persists, and what is actually different about the machinery underneath.
  2. What a compiler actually doesFour jobs in a row, each mechanical, none magic: text to tokens, tokens to a tree, a tree to a judgement, and a judgement to instructions.
  3. Functional programming and object orientationTwo ways of arranging a program, what each one genuinely makes easy, and the trade-off underneath the argument that neither side can escape.
  4. The principles of object orientationEncapsulation, inheritance, polymorphism and SOLID, one at a time: what each actually claims, which held up, and which its own community abandoned.
  5. Types: what they can and cannot proveA type checker proves one thing about every possible run of your program. Knowing which proposition explains both the enthusiasm and the disappointment.
  6. Null, and the mistake its inventor apologised forTony Hoare called it his billion-dollar mistake. What was actually wrong with it, what the alternatives cost, and why the fix took forty years to arrive.
  7. Immutability, and what it is not free ofValues that never change buy sharing, comparison and time travel. They are not free, and it is worth knowing where they are expensive before you commit.
  8. Recursion, and why it feels like cheatingA function that calls itself looks like an unpaid debt. What makes it terminate, what it costs on the stack, and why some problems resist any other shape.
  9. Big-O, and what it deliberately ignoresComplexity notation throws away constants, hardware and every input you will actually see. Knowing what it discards is what makes the number useful.
  10. What regular expressions cannot matchThere is a precise boundary around what a regex can recognise. It explains the famous refusal to parse HTML, and why some patterns run forever.
  11. Concurrency is not parallelismOne is a way of structuring a program, the other a way of executing it. Keeping them apart explains why async helps a web server and threads often do not.
  12. Errors: exceptions, values, and what each hidesThrowing makes the happy path readable and the failure paths invisible. Returning errors as values does the opposite. Neither side has won.
  13. Why programs are hard to changeSoftware is called soft because it can be edited. Why editing gets harder every year, what the mechanism is, and which of the usual remedies work.

← All lecturesAtrium