There are bunch of tools that helps c#-developers write code better, more performant and with a better quality. NDepend is one of them. Here is my experience of using this tool.
Firstly, compare NDepend with other products. We know about Resharper or CodeRush, but they are mostly productivity tools - helps us to write code faster, helps with refactoring and also with small code problems. NDepend seems to be on the other side. It’s useful for whole-solution analyzes.
NDepend is built for continuous usage when we can watch the result of refactoring and improving. But checking your solution time-to-time gives nice results too.
Let’s check some solution and see what interesting we can find.
Dashboard
Starting from the dashboard we can go to critical rule violations:
As for me, not all of them are critical, but fortunately we can suppress any issue and exclude it from analysis.
Look closer
For example we select Attribute class name should be suffixed with 'Attribute' rule violation:
We see a short description, a way to fix and list of types. Common info, and all we need.
As for me, there is an interesting column - Debt
. NDepend tells we the estimated time to fix the issue. We can use this metric for a rough estimate of the time refactoring time.
Find total debt of our project, sort types by debt and here is most interesting places:
Let's dive deeper
Remember about the Attribute issue? Don’t you think that 5 minutes for refactoring is too much? I can fix it in a minute or less! All NDepend issues are customizable through the code (CQLinq):
// <Name>Attribute class name should be suffixed with 'Attribute'</Name>
// <Id>ND2005:AttributeClassNameShouldBeSuffixedWithAttribute</Id>
warnif count > 0 from t in Application.Types where
t.IsAttributeClass &&
!t.NameLike (@"Attribute$")
select new {
t,
Debt = 5.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Description>
// This rule warns about *attribute classes* whose names are not
// suffixed with **Attribute**. It is a common practice in the .NET
// world to suffix attribute classes names with **Attribute**.
//</Description>
//<HowToFix>
// Suffix the names of matched attribute classes with **Attribute**.
//</HowToFix>
We can change debt time and severity:
warnif count > 0 from t in Application.Types where
t.IsAttributeClass &&
!t.NameLike (@"Attribute$")
select new {
t,
Debt = 0.5.ToMinutes().ToDebt(),
Severity = Severity.Low
}
Also, it's easy to add our custom calculated columns. For example, define an Assigner
column and distribute issues between all developers of your team:
warnif count > 0 from t in Application.Types where
t.IsAttributeClass &&
!t.NameLike (@"Attribute$")
select new {
t,
Debt = 0.5.ToMinutes().ToDebt(),
Severity = Severity.Low,
Assigner = "Mark",
Sprint = 10
}
I think this is an interesting way to rule the technical debt of our project using more complex expressions.
Let's look around
We continue to explore other issues of the project.
Classes that are candidate to be turned into structures
Very interesting issue. If we look at matched types for this issue, we find classes with 2 or 3 fields:
public sealed class SyncResult
{
public SyncResult(DateTime? beginDate, int offset, int count)
{
this.BeginDate = beginDate;
this.Offset = offset;
this.Count = count;
}
public DateTime? BeginDate { get; }
public int Offset { get; }
public int Count { get; }
}
No reason to keep SyncResult
as a class, we can turn it to struct
to reduce memory traffic and GC.
Avoid various capitalizations for method name
NDepend found methods with the same meaning but different ways of writing. Indeed, it’s better when all similar methods are written the same way.
Potentially Dead Methods
As many other analyzers, NDepend can’t understand that our project uses DI-container and marks class constructors as dead methods. There is no silver-bullet solution for this situation, but I believe we can change the CQLinq code so it can handle the container we use.
Avoid types with poor cohesion
Interesting metrics tell we that our class is just an utility class with incoherent methods.
Let’s look at the issue source code:
warnif count > 0 from t in JustMyCode.Types where
t.LCOM > 0.8 &&
t.NbFields > 10 &&
t.NbMethods >10
let poorCohesionScore = 1/(1.01 - t.LCOM)
orderby poorCohesionScore descending
select new {
t,
t.LCOM,
t.NbMethods,
t.NbFields,
poorCohesionScore,
Debt = poorCohesionScore.Linear(5, 5, 50, 4*60).ToMinutes().ToDebt(),
// The annual interest varies linearly from interest for severity Medium for low poorCohesionScore
// to 4 times interest for severity High for high poorCohesionScore
AnnualInterest = poorCohesionScore.Linear(5, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes,
50, 4*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest()
}
Worthy of attention. Many methods, many fields and one method usually use one field.
API Breaking Changes: Methods
Ok, we refactored our project, fixed many issues. How can we be sure that everything is ok? Tests, of course! We need to build the solution and run tests. But NDepend has one option for us: checking public api changes. During refactoring we removed a method that seemed unused, but our client did not think so!
Really potential blocker on production.
Opinion
NDepend is a very flexible tool. We can choose depth of use - from time-to-time checking to integration with CI and building reports everytime. Powerful support of CQLinq allows we to customize existing rules or create your own project-specific or team-specific. NDepend does not compete with Resharper nor replace it- these tools are for different purposes.
I have some ideas for rules that NDepend can check, so I’ll try to implement them.