I'd like give a big thanks to Patrick Smacchia for offering me a free license for NDepend, a code analysis tool. I've spent the past couple days experimenting, watching tutorial videos, reading his articles, and running the tool against my work project and also NHibernate, for which I am an occasional contributor. My opinion of NDepend is very high. As a metrics-junkie, this tool is my Columbian drug lord. I won't bother rehashing all the information already provided on the product website, but, as an example, you can do stuff like this...
WARN IF Count > 0 IN SELECT METHODS WHERE IsUsing "System.Web" AND IsUsing "System.Data.SqlClient"
And you will get a result set of any methods in your project that are doing web stuff and SQL stuff in the same method! COOL! Now you know which n00b programmers you need to go
slap mentor. In other words, you can define NDepend CQL queries to find methods that violate the Separation of Concerns principle. Another feature of NDepend that is very fascinating is the dependency matrix and how it visualizes dependency levelization. (Go read that article, it's ok, I'll wait...) I was very interested to see how well NHibernate levelizes its inner-namespace dependencies. Here's the results...
That big red box that nearly encompasses the entire grid indicates that all of those namespaces have a cyclical dependency. That kinda sucks. The black squares that are peppered about the grid indicate a cyclical dependency between that row and column. Looking closer, there are four namespaces in particular that heavily used (as indicated by a high density of blue and black squares):
- NHibernate (row 51)
- NHibernate.Engine (row 16)
- NHibernate.Type (row 37)
- NHibernate.Util (row 33).
If properly leveled, these namespaces should drop to the bottom of the grid. In order to achieve namespace levelization, refactoring effort would focus on the details of the black boxes on these rows. There are several difficulties in doing this:
- NHibernate is a public API. Making API changes to break dependencies will annoy current users when they upgrade to a new version with a changed API. This would have to be mitigated by making some changes transitional by leaving the offending members and marking them with the [Obsolete] attribute.
- NHibernate has tried to maintain a similar API to the Java version in order to be able to reuse documentation and make porting of features easier. Much of this problem is "sins of the father."
- Many of these refactorings are likely to be broad in scope. This will require extra coordination amongst the team to make sure refactoring doesn't conflict with feature development. There is also the chance of introducing bugs. Making sure the relevant code is being tested effectively would be important.
- The open-source nature of the application means there isn't necessarily a central architect to enforce these constraints. But we could beef up the build script to run NDepend with a defined set of queries to inform the developer if he is deviating from the intended design.
What is the next step? I'm not sure. It's likely that many of the cyclical dependencies may be easy to bust up with minimal effort. Is that effort even worth it if no work is being done in that part of the code? The areas of code that are modified most are probably the ones with the most coupling. I'm having a fantasy where some master tool could combine the data from NDepend (which files hurt the most) with the Subversion commit log (what files are changed most), the JIRA issue tracking data (what files need changed the most), and dotTrace profiling data of any defined NUnit tests (what files get used most) into some uber-report.
12-01-2007 5:00 PM