That's not an abstraction, that's a layer of indirection

Abstraction in software is praised for hiding complexity, but many argue that what developers often add are mere layers of indirection that increase cognitive load without real benefit. Commenters contrast “deep” abstractions like TCP, filesystems, or well-designed data structures—where a simple interface truly encapsulates complex behavior—with thin wrappers, over-generalized frameworks, and premature interfaces that make code harder to debug and change. A recurring theme is that good abstractions emerge from concrete use and real repetition, align closely with the problem domain, and reduce the mental effort needed to reason about correctness, while their costs often fall on future maintainers rather than the original author.

Definition debates: abstraction vs. indirection vs. encapsulation

  • Several commenters argue much of what devs call “abstraction” is really:
    • Indirection: extra layers that just forward calls and permute arguments.
    • Encapsulation / information hiding: restricting access to implementation details.
    • Modularity: splitting code into components with minimal coupling.
  • A stricter view: an abstraction introduces a new semantic level or concept that lets you reason precisely (e.g., TCP streams over packets, “file” over devices), not just hide mechanics.
  • Others stress “generalization”: a good abstraction represents shared properties across multiple concrete cases; it usually only makes sense once you have at least two real implementations or repetitions.

What makes an abstraction “good”

  • Common criteria mentioned:
    • Creates a simpler “what” over a complex “how” (deep, small interfaces).
    • Aligns with the problem or business domain; easier to explain to non‑technical people.
    • Lets parts of the system change independently (e.g., hardware, database, protocol).
    • Rarely forces you to “peek under the hood”; the less you do, the better it is.
  • Cited positive examples: TCP, HTTP, filesystems, CRDTs, standard data structures, SQL, and classic algorithms when exposed via small, clear APIs.
  • Some argue not all abstractions must hide complexity; some primarily add flexibility or composability.

Bad abstractions, indirection hell, and leaky layers

  • Complaints about:
    • ORMs as leaky abstractions or mere mapping/indirection, often failing to really hide SQL and dialect differences.
    • Long chains of tiny wrapper functions, pointless interfaces with a single implementation, and overuse of patterns (factories, facades, adapters).
    • 3‑tier CRUD architectures and deep frameworks added “just in case” that never pay off.
    • UI layers over‑abstracted or over‑generalized, making display bugs hard to debug.
  • Examples of leaks and hidden costs: TCP vs close/reset semantics, unbuffered I/O hidden by a “read” call, ORM N+1 queries, performance surprises inside “identity” functions.

When and how to abstract

  • Strong support for:
    • Delaying abstractions until patterns or multiple implementations actually exist (“rule of three”).
    • Preferring simple, concrete code first, then refactoring into abstractions once real needs are understood.
    • Designing around orthogonal concerns and “key decisions” (e.g., storage, routing, encryption) rather than rigid vertical layering.
  • Several note an asymmetry of costs: authors enjoy immediate cleanliness; future maintainers pay for unnecessary layers.
  • Some say ultimately good abstraction choice comes from experience and judgment (“taste”), though others push for more explicit, teachable criteria.