NDepend Blog

Improve your .NET code quality with NDepend

extension_methods_decline_of_traditional_oop

Extension Methods and the Decline of Traditional OOP

October 9, 2018 7 minutes read

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.

Why not?

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:

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.

Comments:

  1. Anonymous says:

    Can you make your site even more unreadable? I think the thin, small and light gray font against a white background is still too readable. Can you put some white noise in the background? Or change the text color to #FEFEFE to contrast nicely with the background in #FFFFFF.

    /s
    You want your readers to read or to squint?

  2. I love you. I do not know you but I love you little angry puppy.

  3. Tom Faber says:

    Based on my experience, extension methods make mocking harder, which may be why you see fewer unit tests in codebases with more extension methods. I do much of my work in C#, using NSubstitute as a mocking framework. Testing extension methods is easy (as with any method, the simpler and purer the function, the easier to test) – but testing something else that depends on an extension method is not. If I need to mock an extension method, I have to wrap the extension method in an interface and at that point my code is complex enough that it’s not worth keeping the extension method. For an example that I’ve run into, see https://github.com/Azure/autorest/issues/976.

  4. James M Lonero says:

    You are right Erik. As C# has progressed, there has been a decline of OOP principles. Extension methods, that would normally be taken up by a subclass. Events that would have used an observer design pattern. Even linq has some abstractions that are questionable as OOP fundamentals. But, look at C++, where C# and Java have roots. C++ is not truly an object oriented language. You can keep your code as OO as possible, but it is still quite easy to fall back into C constructs. Is it easier to not completely use OO in your coding? Sometimes, it is. Possibly, as projects get larger, we don’t need as much OO design and programming. Even in small projects, all OO is not needed. What does the rest of the community think? What about the OO purists?

  5. Today I read your 2010 article and then this one. I personally hate extension methods. I curse the designer who added them to the language. But not for the primary reasons you seem to be describing.

    Having a static method that operates on an interface or a class and performs a reusable set of operations on that interface or class makes perfects sense and is very useful when appropriate. Adding this capability into a closed class hierarchy or where the functionality applies to two different hierarchies is nearly impossible without this technique. In providing this ability, there is nothing wrong with the stateless nature of extension methods. But extension methods did not add this. This is simply a static method.

    My problem with extension methods is the syntax. You discussed in 2010 the similarity of extension methods to having a substantial number of global functions that all operate on a type. But extension methods are far worse than that. If you took the same function and removed the way the method is called and forced a consumer of the method to write EXTENSION_CLASS.EXTENSION_METHOD(object,…..), then your code is still readable and clear. At least exponentially clearer and less error prone than it is now. But calling object.METHOD(….) is extremely ambiguous and the meaning of it changes based on your using statements. Of course you could argue that your using statements might just reference a different class with the same name. But that would still likely have far less conflicts and far less code to inspect.

    Imagine trying to debug a situation where an extension method had its signature changed in a source code file that had 50 includes that you are unfamiliar with.

    IMO, good code substantially considers the time it takes to read and understand it, instead of only the time it takes to write it. Using var when the type is not obvious, anonymous methods without parameter types, and a slew of other modern practices substantially increases the time it takes to understand code. Quick to understand non error prone code is characterized by a developer other than the author having to inspect the minimum amount of peripheral code.

    As you pointed out if you are only dealing with a hand full of extension methods, then this is not an issue. But if you work on a large code base and use extension methods every time a reusable static method that targets a specific interface is what makes sense, you quickly degrade into a far slower than necessary understanding of the code unless your handy IDE can always find the exact method that was trying to be called.

    Good programming paradigms scale. Meaning you can use them as much as necessary and if they make sense has nothing to do with how many other times you have used the same paradigm within the same project. The creation of extension methods doesn’t scale. This is equivalent to why the effective size of a JavaScript program was minimal prior to Google’s Closure Compiler and more recently Microsoft’s Typescript. It is effectively unreadable and uncheckable leaving the amount a developer has to know prior to being able to read and understand a small set of code have too much overhead to be effective. Extension methods are an attempt to take .NET towards the weaknesses of untyped javascript IMO. While the compiler will catch the error before deployment, it does nothing to help the developer.

    I feel like I am going out of my mind as I do not see other developers point out this problem with extension methods. A google search for “are extension methods an anti-pattern” took me to your doorstep.

    Am I missing something?

Comments are closed.