Saturday, September 27, 2008

NDepend, or a Young .NET Programmer's Illustrated Primer


I have said before that I have no axe to grind against Microsoft, and I stand by that.  They set out long ago to have a computer on every desktop and Microsoft software running on every computer.  They have gone a long way towards achieving that goal, and in the process they have brought value to more people than I can count.  Since their inception they have produced operating systems, languages, and development tools that have allowed people without computer science backgrounds to learn how to program computers to do actual stuff. 

Now, granted, Microsoft has promoted development techniques that are much more in line with their own marketing plans than with sound design principles.  But I cannot think of a time during Microsoft's existence when information about sound design principles was unavailable, or when Microsoft has ever actively sought to squelch that information.  And I'm old.

I guess my point is this: Microsoft has produced some really nice tools, and it's up to me as a developer to use those tools wisely.  But it's also up to me to find other tools and techniques that help me to be more effective as a developer.  I feel a constant call to improve myself as a developer.  I also feel a call to watch old Twilight Zone episodes on YouTube.  But that first call is still there, and I respond to it too.  I certainly benefit from my efforts to learn and grow, and my team benefits from my efforts as well.  And ultimately, so does my company.

Many companies I have worked for have thought of their developers as commodities, particularly those who use Microsoft technologies.  Approaching hiring, training, and project management practices from a commodity perspective has lead to commodity behavior in many developers: one MCP is as good as another; it Microsoft didn't invent it, it can't be good; we're all "human resources" rather than "personnel". 

If you're in this situation, I have good news for you: you can take the red pill.  You don't have to wait for your employer to educate you about your chosen profession.  You can take the grass by the roots and educate yourself.  There are tons of resources out there for you.  One of those resources is a great little tool called NDepend.


A Real-World Example

The purpose of NDepend is to give you views into the actual structure of your software, regardless of how you intended to design it, that you would otherwise be unaware of.  By blindly following the herd rather than thinking about long term design consequences, we often end up with some silly, expensive situations.  NDepend can help us pinpoint the details of those situations so that we can address them.

A while back Chad Myers posted about the hazards of having WAAAAAAYYYYYY too many projects in a single solution file. I have wanted to write a post on NDepend for a while now.  Others have already written better overviews than I could produce.  And the NDepend tool itself ships with a whole host of how-to's in its documentation.  Instead, I want to write about how we use NDepend at my office to address problems like those Chad describes, and that will probably take more than one post.

At my office we definitely suffer from the malady Chad describes.  Our problem started innocently enough: years ago, we thought that we should package our API so granularly that we could deploy different pieces of it in an easy and atomic way.  Well, it has proven not to be easy, but it is certainly atomic.  And not in the good way.  As we have added to our codebase over the years, we kept following the idea of many granular assemblies, but we somehow lost sight of the goal of easy deployment, and we ignored our need for maintainability.  We now have a single solution file for assemblies meant to be shared among multiple applications that has nearly 150 projects in it.  NEARLY 150!!!  How do you even start to address a situation like that?  Step 1 is admitting you have a problem.  So first, we'll consolidate our assemblies, and manage our code with namespaces and project folders.  But that's the subject of another post.

Step 2 is finding dead code and eliminating it, and that's the subject of this post.  My problem here is that I'm under an NDA, so I can't show you details of the real situation, although I would love to.  Instead here's a very trivial example to illuminate a couple of concepts: download.  At work, I've got a (HUGE!) set of shared assemblies, and a couple of apps that consume those assemblies.  So my example contains code meant to represent a situation like that: the CouplingExample assembly is the stand-in for the shared assemblies, and the CouplingExample.ConsoleApplication and CouplingExample.UnitTests assemblies represent the apps that depend on our shared assemblies.

NDepend ships with a SQL-like scripting language called Code Query Language (CQL).  CQL allows you to write custom queries about the assemblies you're analyzing based on several different kinds of code metrics.  NDepend ships with a bunch of pre-built CQL queries to give you a good starting place for cracking into your code.  Since I'm trying to kill dead code, the metric I'm particularly interested in is afferent coupling, or actually the lack of it.  For CouplingExample, I want to know which methods in the CouplingExample assembly have no methods that depend directly on them.  I'm interested in non-public methods - it's a pretty safe bet that any non-publicly accessible method in my assembly that has nothing depending on it is dead code, and can therefore be eliminated.  But I'm also interested in public methods with an afferent coupling of zero.  Since I know all the applications that consume my shared assemblies, I can include those app assemblies in the same NDepend project.  I can narrow down the scope of my CQL query just by including an ASSEMBLIES list.  I swiped NDepend's canned "Potentially unused methods" query, and bent it to my own bidding like so:

   1: // <Name>Potentially unused shared assembly methods</Name>
   3:  MethodCa == 0 AND            // Ca=0 -> No Afferent Coupling -> The method is not used in the context of this application.
   4:  !IsEntryPoint AND            // Main() method is not used by-design.
   5:  !IsExplicitInterfaceImpl AND // The IL code never explicitely calls explicit interface methods implementation.
   6:  !IsClassConstructor AND      // The IL code never explicitely calls class constructors.
   7:  !IsFinalizer                 // The IL code never explicitely calls finalizers.
My NDepend project contains the shared assembly and the application assemblies that consume it.  But this query only looks at methods in the CouplingExample assembly.  When I run the query, NDepend paints a pretty picture of my code for me.  Here's a context map showing all the assemblies I analyzed, and highlighting the methods in the CouplingExample assembly that have no other methods depending on them, either internally inside CouplingExample, or externally from the UnitTests and ConsoleApplication assemblies:
You can easily see which methods have no other methods depending on them, the ones in bright blue (and conveniently named "Public_no_afferent_coupling()" and "Private_no_afferent_coupling()"). Back in real life, I used the query above with all 140+ assembly names in it pointed at my shared assemblies and the consuming app assemblies.  Here's a scrubbed version of the resulting context map:
Look at that!  Tangled up in blue!  Literally THOUSANDS of lines of code we can just drop.  What a great feeling.  I ran this picture by people in our group who, unlike me, actually have authority to buy stuff.  Almost immediately, they put a line item in the budget for 10 NDepend licenses.  This thing sells itself.  And I feel confident that once more of our developers have begun to use it, we'll end up buying a license for everyone in the group.
This one little query for dead code is just the very top snowflake on the tip of the iceberg.  NDepend gives you a myriad of insights into how your code actually works, empowering you to make it better, and educating you on how to create better designs in the future.  A really cool feature of NDepend is its integration with Visual Studio: if you include the .pdb files in the NDepend project, you can just double-click on a method to bring it up in Visual Studio.  I'm a ReSharper nut - I think Patrick Smacchia hit the nail on the head when he said that what ReSharper does for you on a micro-level, NDepend does for you on a macro-level.  But the code metric analysis NDepend offers also puts it in a different realm than a refactoring tool like ReSharper.  I think these two tools make a very powerful team together.  Next time, I'll talk a bit about how we are beginning to use ReSharper to address situations like this one that NDepend illuminated for us.
We are now (finally!) giving attention to cleaning up messy old code.  As we move along, I hope to write about other examples of how we change our code after examining it with NDepend, so stay tuned.  This kind of stuff isn't only for the uber-geeks, it's for the day-to-day programmer who wants to improve skills, improve code, and deliver better products.
Share this post :
Technorati Tags: ,,,,