I have used NDepend on and off for the past couple of years. It has never been an everyday thing, but whenever I have an itch to find out just how convoluted my code dependency graph has become, NDepend has been there for me.
Full disclosure: Patrick Smacchia sent me a free professional license of NDepend to play with some time ago. To be honest, I didn't really need the license because I was already using the OSS edition of NDepend at the time. I just think this is a cool tool. :-)
Download and Installation
Installing NDepend is pretty easy. Just visit the NDepend download page, type in your email address for the OSS or trial version or your license code for the progressional addition, then download the NDepend ZIP file. Extract the ZIP someplace useful (like C:\Program Files) then pin a shortcut to VisualNDepend.exe to your Quick Launch bar.
Now launch VisualNDepend. To enable Visual Studio and Reflector integration click on the button in the UI. A couple more clicks and you're done.
I have to say, unboxing NDepend is very slick! There are a bunch of links to little screencast demos to explain how stuff works. I have to admit I have tool envy here. I wish my tools could help the user along like this.
That said, I do wish NDepend (and Reflector also) had an MSI installer flavor. It's harder to explain to others how to extract ZIPs and create shortcuts than to just ask them to run through a standard installer process.
Dissecting Gallio with NDepend
After installing the NDepend add-in for Visual Studio, I opened up the Gallio solution. There's a lot of stuff in here, so for this exploration I'm going to start by looking at the dependencies within Gallio.dll.
It's been a few months since I looked at this so I'm cringing here... What will I discover?
Up pops an instance of Visual NDepend with this dialog listing all of the assemblies in my solution. Pretty slick. I don't have to create a new NDepend project from scratch. On the other hand, I'm only interested in Gallio.dll and I did right-click specifically on that project in Visual Studio so I will remove everything else.
The next thing that pops up is an NDepend report. It shows up in my default web browser (FireFox). I think it might be cooler if it appeared inside of the Visual NDepend application itself using an embedded web browser (eg. System.Windows.Forms.WebBrowser) but this report is pretty useful anyways.
Bunch of metrics regarding both the source code and the compiled binaries. Looks like Gallio.dll consists 66% of comments. Heh. There's all of the XML documentation!
One thing to keep in mind when reading the report is that not all of the information is equally useful. NDepend computes lots of different metrics to help you find hot spots. Of course, if there's nothing really wrong then what it finds might not be very interesting. You need to read the report intelligently.
Top 10 Methods to Refactor?
For example, here are the top 10 methods in Gallio.dll that I should refactor, according to NDepend.
Whoa, so the top 2 are compiler generated and most of the others are pretty simple methods. Based on the criteria used (see CQL below).
The main thing I'm hitting up against is the IL nesting depth and number of variables. None of these methods have a particularly troubling cyclomatic complexity except for CreateFilteredClosure which has a CC of 20 but can't really be improved much more given what it does.
So in sum, I guess I'm doing ok! Cool. However I bet if I threw this at some production code of my employer, we'd find some interesting things to talk about.
Let's keep digging.
Finding Large Methods
Out of the box, the NDepend report shows a summary of large methods. This is worth paying close attention to.
Here are the 10 biggest methods in Gallio.dll.
I'm amused that FindMiddleSnake made the list. That is one monster method! Unfortunately I can't really do much to improve it because of the nature of the algorithm. Calculating diffs is hard stuff! The topological sort is in a similar category.
However, the PluginCatalog.ApplyTo and DefaultRuntime.VerifyInstallation methods are long for no good reason. So with a little help from ReSharper's Extract Method refactoring here's what ApplyTo now looks like:
Overlap with FxCop
Some of the metrics that NDepend computes out of the box produce similar results to those that could be obtained using FxCop. This is rather interesting since NDepend's CQL language actually has the potential to be much more expressive and configurable than the FxCop verifications, if you like. However there can be some quirks.
Here are some methods and constructors that appear in abstract classes that were marked "public" but that NDepend thinks could be "protected".
The NativeCodeElementWrapper, NativeMemberWrapper and NativeFunctionWrapper constructors I understand (fixed!), the others not so much. TestTypePatternAttribute is a public attribute class whose constructor really does need to be public because it is public API although NDepend doesn't know that. The NativeCodeElementWrapper.Target property is only used internally in Gallio.dll so it could be made protected or internal if desired.
What it comes down to is that you still have to use your head when you interpret the data. It's worth the effort though!
Finding Inadvertent Coupling
Of all of the many metrics and views provided by NDepend, this one is by far my favorite. It shows the afferent coupling (incoming dependencies) in blue, efferent coupling (outgoing dependencies) in green, and mutual coupling (both incoming and outgoing dependencies) in black. The following chart is displaying coupling among namespaces.
Oh no! There's some black in there. The black means that some code in one namespace is using code in another namespace which also happens to use code in the former.
Namespaces are frequently used to separate subsystems from one another. Coupling among namespaces can indicate one of the following problems:
- One or more classes are in the wrong namespace and should be moved.
- One or more classes have dependencies on the wrong things and should be refactored to use dependency injection or use inversion of control to access foreign services.
- The namespaces are not very cohesive and the subsystems they represent should be restructured to be more tightly encapsulated or have fewer responsibilities.
- There are common services partially defined in both namespaces that should be extracted and moved elsewhere.
To take a deeper look I just double-click on one of the black squares to drill down.
Ah ha! It looks like the Gallio.Model.Diagnostics.ExceptionData class is being used by several of the classes in the Gallio.Runtime.Logging namespace. This is bad.
The Runtime subsystem is the lowest tier of Gallio and provides the core foundational services needed to get Gallio up and running. The Model subsystem is one of the tiers above the Runtime and its job is to define the test object model. What we have here is a cyclic dependency that crosses tiers.
In a tiered architecture, dependencies should always flow from one tier down to the ones below it and never the other way around. If you let dependencies flow both ways then you may quickly find yourself enmeshed in a bowl of spaghetti!
It looks like in order to fix this problem, I'm going to have to rearrange a few of these dependencies. The upside is that the structure of the system will be cleaner when I am done.
CQL: A Recipe for Analysis
NDepend provides a code query language called CQL which enables all sorts of fancy analysis. In addition to answering various questions interactively, you can incorporate NDepend CQL constraints into your build process such that the build will fail if a constraint is violated. By using CQL as part of a continuous integration process you can validate system design conventions early and often. For example, you can add a CQL constraint to detect when cyclic dependencies are accidentally created across tiers.
To be honest, I have not delved into CQL very much myself. Using it as part of the build process looks like it could be a very useful tool for managing large teams of developers. (I just need to find more time to do architecture work...)
NDepend is a good tool that repays careful analysis. It won't fix your system design defects for you but it will help you find the trouble spots.
As a result of writing this post, I fixed a few big methods and performed a major refactoring of Gallio's primary namespaces guided mainly by NDepend's dependency matrix view. This is not the first time I have used NDepend to improve Gallio. Here is how that dependency matrix is looking now:
All in all, I feel much better now!