A brief look at code analysis with NDepend
If you're a developer, you're probably familiar with various tenets of your craft, such as "naming things is hard" and "every non trivial program has at least one bug". The latter example is one of the reasons why there are ever increasing amounts of tools designed to reduce the number of bugs in an application, from testing, to performance profiling, to code analysis.
In this article, I'm going to briefly take a look NDepend, a code analysis tool for Visual Studio. This is the point where I'd like to quote the summary of the product from the NDepend website, but there's no simple description - which sums up NDepend pretty well actually. This is a complicated product offering a lot of features.
So when I say "a brief look", that's exactly what I mean. When I've had a chance to explore the functionality fully I hope I'll have enough knowledge and material to expand upon this initial post.
Disclaimer: I received a professional license for NDepend on the condition I would write about my experiences.
What is NDepend and what can it do for me?
Simply put, NDepend will analyse your code and spit out a report full of metrics, and violations against a large database of rules. These might be the mundane (a method has too many lines) to the more serious (your method is so complicated you will never remember how it works in 6 months time).
This really doesn't even begin to cover it though, as it can do so much more, from dependency graphs to trend analysis. One of the interesting things about NDepend is it saves the results of each analysis you do, allowing you to see if metrics such as test coverage are improving (good) or critical violations increased (not so good!).
A sample project
For this article, I'm going to be using the Dithering project I created in previous blog posts to test some of the functionality of NDepend. I choose this because the project was fresh in my mind as I've been heavily working on it the last few weeks, and because it was small enough that I assumed NDepend wouldn't find much amiss. Here's another tenet - assumptions are the mother of all <censored>.
You can use NDepend one of two ways, either via a stand alone application, or via a Visual Studio extension. For this article, I'm going to be using Visual Studio, but you should be able to do everything in the stand alone tool as well. There's also a CLI tool which I assume is for build integration but I haven't looked at it yet.
That first analysis
If this is the first time using NDepend, you need to attach an NDepend project to your solution.
- Open the NDepend menu and select the Attach New NDepend Project to Current VS Solution menu item
- The dialog that is displayed will list all the projects in your solution, if there any you don't want to include in the analysis, just right click them and choose the appropriate option
- Click the Analyze button to generate the project
- Once the project has been created, a welcome dialog will be displayed. Click the View NDepend Dashboard button to continue
This will open the dashboard, looking something similar to the below.
A HTML report will also be generated and opened in your default browser, providing a helpful synopsis of the analysis.
At this point, all the charts you can see are going to be non-existent as you have to rerun the analysis at future times in order to get additional data points for plotting.
The main information I'm interested in right now is contained in the Code Rules block. And it doesn't make me happy to read it:
- 4 Critical Rules Violated for a total of 9 Critical Violations
- 37 Rules Violated for a total of 215 Violations
Wow, that's a lot of violations for such a small project! Lets take a look at these in detail.
Viewing Rules
Clicking the blue hyper-links in the Dashboard will automatically open a new view to drill down into the details of the analysis. On clicking the Critical Rules Violated link, I'm presented with the following
Clicking one of the rules in the list displays the code of the rule and the execution results.
Here we can see the the violation is triggered if any method has
more than eight parameters. In the dithering example project,
there is a class I that I used to generate the diagrams used on
the blog posts, and the DrawString
method of this helper class
has 10 parameters, thus falling foul of the rule. Great start!
The next rule on the list is a bit more complicated, but
essentially it's trying to detect dead code. In a non-library
project, this should be fairly straight forward and true to form
it has detected that the ArticleDiagrams
class and its methods
are dead code.
This is actually a very useful rule if your coding standards insist that all dead code is removed. How useful depends on your code coverage, if you also have a 100% rule then you should already found and removed such code.
So far so good. Lets look at the final critical rule failure.
When rules go wrong
The last critical rule violation is Don't call your method
Dispose. I imagine this makes a lot of sense, if your class
doesn't implement IDisposable
, then having a method named
Dispose
is going to be confusing at best.
Interesting. So it somehow thinks that the MainForm
and
AboutDialog
classes - both of which inherit from Form
-
shouldn't have methods named Dispose
. Well, somewhere in its
inheritance chain Form
does implement IDisposable
so this
violation is completely wrong.
As a test, I added IDisposable
to the signature of
AboutDialog
and re-ran the NDepend analysis. It promptly
decided that the Dispose
method in that class was now fine. Of
course, now Resharper is complaining Base interface
'IDisposable' is redundant because
Cyotek.DitheringTest.AboutDialog inherits 'Form'. Sorry
NDepend, you're definitely wrong in this instance.
At this point, I excluded the
ArticleDiagrams
class from the solution and reran the analysis, removing some the violations that were valid, but not really appropriate as it was dead code.
More violations
So far, I've looked at 4 failed rules. 3 I'm happy to accept, and if this were production code I'd be getting rid of the dead code and resolving all three. The fourth violation is flat out wrong and I'm ignoring it for now.
However, there were lots of other (non-critical) violations, so I'll have a look at those now. The Queries and Rules Explorer window opened earlier has a drop down list which I can use to filter the results, so now I choose 31 Rules Violated to look at the other warnings.
There's plenty of other violations listed. I'll outline a tiny handful of them below.
Override equals and operator equals on value types / Structures should be immutable
This pair of failures is caused by the custom ArgbColor
struct
and is the simplest structure to handle a 32bit colour.
Actually, this struct is being called out for a few rules all of
which I agree with. If this were production code, I'd be
following a lot of the recommendations it makes (in fact, in the
"real" version of this class in my production libraries I do
follow most of them - a key exception being my structs are still
mutable).
Static fields should be prefixed with a 's_' / Instance fields should be prefixed with a 'm_'
These rules vie between I disagree with them, and NDepend shouldn't be picking them up. In the first place, I disagree with the rule - I simply use an underscore prefix and leave it at that.
However, NDepend is also picking up all of the control names in
my forms. I seriously doubt any developer is going to use m_
in front of their control names and so I don't think NDepend
should be looking at these - I consider them "designer" code of
sorts and should be excluded. There's a few more rules being
triggered by controls, and I think it's looking messier than it
should.
I can edit the rule to use my own conversion of the plain underscore, but I can't do much about NDepend picking up WinForm control names.
Non-static classes should be instantiated or turned to static
This is an interesting one. It's basically being triggered by
the LineDesigner
class, a designer for the Line
control to
allow only horizontal resizing. Control designers can't be
static and so this rule doesn't apply. It is referenced by the
Designer
attribute of the Line
class so we probably just
need to edit the rule to support it.
And more
There's quite a few rule violations so I won't cover them all.
It's an interesting mix of rules I would find useful, and rules
subject to interpretation (an example is if I have an internal
class I still mark its members as public
, NDepend think this
is incorrect).
But, NDepend doesn't force you to accept its view. You can simply turn off any rule that you don't want influencing the analysis and it will be fully disabled, including the dashboard updating itself in real-time.
Assuming you have analysed the project multiple times, you can turn on recent violations only, thus hiding any previous violations. You may find this very useful if you are working from a legacy code base!
Editing Rules
With that said, there are other options if a rule doesn't quite fit the bill. NDepend uses LINQ with a set of custom extensions (Code Query over LINQ (CQLinq)) as the base of its rules. So you can put your programmer hat on and modify these rules to suit your needs.
As a concrete example, I'm going to look at the Instances size
shouldn't be too big rule. This has flagged the Line
control
as being too big, something I found curious as the control is a
simple affair that just draws a 3D line. When I look at the
details for the violation it mentions 6 fields. But the control
only has 3. Or does it?
The query results don't include the names of the fields, so I'm going to adjust the code of the rule to include them. This is a really nice aspect of NDepend - as I type in the code pane, it continually tries to compile and run the rule, including syntax highlighting of errors, and intellisense.
I added the , names = ...
condition to the code as follows,
which allowed me to influence the output to include an extra
column
warnif count > 0 from t in JustMyCode.Types where
t.SizeOfInst > 64
orderby t.SizeOfInst descending
select new { t, t.SizeOfInst, t.InstanceFields, names = string.Join(", ", t.InstanceFields.Select(f => f.Name)) }
The results of the modified rule show that there are 3 variables which are backing fields for properties, and then 3 events. Is an event a field? I don't think so, an event is an event. But NDepend thinks it is a field. Regardless though, by editing the rule I was easily able to add additional output from the rule, and although not demonstrated here I've also used some of the built in filtering options to exclude results from being returned.
The ability to write your own rules could potentially be very useful with many possibilities.
Interpretation is king
In a way, I'm glad that NDepend doesn't have the ability to
automatically fix violations the way some other tools do. I ran
NDepend on my CircularBuffer library, and one of the
suggestions was to change the visibility of the class from
public
to internal
. Making the single class of a library
project inaccessible to consumers isn't the best of ideas!
I think what I'm leading to here, is use common sense with the violations, do not just blindly accept anything it says as gospel.
Viewing Dependencies
Any application is going to have dependencies, and depending on how tight your coupling is, this could be an evil nightmare. You can display a visual hierarchy of the dependencies of your project via a handy Dependency Diagram - below is the one for the dithering project. Quite small as there are few references, The thicker the arrow, the more dependencies from the destination assembly you're using.
In the case where the diagram is so big as to become meaningless, you can also view a Dependency Matrix - this lets you plot assemblies against each other and see the usages.
Clicking one of the nodes in the matrix will then open a simplified Dependency Graph, making it a little easier to browse than a huge spaghetti diagram.
Code Metrics
Many years ago, I used a small tool that displayed the size of the different directories on my computer in a treemap to see which folders took up the most space. I haven't used that tool for years (I don't need a colour graph to know my Steam directory is huge!) but I do find that sort of display to be oddly compelling.
NDepend makes use of a tree map to display code metrics - the size of the squares defaults to the code size (useful for seeing huge methods, although again, as the screenshot below indicates, I really wish NDepend would exclude designer code). You can also control the colour of the square via another metric - the default being complexity, so the greener the square the easier the code should be to maintain.
I couldn't see how to access this from Visual Studio, but the HTML report also includes an Abstractness versus Instability diagram which "helps to detect which assemblies are potentially painful to maintain (i.e concrete and stable) and which assemblies are potentially useless (i.e abstract and instable)". Meaning you should probably take note if anything appears in the red zone!
Updating the analysis
You can trigger a manual refresh of the analysis at any time, but also by default NDepend will perform one after each build, meaning you can always be up to date on the metrics of your project.
Show me a big project
So far I have looked at only a small demonstration project. However, as the ultimate test of my review, I decided to scan WebCopy. I was very curious to see how NDepend would handle that solution. NDepend scanned the code base quite nicely (despite an old version of one of my libraries getting detected and playing havoc)
As an indication of the size of the project, it reports that WebCopy has 60 thousand lines of code (translating to half a million IL instructions), 24 thousand lines of comments, and nearly 1800 types spread over 44 assemblies. A fair amount!
I had a quick look through the violations list, and noticed a
few oddities - there are lots of Forms
in these projects,
yet the Don't call your method Dispose violation that so
annoyed me earlier was only recorded 4 times. One of these was
actually valid (a manager class who's children were disposable),
while the others weren't. Still, there's a curious disparity in
the way NDepend is running these rules it seems.
I did find some violations indicating genuine problems (or potential problems) in the code through so at some point (sigh - there's a lot of them) I will have to take a closer look and go through them all in detail.
Just before I sign off, I shall show you the dependency diagram (maybe I need to try and make my code simpler!) and the complexity diagram.
That's all, folks
For a "brief" overview, this has been quite a long article - NDepend is such a big product, one article cannot possibly cover it all. Just take a look at their feature list!
Ideally I will try to cover more of NDepend in future articles, as I'm still exploring the feature set, so stay tuned.
Update History
- 2015-06-27 - First published
- 2020-11-21 - Updated formatting
Related articles you may be interested in
Leave a Comment
While we appreciate comments from our users, please follow our posting guidelines. Have you tried the Cyotek Forums for support from Cyotek and the community?