NDepend: first overview

Published on Tuesday, June 1, 2021

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

Dashboard

Starting from the dashboard we can go to critical rule violations:

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:

Attribute class name should be suffixed with 'Attribute'

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:

Debt types

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
}

Change issue

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

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!

API Breaking Changes: Methods

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.

Links

NDepend official site