An abstraction is useful when it hides the right things.

Good abstractions:

  • TCP/IP hides physical medium; you think in packets
  • SQL hides storage layout; you think in sets
  • Functions hide implementation; you think in transformations

Bad abstractions hide the wrong things — they leak. When a leaky abstraction breaks, you have to understand both layers at once, which is harder than understanding either alone.

Related: relational-model, @bob / go-concurrency-patterns