I once worked for a company that had attempted to analyse and document its business processes, and the way the approach was described to me by the author was that he had written the process documents like computer code, but in English. He was not himself a programmer.
What this meant was that there was extensive use of indirection and cross-references in the documentation, used like procedure calls would be in a programming language. This was done intentionally, to avoid writing redundant information into each document, and the possibility of update anomalies. This was not web-based with hyperlinks, it was paper-based - and as a series of separate documents, not as a bound book with a table of contents.
Unfortunately the number of levels of indirection - usually between half a dozen and a dozen - was unmanageable from a human factors point of view.
Not only unmanageable for the intended audience - trainees and similar - but even for the author. The documentation was actually riddled with circular references, or references left dangling due to the target document being updated without regard to it being a call target. My brief was to suggest ways of improvement.
My suggestion at the time was that one level of indirection would likely make this documentation baffling for new staff, and two levels would give the author no hope of maintaining the referential integrity of his own edifice.
I wonder whether the same rule of thumb is actually close to the truth for programming, that one level of indirection makes navigating the code a skilled job (a thing requiring training and experience in its own right), and two levels is where most codebases (written by average programmers on timescales considered reasonable by management) begin to feature serious defects.
This isn't meant to be taken too literally and without caveat. Simple pure functions that operate on local variables, especially those that implement mathematical operators like
Math.Add, don't need to be treated as a layer of indirection. And simple helper methods, like
Log.Write are typically safe enough when they do what they say on the tin.
I'm talking about methods that do the heavy lifting of describing and implementing business procedures and operating on shared data (either shared across disparate parts of the application code, or in a database and shared across applications and sessions).
So my rule of thumb is really speaking about methods that have to be investigated before it's possible to understand exactly what they do and what part they play in the system. In my view, one layer of indirection is complicated, and two risks frequent failure without above-average care and study.
And I'm speaking about indirection in executable methods, but it would equally apply to data structures. Representing simple mathematical objects like regular multi-dimensional arrays with pointers, and containing values with common meanings, might go to one or two more levels without introducing fiendish complexity. But in a database containing general business records, or in an application that manipulates them, one level of indirection in structures will make reasoning and correct coding complicated already, and two is reaching an extreme.