7

NDepend: A Static Analyser for .NET and .NET Core

 3 years ago
source link: https://codingadventures.me/2018/09/10/ndepend-a-static-analyser-for-net-and-net-core/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

A Static Analyser for .NET and .NET Core ~ Coding AdventuresSkip to main content

NDepend is static analyser for .NET and .NET Core. Recently I was contacted by its creator, Patrick Smacchia, who kindly offered a license in support of my OSS project LINQBridgeVs.

Overview

NDepend is a tool mainly targeted for software architects who want to have a deep insight into their projects. NDepend gathers data from a code base and includes code quality metrics, test coverage statistics, assembly dependencies, evolution and changes, state mutability, usage of tier code, tech debt estimation and more. Another interesting feature is the ability to write custom rules using a domain specific language called CQLinq, which is based on LINQ, C# and the NDepend API.

NDepend comes with tons of features and it feels overwhelming at first. It has a quite steep learning curve before getting familiar with it. However there is lot of documentation, both on the official website and also in App which helps a lot.

Licensing System

There are two types of licenses: per seat (€ 399.00) for developers that use the UI (or the stand-alone app), and per machine ( 799.00)for servers that integrate NDepend into their build process. This price model is probably not a problem for companies but it might be a for individual developers. Although it is not advertised on the website there is official support for MVP and open source projects. Microsoft MVP are eligible for a free personnal license. Get in touch at this email address [email protected].

Installation

NDepend does not have an installer, it comes in a zip file, but its setup and first activation are pretty straightforward. Check out the introduction video on how to install it and get started.

zipcontent.png?w=788

NDepend can be run either as a stand alone application, using the executable VisualNDepend or within Visual Studio, by installing the extension (support for 2012 through 2017). There are also two console applications:

  • NDepend.Console is the command-line version of NDepend and can be used to automate the report generation using a CI server. NDepend has built-in integration with TeamCity, FinalBuilder, TFS and CruiseControl.NET
  • NDepend.PowerTools shows how to make the best out of the NDepend API syntax. It contains a lot of examples for code rules checker, code diff reporter, handy development tools, build process checker, etc.

First Impressions

I’ve been using NDepend for quite a while now and honestly it took me some time to get my head around the UI and the tons of features this tool is shipped with. The amount of information is at first overwhelming, and in my opinion the UI at times contains a lot of information. However after a few hours of usage I felt I was more confident to use it and it became easier to search and find what I needed.

The Visual Studio integration is handy, as you don’t have to leave Visual Studio. New reports are generated after successful build and it’s possible to compare metrics across different analysis. Having a second monitor is advised though as it makes it tidier and easier to keep the NDepend window on one monitor and avoid annoying switching between windows.

One feature that caught my eye is the technical debt estimation (TB). The TB metaphor was coined in 1992 by Ward Cunningam, a design pattern and extreme programming pioneer. Think of TB as a financial debt. It must be paid at some stage and it accumulates with other debts, generating interests and prolonging the time needed to pay it back.

I’m sure at some stage in every software developer’s carrier there grows an urge to refactor the code and reality is there is never time for it. Unfortunately strict deadlines or difficult targets lash back leading to hacks, quick fixes and poor design decisions in favour of quicker releases to satisfy customers. Technical debt is often underestimated as it is seen as a cost that doesn’t produce an immediate benefit. “It’s just code!” as they might say.

The technical debt estimation in NDepend produces an estimate, expressed in man-hour to fix code issues found. Settings can also be changed for the average cost of man-hour of development, the currency, the estimated number of man days to develop 1000 logical lines of code, scale debt rating percentage etc in the debt settings. The ability to calculate the cost of technical debt and monitor it over time is a great advantage especially to communicate it to non technical people and justify how a refactor or a new re-design could save a lot of time and money over time.

DebtSettings
Smart Technical Debt Estimation – Configuration

Under the hood NDepend uses code rules, i.e. code metrics, to calculate debt and are grouped by: application, assemblies, namespaces, types, and methods. When a rule is violated then there could be a code smell, poor object oriented design, immutability and/or threading issues, naming convention consistency across types, methods and namespaces, source file organisation and so on.

NDepend and LINQBridgeVs

LINQBridgeVs is a Visual Studio extension that transmits debugging variables from VS to LINQPad. I’ve been working on it for quite some time now but the code base is not huge. There are over 1700 lines of code over 6 assemblies. Let’s have a look at the NDepend dashboard:

Dashboard
NDepend Dashboard – I got a B!

The dashboard page has several panels on top and trend graphs on the bottom or on the side if your monitor has enough resolution. Panels contain info about code metrics, technical debt estimation, test code coverage, method cyclomatic complexity, quality gates and violated rules. NDepend also supports trend metrics, which is the ability to monitor the evolution of the project by comparing metrics across different analysis. Trend graphs can be found below the metric panels:

TrendGraphs
Trend Graphs

Every number in the dashboard is interactive. Clicking on any of them generates a reports, which is essentially a specific CQLinq query run in a temporary preview tab (like the temporary preview documents in Visual Studio), which can be found in the “Rule Editor Window”, i.e. the NDepend query editor.

ViolatedRules
Rules Violated Tab in the Rule Editor Window

Tabs are divided in two sections: the description on top and the result of the query below in a data grid form, which is highly interactive. It’s possible to switch between description and source code view to personalize the corresponding query:

ViolatedRules_CQLINQ
Rules Violated – CQLinq Source

The result is shown in the grid at the bottom. For each rule there are the number of issues:

  • Debt: the estimated effort to fix the issues.
  • Annual interest: The amount of time needed required to fix the issue if left unfixed.
  • Breaking point: it represents the time-point from now to when the estimated cost-to-fix the issue will reach the estimated cost to leave the issue unfixed. It is calculated by dividing the estimated debt by the annual interest. The breaking point is inversely proportional to the Return On Investment of fixing an issue. Thus the lower the breaking point, the higher the ROI.

Based on this assumption I modified the query above for violated rules to filter only those that have a breaking point within 1 and 365 days:

// Rules violated
from r in Rules
where r.IsViolated() && r.BreakingPoint() <= TimeSpan.Zero && r.BreakingPoint() < TimeSpan.FromDays(365)
orderby r.BreakingPoint().TotalMinutes ascending
select new
Issues = r.Issues(),
Debt = r.Debt(),
AnnualInterest = r.AnnualInterest(),
BreakingPoint = r.BreakingPoint(),
Category = r.Category
// Rules violated
from r in Rules
where r.IsViolated() && r.BreakingPoint() <= TimeSpan.Zero && r.BreakingPoint() < TimeSpan.FromDays(365)
orderby r.BreakingPoint().TotalMinutes ascending
select new
{
 r,
 Issues = r.Issues(),
 Debt = r.Debt(),
 AnnualInterest = r.AnnualInterest(),
 BreakingPoint = r.BreakingPoint(),
 Category = r.Category
}

The grid in the image below shows 19 rules that need to be addressed, two of which are flagged as critical. In NDepend critical rules represent high priority rules that must never be violated. They are marked with a red triangle over the exclamation mark.

ViolatedRulesResult
Rule Violated in LINQBridgeVs

Hovering the mouse over a rule or clicking on it opens a floating panel that contains a thorough description of the issue and often link to a discussion page on the topic. Double clicking on a rule opens instead its corresponding CQLinq source.

InfoRuleViolated

The two violated critical rules are: “Avoid having different types with the same name”, and “Avoid non-readonly static fields”.

I only agree with the first rule partially. I wouldn’t want to have a huge number of different types with the same name as that in fact could easily generates confusion (and probably is a sign of bad design). However if there are only a few types with the same name I believe it’s not going to be an issue. It is not uncommon to have shared names across types in different domains. For instance, in the .NET Framework, the class Timer is defined either in the System.Windows.Forms.Timer and also in System.Threading.Timer namespace. The former is a timer suitable for Windows Form environment and it runs on the main thread as it is often used to modify properties on a form. The latter instead is a thread timer and provides a mechanism for executing a method on a thread pool’s thread at specified intervals. The two classes, despite the same name, do similar things in a very different way. One could argue that the two timers could be called with different names, e.g. FormTimer and ThreadTimer but the disambiguation is better managed at namespace level.

Conditions for a rule to be violated can be changed though in its corresponding CQLinq source. For example I changed the minimum number of types before the rule is considered violated and reported:

DifferentTypesSameNAme_2
Increasing the minimum number of types allowed with same name.

The second critical rule “Avoid non-readonly static fields” is a warning for an OOP design flaw. Static fields are states shared with every instance of the class where they are declared. If they are mutable then extreme care must be taken to initialise and to reset them correctly.  In a multi-threaded environment mutable static fields are not thread-safe. Race conditions cannot be avoided, and in this scenario bugs could be very hard to trace and even to replicate. It is important to make static fields immutable and private. The rule however suggests to declare them as readonly but that by itself doesn’t enforce complete immutability. Readonly guarantees that the instance can only be assigned once during the construction and it cannot be changed during its lifetime in the AppDomain. For instance, in a readonly List or a Dictionary, values can still be added or removed. Clearly it’s up to us to “protect” those fields from being modified by enforcing immutability. To read more on the topic a good explanation can be found here.

In my specific case I declared a public static string in a class (I know it’s horrible) called RavenWrapper. Such class has been designed to be a Singleton that uses lazy instantiation.  I wrote this class as a separation layer for the Raven SDK, a library that sends errors to Sentry (an open source error tracking system). More on the singleton later.

RavenWrapper
6 methods use the public static field VisualStudioVersion directly.

The public static string represents the version of the Visual Studio instance where LINQBridgeVs is running on. For “convenience” (truth is laziness) I set this field once outside the class so that I don’t have to pass the vs version all the time as a method parameter. The choice of a singleton class doesn’t help either. I can’t overload the constructor to do the most obvious thing: make the field private, non-static and read-only and let the constructor initialise it.

In fairness this class has too many design flaws and issues, so I decided to isolate it in a query and see what NDepend thinks about it:

RavenWrapper_rules
Violated Rules in Raven Wrapper

As I imagined this class is violating many more rules than I expected: immutability, visibility, OOP design and naming convention. Let’s have a look at the class code:

public sealed class RavenWrapper {
private static Lazy _instance = new Lazy(() => new RavenWrapper());
public static RavenWrapper Instance => _instance.Value;
private RavenClient _ravenClient;
public static string VisualStudioVersion;
private RavenWrapper() { /*init stuff*/ }
public void Capture(Exception exception, ErrorLevel errorLevel, strIng message){ }
[/code]
public sealed class RavenWrapper {
 private static Lazy _instance = new Lazy(() => new RavenWrapper());

 public static RavenWrapper Instance => _instance.Value;

 private RavenClient _ravenClient;
 public static string VisualStudioVersion;

 private RavenWrapper() { /*init stuff*/ }

 public void Capture(Exception exception, ErrorLevel errorLevel, strIng message){ }
}
[/code]

Looking back at it now, I realise there was no real benefit in having this class as a singleton. I should probably have made the RavenClient static, readonly and private. Even in that case there is no advantage really. RavenClient doesn’t open or reserve a connection when it is instantiated, so there wouldn’t’ be any benefit in “caching” its instance.

In my first refactor attempt to the class I changed the way the static instance of the object is created using a method instead of a property so that I can pass along the vs version as a parameter to the constructor. This class is still designed as a singleton:

private static RavenWrapper _instance;
public readonly string _visualStudioVersion;
public static RavenWrapper Instance(string vsVersion)
if (_instance == null)
_instance = new RavenWrapper(vsVersion);
return _instance;
private RavenWrapper(string vsVersion)
_visualStudioVersion = vsVersion;
private static RavenWrapper _instance;
public readonly string _visualStudioVersion;

public static RavenWrapper Instance(string vsVersion)
{
   if (_instance == null)
         _instance = new RavenWrapper(vsVersion);
   return _instance;
};
private RavenWrapper(string vsVersion)
{
   _visualStudioVersion = vsVersion;
}

When I ran another analysis on the solution I surprisingly found that the debt went up by 2%. Also 4 more rules were violated (1 of which was critical). It didn’t seem the change I made to the class was the right one.

NewDebt
~2% Tech Debt Increase.

Let’s see again what NDepend thinks about RavenWrapper now, and how many rules I fixed/broke.

RavenWrapper_rules_first_change
RavenWrapper – More violated rules.

Despite the number of violated rules hasn’t changed, the total cost, the debt and annual interest has halved just by resolving the first critical rule. Although I am not quite there yet, I think I’m on the right track.

Interestingly NDepend now recognises the RavenWrapper uses the singleton pattern while it didn’t when the it was implemented using lazy instantiation. The singleton is considered by many an anti-pattern and there can be found a lot of different opinions around the web. On the corresponding violated rule there’s an interesting link to an article that treats the topic extensively.

In my third refactor attempt I decided to remove entirely the singleton implementation. This is what I came up with:

public sealed class RavenWrapper
private readonly RavenClient _ravenClient;
private readonly string _visualStudioVersion;
public RavenWrapper(string vsVersion)
_visualStudioVersion = vsVersion;
public sealed class RavenWrapper
{
    private readonly RavenClient _ravenClient;

    private readonly string _visualStudioVersion;

    public RavenWrapper(string vsVersion)
    {
      _visualStudioVersion = vsVersion;
    }
}

Only now I understand how complicated and unnecessary the original design was. The simplest solution is most of the time the best. When I ran again the query I finally got a good result:

final_Raven_wrapper
RavenWrapper – Two violated rules only.

The “API breaking changes Methods/Fields” rule warns that the current API has changed since the baseline and a method or a field has been removed. These warnings are very important for SDK development because a change in the API can break that rely on them. This is not relevant to me though as this is a Visual Studio extension and not a library like JSON.NET or Moq for instance.

Final Thoughts

It’s been fun to play around with NDepend. I brushed up my skills on code metrics, OOP design practices and code smells. Although it is a software targeted for skilled software architects I also believe it could be a great learning opportunity for mid and senior developers.

I can’t of course say if this software is the right one for you as it very much depends on your needs, budget and size of your project.

NDepend has a lot of features and it’s fully configurable although the first few hours will be a bit of a pain. NDepend is very broad it might take a long time to master it. There are plans though to release with the next version of NDepend a simplified beginners VS menu, that can be switched to the actual one at any time. Also a series of 2 minutes intro video will be released soon for each feature.

I hope you enjoyed this article. Try NDepend if you get a chance, you can download a 14-day trial evaluation here. If you instead want a boost during your debugging sessions try LINQBridgeVs, and let me know what you think in the comments below!

See you soon!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK