When code can't fit your brain: NDepend and PostSharp

by Petr Hudeček and Patrick Smacchia on 02 Nov 2020

The size and complexity of codebases have exploded in the last decade. What can you do when your codebase no longer fits your brain? In this article I’ll suggest two completely different tools: NDepend to visualize the code, and PostSharp to reduce its complexity.

Since PostSharp is itself a complex codebase, we’ll use NDepend to produce some interesting graphs out of it.

NDepend is like a swiss-army knife for .NET developers. Among the many use cases the tool can handle is providing visualizations that help you with understanding complex code bases.

PostSharp is an extension to C# that allows you to remove repetitive code from your projects.

Reducing complexity with PostSharp

With PostSharp, you can dramatically reduce the boilerplate code that stems from the implementation of design patterns or non-functional requirements like logging or caching.

Instead of cluttering your source code with boilerplate, you add a few custom attributes telling the compiler what needs to be done. These custom attributes are called aspects and they describe how the target code should be enhanced. At build time, after the C# compiler finishes, PostSharp injects the repetitive code directly into your binaries.

The result: your code is shorter, simpler, and more readable. Because some concerns like caching, logging, INotifyPropertyChanged or multithreading are abstracted away into aspects, your code is more likely to fit your brain.

For example, you can implement INotifyPropertyChanged just by adding an attribute:

[NotifyPropertyChanged] // 1. Add this.
public class CustomerViewModel
{
    public CustomerModel Customer { get; set; }

    public string FullName
    {
        get
        {            
            // 2. Now, a change in Customer, Customer.FirstName or
            // Customer.PrincipalAddress.Line1 will trigger the event.
            return string.Format("{0} {1} from {2}",
                Customer?.FirstName,
                Customer?.LastName,
                Customer?.PrincipalAddress?.FullAddress);
        }
    }
}

Or, you can implement method memoization like this:

[Cache] // 1. Add this.
public static Customer GetCustomer(int id) 
{
    return ExpensiveDatabaseCall(); // 2. Runs only once per customer (until cache is invalidated)
}

Understanding the PostSharp code base with NDepend

PostSharp lists 41 packages on NuGet, made from 75 C# projects (not including tests), many of those targeting several frameworks. Some parts of the code are 15 years old and some other address the newest features on .NET 5 and C# 9.

How to make sense of such a complex and extensive codebase? This is where NDepend can help.

Let’s start with a dependency graph of PostSharp assemblies, which in NDepend looks like this:

Note that I created this graph from assemblies, not from source code, so you can visualize even projects that you don’t own. The size of the box of each assembly corresponds to the assembly’s code size (specifically, the total number of lines of code if source code is available, otherwise the number of IL instructions).

This is a good first look at a new code base that can help you understand what is what. In our case, we see PostSharp.dll, our main redistributable library, on the right, and the entire Patterns library sitting to the left.

All the green assemblies come from PostSharp Logging because we have a separate library (and a NuGet package) for each logging framework that we support officially, and here we can see that they all depend on the main PostSharp Logging assembly, the Commons library and of course the main redistributable.

An inheritance diagram

Let’s now zoom in on the main feature of PostSharp: the aspect framework. In PostSharp, you add functionality to your code by annotating it with attributes which all extend the Aspect class.

With NDepend, we can easily create an inheritance diagram of all classes that extend Aspect across all of our assemblies, even if they are in different Visual Studio solutions:

The highlighted red box, the MethodInterceptionAspect, is the currently selected class. The blue classes are those used by the MethodInterceptionAspect and the green classes are those that subclass MethodInterceptionAspect.

MethodInterceptionAspect is an interceptor - a class that, when you apply it to a method, replaces that method’s body with its OnInvoke method (and you can then call the original method body from the OnInvoke).

With PostSharp, you can subclass MethodInterceptionAspect to create your own interceptors and you can see that aspects in our Caching and Threading libraries do just that.

Finally, let’s use NDepend to help debug a specific issue.

For example, suppose that we have a flaky test in our Caching product (n.b. our caching tests aren’t actually randomly failing). PostSharp Caching caches return values of your methods: you annotate a C# method with the [Cache] attribute and each time it’s called with the same arguments, you get the previous result instead of the method body being called again.

But what if a test says that sometimes, the original method body is called twice anyway? What we can do is find the only place that stores the cached value in PostSharp Caching, the SetItemCore() method, and create a caller-callee graph for it in NDepend, creating a complete map of all methods that call it, even indirectly. This is what that looks like:

The culprit may well be somewhere in this area. The class AutoReloadManager in particular seems suspicious. That could be the place that’s generating a random behavior and we can go investigate it.

Conclusion

The size and complexity of codebases has exploded during the last decade, and we need tools to cope with this challenge. In this article, we looked at two different but complementary approaches: NDepend and PostSharp.

Visualization tools like NDepend can help you explore and understand such code, for example, when you’re moving to work on another area of the code base.

Let’s clarify that NDepend is not just for code visualization. The tool can handle many other scenarios related to code quality, technical-debt estimation, code coverage by tests, code metrics, code querying and more. See a list of use cases here. You can download a free full-featured trial here.

The approach ofPostSharp, on the other side, is to help you eliminate boilerplate code, and reduce and simplify your code base. It also contains many useful .NET libraries that can add automatic detailed logging, method memoization, automatic and smart INotifyPropertyChanged and other features to your code.

You can download the free Community edition of PostSharp on the official website.