We can solve any problem by introducing an extra level of indirection.1
...except the problem of too much indirection.2
And too much indirection is the third root of all evil.3
- Indirection refers to dispatch of communications via an intermediary. The intermediary is the cause of indirection, and is (strictly) an entity capable of examining a message and making a decision regarding it (e.g. on whether to forward it or answer it, or on where to forward it, etc.). There may be a chain of such intermediaries along a communications path, each constituting one level of indirection. ↩
- For instance, to fix a slow storage system, one may reckon:
- To add caching, an indirection of access. On each request, the cache manager decides whether to serve the request from the cache or to forward it to the storage system.
- To parallelize and distribute access, an indirection over a load balancer. On each request, the load balancer decides which storage node to forward the request to.
- To reduce iterated strength4 — such as by refactoring logic out of loops or functions when computed effects can be shared across iterations or function calls (a sort of caching?).
- At this scale of complexity, one perhaps needs yet another indirection in the form of a compiler (even real programmers need a compiler!).
As the system complexity explodes, one reckons the collective ought to be refactored into smaller, "atomic" components, each abstracting away specific complexities and exposing a reasonable API for the next level of indirection to consume. But the complexity of each subsystem soon matches that of the original system. Voilà: the original pain is rediscovered, and recursion2 beckons. ↩ ↩2 - The first two are premature optimization, atrocious naming, and off-by-one errors. ↩
- Made up our own terminology, have we? ↩