Understanding Code: Static vs Dynamic Dependencies

 There have been some NDepend users’ requests to make the tool able to cop with dynamically fabricated dependency/IoC framework such as Spring.NET.

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… :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
interface IFoo { void Method(); }
class Foo : IFoo { public void Method() { Console.WriteLine(hello); } }
 
// Interface
class FooUser1 {
void MethodUser(IFoo foo) { foo.Method(); }
}
 
// Generic
class FooUser2<T> where T : IFoo {
void MethodUser(T foo) { foo.Method(); }
}
 
// Delegate
internal delegate void MyDelegate();
class FooUser3 {
void MethodUser(MyDelegate fooMethod) { fooMethod(); }
}
view raw gistfile1.cs hosted with ❤ by GitHub

 

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().

1 2 3
void MethodA(IFoo foo) {
foo.MethodB();
}
view raw gistfile1.cs hosted with ❤ by GitHub

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.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// With abstraction
void MethodA(IFoo foo) {
foo.MethodB();
}
// Without abstraction
void MethodA(Foo foo) {
if() {
foo.MethodB_Impl1();
} else if() {
foo.MethodB_Impl2();
}
} else if() {
foo.MethodB_ImplN();
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

 

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
themselves.

 

 

 

This entry was posted in Code Dependency, code organization, code structure, coupling, Dependencies. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Frédéric DIDIER

    I not only read this article, I had experiences about IOC and static coding. then I agree with deductions and conclusions.

  • Kevin Laudrup (C# developer)

    Patrick, NDepend is a brilliant tool.
    I discovered it reading a new C# multicore programming book: “C# 2008 and 2005 threaded programming”, by Gastón Hillar, Packt Publishing – http://www.packtpub.com/beginners-guide-for-C-sharp-2008-and-2005-threaded-programming
    The book talks about NDepend as a great tool for analyzing several aspects of C# code realated to multithreading safety. Great value.
    The book is great too.
    We are working with medical imaging and the company decided to buy NDepend because I downloaded the demo and the developers team considered it great.
    When you develop highly threaded applications like the ones we are now working with, NDepend helps a lot to avoid great mistakes. My technical leader is amazed with the metrics offered and the CQL.
    NDepend is highly recommended to every serious C# developer.