Problems of indirection in large codebases

Dynamic indirection makes it very hard to follow control flow for anyone who didn't write the code. Just finding what functions are being called can be challenging and frustrating. IDEs are not much help since they only understand static references. Forms of dynamic indirection includes:

When exploring a large codebase I find myself wishing for more direct, straight-line code. I would prefer a big switch statement to an indirect call. The big switch statement at least allows me to see all the possible code paths. An indirect call could be calling anything and I have to globally search the whole codebase to find it. I have to hunt down where the variable holding the function pointer is initialized, which then leads me to hunting for where constructors or methods are called which pass in the callback as an argument. Often this leads to several layers of calls to find where the original callback came from. It doesn't help that C++ makes grepping for constructor calls very difficult, since there are so many syntactically-different ways an object can be constructed.

Similarly, when there is a call to a virtual method, I have to find all possible classes that implement this virtual method and hunt down which real object is actually being passed in.

These techniques are used are to increase modularity, i.e. decrease coupling. Instead of a module directly calling a function in another module, creating coupling between those modules, something (function pointer, lambda, object with virtual methods) is passed as an argument instead which allows a dynamic call. I think in most cases it would be better to defer using these indirection techniques until they are absolutely necessary.