The idea is to read configuration files to artificially inject these dependencies into the code model fabricated by NDepend. As every relevant user requests we put this feature in our TODO list but I would like to explain here why it is not a top priority feature.
Quick remind on abstractions
Why do we use abstraction in our code? There are plenty of ways to define abstractions like interface, generic, delegate, base class/polymorphism , IoC through reflection, C#4 dynamics… :
100% of our code could be rewritten with C style code without any abstraction. The reason why we use abstraction is to make our source code more understandable and as a direct consequence, more maintainable. Abstraction is a mean to isolate implementations (i.e method bodies). If the MethodA() receive an object through an interface, when calling a MethodB() on this interface, the developer of the MethodA() cannot know anything about the class(es) that implement the MethodB().
From the MethodA() perspective, the MethodB() implementation is something abstract. The benefit is that the developer in charge of writing the implementation(s) of MethodB(), can refactor it more easily, because its caller(s), like MethodA(), are not supposed to rely on some implementation details. In practice, this works pretty well and abstractions bring welcomed flexibility to make our code more refactorable, which leads to more maintainable program, which leads to more agility, which leads to more productivity, which leads to less cost.
Static vs. Dynamic view: Abstraction and Dependency
If we look at abstraction through the dependency prism, we can see that it helps reduce the complexity of the method/class call graph.
Thanks to the interface IFoo and the polymorphism, MethodA() can depend on one abstract MethodB() instead of N concrete MethodB() implementations.
This shows clearly that there are 2 call graphs to consider. The one that can be inferred from the source code, i.e calls between methods in the source code, and the one that can be inferred by looking at calls between method implementations at run-time. It makes sense to qualify the first graph with the term static, and the second one with the term dynamic.
This example also shows that the dynamic dependencies graph is more complex than the static dependencies graph. This comes naturally because the static dependencies graph is a sub-graph of the dynamic one. Without surprise, abstraction helps decreasing complexity.
Static structure is the key
The idea I would like to defend now is that when it comes to understand and maintain a program, one need to focus mostly on the static dependencies, the ones found in the source code. Knowing dynamic dependencies (who calls who at runtime) can make sense for example to profile performance or to fix some particular bugs. But most of the time invested to understand a program is generally spent in browsing static dependencies.
What does it mean to understand some code? IMHO understanding some code means to be able to recover the mental state of the developer(s) who did the code (maybe it was you a few months or a few years ago). This way you’ll be able to refactor the code and to extend its features to abide by new functional requirements.
There are really 2 levels of understandability: the micro-level, i.e understanding methods bodies and their immediate interactions, and the macro-level, i.e understanding why and how types/namespaces/assemblies, depend on each other. While tools like Resharper help dealing with the micro-level, tools like NDepend help dealing with the macro-level.
The key is that if you need to know what implementation resides behind an abstraction, you are in trouble: not only there might be multiple different implementations
but also, some of these implementations might be created or refactored in the future. Of course, we cheat sometime by using the debugger to see at run-time which implementation(s) is really used behind an abstraction. But compared to the show me static usage of this code element action, using the debugger is more the exception than the rule, precisely, because the intention of the developer that created the abstraction is to prevent the need for knowing implementation details.
The need for Loose-Coupling vs. the need for Big Picture
Let’s now conclude on the initial problem: NDepend should be able to inject Spring.NET dynamic dependencies into its static dependencies model of the code base. One worked hard to achieve loose-coupling between some components through the advanced concept of IoC. Now one gets all the benefit of abstraction. One is able to understand and extend features of smaller components without bothering much with other components implementations. While it makes sense to get the dynamic big picture of how loose-coupled components depend on each other at run-time (for some configurations), this NDepend added-value seems smaller than being able to focus on the static structures of components