In case you haven’t seen it, I’ve been diving ever-further down the code research rabbit hole. Today it brings me to the Autofac codebase (found here). But to understand how I’ve arrived here, consider some of the questions that I’ve answered previously.
- Do singletons make your code worse? (Yes)
- What effects do unit tests have on your code’s metrics? (Good ones)
- What effect does functional-style programming have on your codebase? (Not much, other than to reduce the use of object-orientation features)
With each of these studies (and the rest of the series), the research has become more sophisticated. This is because I’ve started working with folks that have a data science and machine learning background. So I’m now having fun assembling measurable codebase properties into broad categories and seeing how my corpus of 500+ open source codebase resolves into patterns.
And that brings me to Autofac.
It caught my eye by ranking in the 85th percentile for what I’m calling “testable” codebases and in the 58th for “readability.” And in general, it scores decently to well in just about every measure. This gave me an idea for a post in the same sort of vein as the one I did about Moq. Let’s do a deep dive into Autofac’s codebase using NDepend.
What Is Autofac?
To understand what caught my eye, let’s talk about what Autofac actually is. Autofac is an inversion of control (IoC) container. If you’re not familiar with this idea, it’s a way in the object-oriented world to inject class dependencies (usually into a constructor).
The purpose? Making it easier to test your code and centralizing the logic for dependency management. But let me beg off from my basic explanation and use Autofac’s own explanation of its value proposition.
Autofac is an IoC container for Microsoft .NET. It manages the dependencies between classes so that applications stay easy to change as they grow in size and complexity. This is achieved by treating regular .NET classes as components.
So Autofac provides you with easy inversion of control, with the idea that this will keep your dependencies manageable and your code clean as it grows. Interesting, given its good percentile scores in my research.
Okay, so Why Study Autofac?
With all that in mind you might wonder why, exactly, this caught my eye. Well, it’s pretty meta.
Autofac exists to make your code testable, clean, and decoupled. So seeing its initial stats at a quick glance made me wonder, “is Autofac’s code testable, clean, and decoupled?” Its scores for testability and readability seem to indicate that yes, it is. But we can certainly dive a lot deeper.
With that in mind, let’s consider a series of questions. The aim is to see the extent to which Autofac “dogfoods” the principles it supports. Now, having written unit testing utilities, I can tell you that you sometimes write support/library code that has unexpected differences from how you’d write normal application code. So these questions are meant to be interesting, rather than any kind of specific prescription for what Autofac should or shouldn’t do.
- Is Autofac loosely coupled at the type level?
- Is it loosely coupled at the namespace and assembly level?
- Does Autofac have comprehensive unit tests?
Let’s fire up NDepend and take a look.
Is Autofac Loosely Coupled at the Type Level?
This first one is pretty easy. To get a sense of this, I’m going to use NDepend’s code metrics view. This will give us the means to use two visual cues for understanding the codebase’s metrics: size and color.
In this heat map view of the codebase, we see all of the types as boxes. The larger the type, the more that other types are using it. And with color, green types are using almost no other types in the codebase, ranging up through yellow, then purple, and on up to red for types that are using a ton of other types.
This codebase appears to consist of a lot of relatively decoupled types, neither using very many other types nor finding themselves used a lot. But there are definitely some hot spots in there, specifically in the form of builder objects and some types containing lots of extension methods. But that’s to be expected.
Generally speaking, they’re doing a pretty effective job keeping their types decoupled.
Is Autofac Loosely Coupled at the Namespace and Assembly Level?
This question is a little less directly relevant to the theme of IoC, but important nonetheless. Loose coupling at every level is what speaks to maintainability.
First, let’s take a quick glance at the assemblies. You can view both assemblies and namespaces using the dependency graph view. And, you can decide whether or not you want that view to include everything, or just the namespaces and assemblies that are part of your codebase.
Here’s Autofac’s assembly graph, which is pretty simple. It’d kind of have to be, given that the application has a really light footprint.
So that looks perfectly fine and reasonable. Each assembly in its place. But now look at the namespace graph:
The codebase has 605 types and 62 namespaces, resulting in fewer than 10 types per namespace. And those namespaces are heavily interrelated, including a number of cycles and mutual dependencies. This is also contributing heavily to the giving Autofac a “B” tech debt rating instead of an “A.”
This is not really a maintainability problem in and of itself, right at the moment. But as codebases grow, namespace boundaries become logical places to break code out into a new assembly. And if your namespaces are all snarled together in cycles, you won’t be able to make that happen.
Does Autofac Have Comprehensive Unit Tests?
This is an easy one to answer. In my research, it was rated one of the more testable codebases, so you’d expect it to have unit tests, but that’s not a given. Of course, also noted in my research was the percentage of methods that are unit tests, and Autofac had one of the highest scores there.
But to satisfy my curiosity, I just ran the unit tests.
669 passing tests. That’s pretty darned impressive when you look at the NDepend dashboard and realize that this entire codebase, unit test methods included, only has 1,999 methods (circled).
Of course, you can use NDepend to import code coverage data. And I would have done that if I’d had coverage data to import. Something about the combination of XUnit tests, .NET core, and Visual Studio, however, conspired to prevent me from generating coverage data (and, apparently, this is a non-trivial issue).
Issues and Anti-Patterns: What Else is Going On Here?
Looking at the dashboard above, you’ll see that this codebase rates a “B” for technical debt. A framework to help with dependency injection offers to help prevent technical debt, so it’s worth looking at what sort of debut it, itself, incurs.
The tangling at the namespace level certainly doesn’t help. But let’s look at a list of issues creating technical debt, ordered by the amount of debt incurred. Here’s NDepend’s “Debt and Issues per Rule” query, sorted by the estimated amount of tech debt:
Here’s what’s going on.
- The codebase has a lot of methods per type, which makes maintenance hard and often runs afoul of the single responsibility principle.
- They have a lot of empty interfaces (most likely the marker interface anti-pattern).
- They use a lot of boxing and unboxing (incurring a performance penalty for converting between value and reference types).
- And, finally, the last one weighing in at over a day of work is the relatively prominent exposure of nested types.
It’d be good to clean some of this up, but none of this necessarily runs afoul of the charter of an IoC framework.
Autofac, What’s the Verdict?
Looking inward at an IoC framework, we see that it does indeed keep its own type coupling to a minimum and that it writes testable code and then tests that code. At the namespace level, things get a little iffy. But that’s really the only complaint.
If I went and fixed the namespace issues, the codebase would be right on the precipice of having an “A” rating for technical debt. (I know this because I disabled the namespace-related warnings and observed the debt rating). This means that Autofac, on the whole, has few warnings across the board, particularly of high severity. NDepend thinks this is a good codebase and my ranking of codebases agrees.
So there you have it. A codebase aimed at helping other people keep their code tidy, testable, and decoupled is, itself, a pleasure to edit and to work in.