NDepend

Improve your .NET code quality with NDepend

Business Complexity vs. Implementation Complexity

It is good software design practice to make sure that methods can be entirely viewed in the code editor that typically shows 30 to 45 lines at a time. The root of this principle is easy to grasp: scrolling up and down over a too large method impedes code readability.

Refactoring a too large method or a too large class implies to create several code elements smaller in terms of number of textual lines. But ultimately the code behavior didn’t change. In other words the business complexity remained the same but the refactor session reduced the implementation complexity (at least we hope so).

Software complexity is a subjective measure relative to the human cognition capabilities. Something is complex when it requires effort to be understood by a human. Software complexity is a 2 dimensional measure. To understand a piece of code one must understand both:

  • What this piece of code is doing at runtime, the behavior of the code, this is the business complexity.
  • How the actual implementation solves the business needs at runtime, this is the implementation complexity.

The whole point of software design, SOLID principles, code maintainability, patterns… is to avoid adding implementation complexity to the business complexity. But from where implementation complexity comes from? There are 5 main sources of implementation complexity:

Too Large Code

We already mentioned this one. It is easy to limit implementation complexity by abiding by simple code rules that check thresholds on code metrics like methods number of lines of code and method complexity, or classes with too many methods. The Single Responsibility Principle (SRP) also contribute to smaller implementations: less responsibilities for a class necessarily means less code.

Lack of Abstractions

An abstraction such as an abstract method, an interface or even a delegate, is the minimum required knowledged to invoke an implementation at runtime without knowing it at design time. In other words an abstraction hides implementation detail. Abstraction represents a great weapon to reduce the implementation complexity because polymorphism can replace quantity of if/else/switch/case. Concretely code like that:

Can be replaced with code like that:

Abstraction also reduces implementation complexity because it frees the developer mind of implementation details.

The S in SOLID is the SRP (mentioned above) and is not related to abstraction. But the OLID principles are all about using abstractions wisely:

How do we check for good usage of abstractions? There is no magic stick like thresholds to limit too large code elements. However the Abstractness vs. Instability graph and metrics and the Level metric are a good start to identify code areas that need more abstractions. They are both described in this post about Dependency Inversion Principle.

State Mutability at Runtime

A common source of implementation complexity is mutable states. The human brain is not properly wired to anticipate what is really happening at runtime in a program. While reviewing a class, it is hard to imagine how many instances will simultaneously exists at runtime and how the states of each these instances will evolve over time. This is actually THE major source of problems when dealing with a multi-threaded program.

If a class is immutable (if the states of all its instances objects don’t change at runtime once the constructor is called) its runtime behavior immediately becomes much easier to grasp and as a bonus, one doesn’t need to synchronize access to immutable objects. See here a post explaining in-depth immutable class.

A method is a pure function if it produces outputs from its inputs without modifying any state. Like immutable classes, pure functions are also a good mean to reduce implementation complexity.

Some code rules exists to enforce state mutability like Structure should be Immutable or Fields should be marked as ReadOnly when possible. Being immutable for a class or pure for a method is such an essential property that dedicated C# keywords for that would be welcome, like readonly for fields. Here is a proposal for C# support of the immutable keyword. By now some ImmutableAttribute or PureAttribute can be used to tag such elements and some code rule can check for Types tagged with ImmutableAttribute must be immutable or Methods tagged with PureAttribute must be pure

Over Coupling

When trying to re-engineer/understand/refactor a large piece of code (I mean something made of dozens of classes like a big namespaces or an assembly), the difficulty is directly proportional to the amount of coupling between considered code elements. Both dependency graphs below shows dependencies between 36 classes: but the left contains 64 edges and the right one contains 133 edges: which one would you prefer to deal with?

One key strategy to control coupling is to layer components and make sure that there is no dependency cycles. This can be checked on namespaces for example with the code rule Avoid namespaces dependency cycles. Using abstractions is also a good way to limit over coupling. If an interface has N implementations then relying only on one interface is virtually like depending on the N underlying classes.

Lack of Unit Tests

Software testing is a large topic I won’t cover here. But one key benefit of writing tests (apart enforcing business rules and detecting regressions early) is to ensure that the code is testable. Being testable for code means less coupling, more abstractions and overall simple code. Ultimately if the code is easily testable we can safely assume that its implementation complexity is kept low. Here also many code rules like Code should be tested can help enforce high testability.

One Measure for All

There are more sources of implementation complexity but those 5 ones are certainly the bigger culprits. To reduce this unnecessary complexity one must be able to measure it. But how to unify too large code, bad design, poorly tested code and more in a single metric?

As we saw most of these aspects can be enforced with code rules. A code rule produces issues and for each issue the code rule can estimate the cost to fix an issue and the annual cost to let an issue unfixed. A famous analogy with the financial field says that:

  • The estimated cost to fix code smells is the Technical-Debt: a measure of the implementation complexity.
  • The estimated annual cost to let code smells unfixed is the Annual Interest of the Debt: a measure of the business operation cost induced by poorly written and poorly tested code.

These estimations can be expressed in developer-time and ultimately in money cost which can be used by management to take the right decisions.

You have no excuse for dead code.

You Have No Excuse for Dead Code

In darker times, software management would measure productivity as a function of lines of code.  More code means more done, right?  Facepalm.  When I work with IT management in my capacity as a consultant, I encourage them to view code differently.  I encourage them to view code as a liability, like inventory.  And when useful code is a liability, think of what a boat anchor dead code is.

I once wrote a fun post about the fate of dead code in your codebase.  And while I enjoyed writing that, it had a serious underlying message.  Dead code costs you time, money, and maintenance headaches.  And it has absolutely no upside.

A Working Definition for Dead Code

Okay. If I’m going to make a blog post out of disparaging dead code, I should probably offer a definition.  Let’s do that here.

Some people will draw a distinction between code that can’t be executed (unreachable) and executed code whose effects don’t matter (dead).  I acknowledge this definition but won’t use it here.  For the sake of simplicity and clarity of message, let’s create a single category of dead code: any code in your codebase that has no bearing on your application’s behavior is, for our purposes here, dead.

The Problems with Dead Code

Having defined it, what’s the problem?  If it has no bearing on your application’s behavior, what’s the harm?  How does it cost time and money, as I claimed a moment ago?

Well, simply put, your code does not live in a shrink-wrapped vacuum.  As your application evolves, developers have to change the code.  When you have only code that matters in your codebase, they can do this with the most efficiency.  If, on the other hand, you have thousands of lines of useless code, these developers will spend hundreds of hours maintaining that useless code.

Think of having dead code as being reminiscent of running your heat in the winter while keeping all of your windows open.  It’s self-defeating and wasteful.

But even worse, it’s a totally solvable problem.  Let’s take a look at different types of dead code that you encounter and what you can do about it.

Continue reading You Have No Excuse for Dead Code

Static analysis of .NET Core 2.0 applications

NDepend v2017.3 has just been released with major improvements. One of the most requested features, now available, is the support for analyzing .NET Core 2.0 and .NET Standard 2.0 projects. .NET Core and its main flavor, ASP.NET Core, represents a major evolution for the .NET platform. Let’s have a look at how NDepend is analyzing .NET Core code.

Resolving .NET Core third party assemblies

In this post I’ll analyze the OSS application ASP.NET Core / EntityFramework MusicStore hosted on github. From the Visual Studio solution file, NDepend is resolving the application assembly MusicStore.dll and also two test assemblies that we won’t analyze here. In the screenshot below, we can see that:

  • NDepend recognizes the .NET profile, .NET Core 2.0, for this application.
  • It resolves several folders on the machine that are related to .NET Core, especially NuGet package folders.
  • It resolves all 77 third-party assemblies referenced by MusicStore.dll. This is important since many code rules and other NDepend features take into account what the application code is using.

It is worth noticing that the .NET Core platform assemblies have high granularity. A simple website like MusicStore references no fewer than 77 assemblies. This is because the .NET Core framework is implemented through a few NuGet packages that each contain many assemblies. The idea is to release the application only with needed assemblies, in order to reduce the memory footprint.

.NET Core 2.0 third party assemblies granularity

NDepend v2017.3 has a new heuristic to resolve .NET Core assemblies. This heuristic is based on .deps.json files that contain the names of the NuGet packages referenced. Here we can see that 3 NuGet packages are referenced by MusicStore. From these package names, the heuristic will resolve third-party assemblies (in the NuGet store) referenced by the application assemblies (MusicStore.dll in our case).

NuGet packages referenced in .deps.json file

Analyzing .NET Standard assemblies

Let’s be clear that NDepend v2017.3 can also analyze .NET Standard assemblies. Interestingly enough, since .NET Standard 2.0, .NET Standard assemblies reference a unique assembly named netstandard.dll and found in C:\Users\[user]\.nuget\packages\NETStandard.Library\2.0.0\build\netstandard2.0\ref\netstandard.dll.

By decompiling this assembly, we can see that it doesn’t contain any implementation, but it does contain all types that are part of .NET Standard 2.0. This makes sense if we remember that .NET Standard is not an implementation, but is a set of APIs implemented by various .NET profiles, including .NET Core 2.0, the .NET Framework v4.6.1, Mono 5.4 and more.

Browsing how the application is using .NET Core

Let’s come back to the MusicStore application that references 77 assemblies. This assembly granularity makes it impractical to browse dependencies with the dependency graph, since this generates dozens of items. We can see that NDepend suggests viewing this graph as a dependency matrix instead.

NDepend Dependency Graph on an ASP.NET Core 2.0 project

The NDepend dependency matrix can scale seamlessly on a large number of items. The numbers in the cells also provide a good hint about the represented coupling. For example, here we can see that  22 members of the assembly Microsoft.EntityFrameworkCore.dll are used by 32 methods of the assembly MusicStore.dll, and a menu lets us dig into this coupling.

NDepend Dependency Matrix on an ASP.NET Core 2.0 project

Clicking the menu item Open this dependency shows a new dependency matrix where only members involved are kept (the 32 elements in column are using the 22 elements in rows). This way you can easily dig into which part of the application is using what.

NDepend Dependency Matrix on an ASP.NET Core 2.0 project

All NDepend features now work when analyzing .NET Core

We saw how to browse the structure of a .NET Core application, but let’s underline that all NDepend features now work when analyzing .NET Core applications. On the Dashboard we can see code quality metrics related to Quality Gates, Code Rules, Issues and Technical Debt.

NDepend Dashboard on an ASP.NET Core 2.0 project

Also, most of the default code rules have been improved to avoid reporting false positives on .NET Core projects.

NDepend code rules on an ASP.NET Core 2.0 project

We hope you’ll enjoy using all your favorite NDepend features on your .NET Core projects!

How to Use NDepend’s Trend Charts

Imagine a scene for a moment.  A year earlier, a corporate VP spun up a major software project for his organization.  He brought a slew of his organization’s software developers into the project.  But he also needed to add more staff in the form of contractors.

This strained the budget, so he cut a few corners in terms of team member experience.  The VP reasoned that he could make up for this with strategic use of experienced architects up front.  Those architects would prototype good patterns and make it so the less seasoned contractors could just kind of paint by numbers.  The architects spent a few months doing just that and handed the work off to the contractors.

Fast forward to the present.  Now a consultant sits in a nice office, explaining to a beleaguered VP how they got so far behind schedule.  I can picture this scene quite easily because organizations hire me to be this consultant.  I live this scene over and over again.
Continue reading How to Use NDepend’s Trend Charts

Computing Technical Debt with NDepend

For years, I have struggled to articulate technical debt to non-technical stakeholders.  This struggle says something, given that technical debt makes an excellent metaphor in and of itself.

The concept explains that you incur a price for taking quality shortcuts in the code to get done quickly.  But you don’t just pay for those shortcuts with more work later — you accrue interest.Save yourself an hour today with some copy pasta, and you’ll eventually pay for that decisions with many hours down the road.

So I say to interested, non-technical parties, “think of these shortcuts today as decisions upon which you pay interest down the line.”  They typically squint at me a little and say, “yeah, I get it.”  But I generally don’t think they get it.  At least, not fully.

Lack of Concreteness

I think the reason for this tends to come from a lack of actual units.  As a counterexample, think of explaining an auto loan to someone.  “I’m going to loan you $30,000 to buy a car.  With sales tax and interest factored in, you’ll pay me back over a 5 year period, and you’ll pay me about $36,000 in total.”  Explained this way to a consumer, they get it.  “Oh, I see.  It’ll cost me about $6,000 if I want you to come up with that much cash on my behalf.”  They can make an informed value decision.

But that falls flat for a project manager in a codebase.  “Oh man, you don’t want us to squeeze this in by Friday.  We’ll have to do terrible, unspeakable things in the code!  We’ll create so much tech debt.”

“Uh, okay.  That sounds ominous.  What’s the cost?”

“What do you mean?  There’s tech debt!  It’ll be worse later when we fix it than if we do it correctly the first time.”

“Right, but how much worse?  How much more time?”

“Well, you can’t exactly put a number to it, but much worse!”

And so and and so forth.  I imagine that anyone reading can recall similar conversations from one end or the other (or maybe even both).  Technical debt provides a phenomenal metaphor in the abstract.  But when it comes to specifics, it tends to fizzle a bit.

Continue reading Computing Technical Debt with NDepend