A bunch of years ago, I wrote a post on my own personal blog titled, “Why I Don’t Like C# Extension Methods.” Over the years, I’ve updated that post a bit, but mostly left it intact for posterity I guess. But if you ask me how that post has aged, I’d respond with “it’s complicated.”
I didn’t like extension methods back then. I do like them now, provided they aren’t abused.
So, what gives? Am I just a hypocrite? Or is it a matter of growth?
Well, none of the above, I’d argue. Instead, the programming landscape has changed and evolved. And I’d submit that extension methods were actually ahead of their time when the language authors came up with the concept. But now they’re hitting their stride.
Don’t take my word for it, though. This is another one of my research posts, whose previous ranks have included how functional approaches affect codebases, what unit tests do to code, and a look at singletons. Today, we’ll take a look at extension methods and what properties of codebases they correlate with.
Extension Methods As a Violation of OOP Practice
Let’s return briefly to my years-old antipathy for extension methods. I didn’t like them back in 2010 or so when I originally wrote the post.
Well, part of it had to do with the codebase I was working in at the time. The folks there had a rather shaky grasp on concepts like separation of concerns, testable code, and OOP design patterns. But they did love new shiny objects.
The result? “Extension method all the things!” The team got ahold of this newfangled language construct and used it everywhere, including to implement functionality that would have made more sense as part of the instance. The codebase became a fragmented nightmare, epitomizing the code smell of shotgun surgery.
Now this is only one single instance of abuse, and it happened to surround me. But, while this piqued my ire, it represented a deeper underlying trend. People using extension methods were making their code less object-oriented. And OOP was the king of the hill back then—the generally accepted practice.
Extension Methods Revisited in 2018
So why, then, has my take on this changed? Well, because a lot has changed in the last eight years or so.
Functional programming in a declarative style has taken the world by storm. As processing has become increasingly parallel, people increasingly prefer stateless code or code that deals with a minimum of state. This has given rise to functional languages, but also to trends in existing. OOP languages use them in a more functional way.
And, along the way, I’ve seen better and better uses of extension methods. People have used them (as Microsoft did in their initial design) to build fluent interfaces and extend third-party functionality in elegant ways. They’ve used them to build pure functions and stateless methods for reuse. And they’ve done all of it without creating procedural nightmares and non-cohesive code constructs.
But don’t take my word for it. Let’s take a look at what else extension methods correlate with in codebases.
As in previous posts, I’ve used a corpus of nearly 600 open-source codebases to run regressions against an interesting property in question. Today, it’s looking at what percentage of a codebase’s methods are extension methods (extension method prevalence) and seeing what statistically significant correlations emerge. I’m discussing only correlations with p-values less than 0.05 and, in some cases, way less.
First, the No Brainers
Let’s start with a sanity check of sorts. Here’s what we’d expect to see if the world weren’t upside down. Extension methods correlate strongly with the following:
- Way more static methods, which makes sense given that extension methods are static by definition.
- More public methods, which also makes sense. Extension methods don’t have to be public (they can also be internal), but they commonly are, particularly in lieu of, say, instance helper methods.
- More pure methods and fewer methods that change state, which is merciful, since the abominable alternative would be a bunch of extension methods that modify global state (shudders).
So, sanity check passed. Extension methods correlate with, well, things they would almost have to correlate with. But let’s look at some more findings.
Changing the Way We Build Methods
In codebases with many extension methods, we see way more method overloads and slightly more parameters per method, on average. The latter makes sense off the cuff since extension methods must take at least one parameter, by definition. The overloads are interesting, however, indicating that perhaps these codebase authors favor overloading their extension methods.
We also see generally fewer virtual methods in the codebase, which could be partially explained by the fact that the extension methods (as statics) cannot be virtual. But there’s such a negative correlation as to indicate that these codebase authors are less likely to take advantage of this construct in instance methods as well, perhaps indicating less reliance on inheritance and polymorphism (more on this in a bit).
You also see a sharp uptick in “methods that could be private,” with an increase in extension methods. That means that codebases with extension methods have disproportionately more situations where the authors ignore possible encapsulation scenarios.
And, perhaps most the interesting and bizarre, codebases with more extension methods have WAY more generic methods. You got me on this one. I have no idea why that would be, unless it’s perhaps a function of both of those being relatively newer language constructs that don’t exist in really old codebases.
Changing the Way We Build Types
Let’s now take a look at things at the type level. First of all, we see a decent uptick in immutable types when codebases have extension methods, beyond just the explanation that extension method types are (hopefully) immutable. So people who use extension methods generally favor immutability in other types relatively more.
With extension method prevalence, we also see fewer abstract types and less inheritance, returning to a theme from the methods section. In general, we’re seeing less inheritance/polymorphism and less object-orientation.
And, interestingly enough, we also see fewer fields at the type level as well as fewer serializable types. I take this to mean that we’re seeing a de-emphasis in the instance state and in the idea of representing data with the sort of constructs that we might dump to files, perhaps favoring less data in memory and a more transient state.
Clean Code Statistics are Mostly Unaffected, But Extension Method Codebases Have Fewer Unit Tests
In my studies and consulting, there are a handful of metrics that, for lack of a better term, I think of as “clean code metrics.” How complex and long are methods, how cohesive are types—that kind of thing. Inasmuch as these showed up in the statistically significant bucket, they did so with near zero slope. This means that there’s a meaningful relationship of “meh, either way.”
So, here are some statistics with almost no variance between codebases with many and with few extension methods:
- Cyclomatic complexity
- Cohesion (LCOM)
- Busyness of constructors
- Assembly instability
- Rate of comments
Now, I haven’t taken a deep dive into any of these codebases to evaluate them at a code review level. But I really saw no statistic that would suggest that extension method prevalence has any bearing at all on how maintainable chunks of code are.
But interestingly and in spite of all that, there’s a definite inverse relationship between the prevalence of extension methods and the prevalence of unit test methods. That’s purely an observation, and I have no operating hypothesis as to why that might be. It may make good fodder for a future post here if we can dig deeper to figure that one out.
Extension Methods Correlate With the Decline of OOP Mainstays
If I had to draw conclusions from this (or at least form qualitative hypotheses worth trying to disprove), I’d start with a relatively simple one. Extension methods correlate with a decline of some of our OOP hallmarks, such as inheritance, encapsulation, and polymorphism.
As this construct becomes more popular, so too do immutability, functional-style approaches, and static, stateless methods. But as all of this becomes more popular, we become no “better” at writing code, per se. We’re not making things more maintainable or simpler on average, necessarily. We’re just blurring our paradigms and creating a functional-OOP hybrid.
Is that good or bad? Probably neither. Programming is, after all, an extremely circular pursuit when you zoom out far enough and watch the pendulum swings. But it sure is fun to do, fun to study, and fun to speculate about.