Managing Code quality in enterprise software with nDepend static analysis

By Joshua Holden

Preamble


Not so long ago whilst working on a large monolithic mission-critical web application renowned for being a pain to work with I was asked by the organisation's CTO, and I paraphrase:

"We are going to be depending on the functionality of this software a lot more going forwards, do you think It needs a total rewrite or can we get by with using the existing codebase and improving it"

This is a common scenario which could be avoided by adding static analysis into the build pipeline to detect and block changes with "code smells" preventing the growth of issues into the codebase until the point it becomes unmaintainable.
Often many developers work on a single application stack and it ends up growing arms and legs, becoming unmaintainable, but the question is, "How do I work out if it's going to be more cost-effective to rewrite the application or try and fix it?", fortunately, there is a way of working this out by calculating technical-debt and annual-interest.

Technical-debt is an abstract term and quite hard to explain without using a metaphorical scenario, so I will do exactly that, let's imagine you own an  old house in the country which you bought for £400,000, you notice it is creaking a bit  and wood is flaking from the ceiling so you call in an expert to look at the house which he examines and comes back to you with the following report:

"I have examined the house, it has a serious termite infestation and it will fall down in about 4 years, but the cost to sure up the wood and remove the termites will be in the region of  £50,000".
In this scenario, the termites are the bugs in the software and the house is the software, after speaking to the expert we can now make the following assertions:

1: If we let the house fall in 4 years it has a £100,000 a year annual-interest, and as with monetary debt will get more expensive to repair the longer is left unaddressed.

2: If you repair it now, it will cost £50,000 but will increase the habitability and resale value of the house by at least the time and money costs, furthermore it will save you having to completely rebuild or abandon it in the long term. 

Using NDepend to calculate technical debt

When managing software and development teams the expert here could be a solutions architect or a lead-developer etc using a tool such as static analysis to help them examine the code base and calculate the technical debt.

Below is an example of NDepend static analysis on the aforementioned solution:

Here, you can see that to rebuild the entire application of 13,704  lines it would take 365 days, and to fix all of the issues (technical debt) addressed by static analysis it would take  28 days (28.908 but NDepend floors the number),  therefore the debt level is 7.92%  (Days to rebuild / debt days * 100).

This can be further reported on showing monetary costs as well, however, obviously, to make these calculations NDepend needs to be aware of how many hours per day a developer works, and that can be set as shown below:

You can see from this, calculating the debt-level is highly configurable depending on developer velocity, hours worked per day and cost per hour, furthermore you can set a threshold on the SQALE debt rating, this can be especially useful in a CI/CD pipeline failing a build if the debt-level crosses the threshold to maintain quality software deployments without slippage.

Okay, I get it technical debt can be managed, but what else can nDepend do?

Quite a lot more actually, to calculate technical debt NDepend also needs to be aware of issues in your code and classify them accordingly in a ranking from low to critical (and blockers which need immediate address), to do this NDepend has a huge list of common and not so common errors it uses its static analysis engine to detect, furthermore other checks can be easily added, and existing rules can be modified if they do not suit your development shop by using C# LINQ queries.

I'm not going to go into to much detail because the rules are discussed in detail here, instead, I will discuss at a high level below:

Built-in issue rules

Hotspots
Hot spot rules allow you to quickly identify issues with the most technical debt allowing you to address them first and cut down the code debt quicker.

Code smells
Covered under code smells are 9 rules detecting common code smells such as a lack of comments, overly complex methods, too many local variables in methods and much more.

Code smells regression
Regression rules only fire when a baseline has already been done (previously ran the analysis on an older version), these rules check if the number of code smells has increased/decreased since the previous analysis (diff checks).

Object orientated design
Rules specifically focusing around SOLID and OOP paradigms, for example: avoid empty interfaces and classes with no descendants should be sealed.

Design
This ruleset addresses design flaws which increase technical debt such as usage of obsoleted methods and detecting classes that should be structs.

Architecture
The ruleset for detecting architectural issues such as accessing database types within a UI layer and not using view models preventing overposting.

API breaking changes
The ruleset is dependant on a previously ran analysis, does a diff check and makes sure nothing has been introduced which could cause breaking changes for anyone or anything consuming the API.

Code coverage
The ruleset for the detection of code not covered by unit tests, a number of the tests here depend on code coverage files being imported from software such as visual studio's own code coverage report or ReSharper dotCover.

Dead code
Detects if a class, method or field is never used anywhere and flags it for removal.

Security
Possibly my favourite test ruleset detects security flaws such as code vulnerable to SQL injection, usage of weaker cyphers such as DES and 3DES and disabling certificate validation.

Visibility
Detection for classes and fields with higher accessibility modifiers than required.

Immutability
Checks such as not marking structs as immutable and arrays marked as read-only.

Naming conventions
Enforces naming conventions such as ensuring types are always capitalised and preventing multiple types with the same name which happens and is extremely annoying.

Source file organisation
Several checks to ensure source code is well organised such as declaring namespaces that do not match the solution structure.

.Net framework utilisation
A huge set of rules making sure when using .net framework classes and functionality such as not throwing generic ApplicationErrors when a more explicit exception type could be used.

NDepend can be run separately to visual studio as a stand-alone app, however, I prefer to run it with the Visual Studio plugin as it detects issues as you code in realtime:


Above you can see on the far left, the different rulesets, in the middle it displays a count of violations of each rule within the ruleset, clicking a rule shows all areas in the application which violate the rule which can be clicked on to jump to the area with the issue for address.

Creation of new rules is extremely simple, take for example the below rule that enforces namespaces start with company name, in this case, ACME Ltd:

warnif count > 0 from n in Application.Namespaces where
!n.NameLike (@"^AcmeLTD")
select new {
n,
n.NbLinesOfCode,
Debt = 10.ToMinutes().ToDebt(),
Severity = Severity.High
}

the above rule asserts that namespaces must start with AcmeLtd and any that do not add 10 minutes to the technical dept and flag as a high priority issue.

Reporting

One of the strongest points of Resharper is the reports it can kick out as standard, as such, below i'm going to go into a little detail of the reporting functionality.

The dependency graph and matrix offer 2 different dimensions of the same data and allow you to take a closer look at how the solution code is actually structured and highlights coding flaws such as large dependency cycles as well as helping with refactoring analysis as you can see at a glance if you alter a specific dependency what impact it will have on other components within the code, shown below is an example of a dependency matrix:

This is just a slice of my application focusing on the controllers only but you can clearly see that GetPostById method is called by many of the controller methods and making changes to it could be considered risky but it also helps with regression testing should you make a change as you know exactly what controllers will need to be checked extra carefully during the testing cycle.

Taking another view on the same data using the dependency graph shown below allows you to visualise the couplings and relations between classes and structures within your application.

Each node in this dependency chart can be double-clicked into to zoom into it and view at an even finer level for example shown below double-clicking on the user services node within the lager Core.Services namespace

As you can see this is an extremely powerful tool for visualising large applications, how they work and the impacts of any changes if you don't have the original UML documents to hand (or more likely they were never created).

We also get a treemap report which by default looks at cyclomatic complexity of methods, wherein the larger (in lines of code) a method is the larger the tree, and the more complex a method  the more it will move towards the red scale, shown below is an example showing the cyclomatic complexity dimension:

Nothing here is showing as red, but I can see at a glance the SaveFilesToDisk() method is starting to  move towards being overly complex, furthermore, the chart is interactive and highlighting an individual tree shows me some additional metadata on the class such as LOC (lines of code) and the signature or the method



The treemap offers a whole host of other dimensions that can be reported on by selecting a dropdown at the top of the treemap view:

furthermore, you can slice the data in another dimension, instead of showing trees as individual methods you can switch to specific assemblies, types, fields and namespaces 

The final report I feel worth mentioning is the abstractness vs instability report, at a glance, it looks quite complex but once you understand the concept it's quite easy to use the report to get useful information.
Abstractness is the measure of rigidity in  a codebase, the more abstract an object is the more flexible it is and easier to change, this is represented by the Y-axis, on the X-axis we have  instability, not instability as in "oh no my application is really unstable and crashes all the time", but stability in the context means the more a class/method is consumed and depended upon the more stable it is and the more likely you are to steer clear of making modifications to it.... enough rambling now, take a look:

For the sake of simplicity I have only analysed a single assembly here, but you can quickly see that the code is within the green area, erring towards instability, that is to say, that this specific assembly is unstable (few dependencies upstream) and it could be modified without too much worry of breaking, however, it's pretty concrete and as such not very extensible.

The general idea is to stay out of the zone of pain, painful because to get into this zone you need to have a lot of references/dependencies on the object but it's not very extensible and very rigid, and also avoiding the zone of uselessness wherein the architecture is very abstract and extensible but nothing is using it.

Using this graph you can quickly see areas where you are depending on libraries and code that would be hard to extend or replace should you need to do so.

 

Finally, NDepend allows you to kick out an HTML interactive report showing the 4 reports illustrated below, this is great for taking into meetings if you need to discuss with your team/boss problems areas in code and how you are progressing.

 

It also summarises the application metrics such as count of issues and technical debt score (woo I got an A!)

Summary

I only meant to write quite a short review on NDepend but fell in love with the power and visibility it offers which kept me coming back for more.
In summary, I have to admit NDepend is a fantastic suite of tools that make a developers job a lot easier when having to make architectural decisions on both new applications during the development process and understanding how to refactor and work with older software you are picking up to work with, it also looks to be a great tool to use within CI/CD to help maintain great code quality in the development pipeline but have yet to test this in anger.

At around £350 a year for a licence it's pretty good value as a lot of work and expertise has gone into the product, I can see it being a useful and cost-effective tool for maintaining code quality which would quickly provide return on investment.

I have barely scratched the surface of this software in this review but encourage you wholeheartedly to check out the awesome documentation on the vendor's website here as it covers the application in a lot more detail than I ever could in a single blog post.

As an aside and as full disclosure, I have no affiliation nor do I have any monetary gains to be made my making this review, it was simply done off the back of having a chat with the lead developer of NDepend on linked-in who piqued my interest and offered me a licence to have a play with.


That's all folks, happy coding!

 

Comments


Comments are closed