NDepend Blog

Improve your .NET code quality with NDepend

What the Singleton Pattern Costs You

What the Singleton Pattern Costs You

August 17, 2017 7 minutes read

Do you use the singleton pattern?  If not, I’m assuming that you either don’t know what it is or that you deliberately avoid it.  If you do use it, you’re probably sick of people judging you for it.  The pattern is something of a lightning rod in the world of object-oriented programming.

You can always use Stack Overflow as a litmus test for programming controversy.  Someone asked “what was so bad” about singletons, and voting, responding, and commenting went off the charts.  Most of those responses fell into the negative category.  Outside of Stack Overflow, people call singletons evil, pathological liars, and anti-patterns.  People really seem to hate this design pattern — at least some of them do, anyway.

NDepend takes a stance on the singleton pattern as well, both in its rules and on the blog.  Specifically, it encourages you to avoid it.

But I’m not going to take a stance today, exactly.  I understand the cathartic temptation to call something evil in the world of programming.  If some (anti) pattern, framework, or language approach has caused you monumental pain in the past, you come to view it as the tool of the devil.  I’ve experienced this and turned it into a blog post, myself.

Instead of going that route here, however, I’m going to speak instead about what it can cost you when you decide to use the singleton pattern — what it can cost the future you, that is.  I have a consulting practice assessing third-party codebases, so I’ve learned to view patterns and practices through a dispassionate lens.  Everything is just trade-offs.

What is the Singleton Pattern, Anyway?

Before I go any further, I should probably explain briefly what this thing is, in case you aren’t familiar.  I won’t belabor the point, but here’s a quick example.  (I’m keeping this to a bare minimum and not worrying about non-core concepts like thread safety.)

Alright, so why do this?  Well, the pattern authors define its charter as serving two principle purposes:

  • Ensure that only one instance of the class ever exists.
  • Provide global access to that single instance.

So let’s consider implications and usage.  When you use this pattern, you define an object that will exist across all application scope and that you can easily access from anywhere in the code at any time.  A logger is, perhaps, the most iconic example of a singleton use case.  You need to manage access to a resource (file), you only want one to exist, and you’ll need to use it in a lot of places.  A marriage made in heaven, right?

Well, in principle, yes, I suppose so.  But in practice, things tend to get a lot messier.  Let’s take a look at what can happen when you introduce the singleton pattern into your codebase.  Let’s look at what it can cost you.

Side Effects Hurt the Ability to Reason About Your Code

Think for a moment about how you would invoke an instance method on a singleton class.  For instance, let’s say that you’d implemented your own logger.  Using it might look something like this:

The method constructs an order in memory based on the order’s ID and then returns that order, stopping along the way to log.  It seems simple enough, but the invocation of the logging presents a simple conundrum.  If I’m looking at just the signature of BuildSimpleOrder, say, via IntelliSense, the logging is utterly opaque to me.  It’s a hidden side effect.

When I instantiate the class containing BuildSimpleOrder, I don’t inject a reference of a logger into a constructor.  I don’t pass the logger in via a setter, and I don’t give it to this method as a parameter.  Instead, the method quietly reaches out into the ether and invokes the logger which, in turn, triggers file I/O (presumably).

When you have singletons in your codebase, you lose any insight into what methods are doing.  You can take nothing for granted, and you must inspect each and every line of your code to understand its behavior, making it generally harder to reason about.  This was why Misko Hevery called singletons “liars.”  BuildSimpleOrder now does something completely unrelated to building a simple order — it writes to a file.

You Give Up Testability

Consuming singletons doesn’t just make your code harder to reason about.  It also makes your code harder to unit test.  Think of writing unit tests for the method above.  Without the logger instance, that would be a piece of cake.  It’d look like this:

Then, if building the order demanded a bit more complexity, you could easily add more tests.  This is an imminently testable method…without the call to the logger.

With the call to the logger, however, things get ugly.  That call starts to trigger file I/O, which will give the test runner fits.  Depending on the runner and machine in question, it might throw exceptions because it lacks permissions to write the file.  It may actually write the file and just take a while.  It may throw exceptions because the file already exists.  Who knows?  And worst of all, it may behave entirely different on your machine, on Bob in the next cubicle’s machine, and on the build.

Normally when you have external dependencies like this, you can use dependency injection and mock them.  But singletons don’t work that way.  They’re inline calls that you, as a consumer, are powerless to interpose on.

You Incur Incredible Fan-In and Promote Hidden Coupling

Singletons tend to invite use and to invite abuse.  You’d be surprised how handy it can be to have a deus ex machina at your disposal when you suddenly need two objects at the complete opposite ends of your object graph to collaborate.

Man, we could solve this really quickly and with little rework if the login screen could just talk directly to the admin screen.  Hey, I know!  They both have access to the logger.  We could just add a flag on the logger.  It’d only be temporary, of course…

Yeah, we both know that flag will NOT be temporary.  And this sort of thing happens in codebases that rely on the singleton pattern.  More and more classes take direct dependencies on the singletons, creating a high degree of fan-in (afferent coupling) for the singleton types.  This makes them simultaneously the riskiest types to change, the hardest types to test, and the most likely types to change, all of which adds up to maintenance headaches.

But it even gets worse.  Singletons don’t just invite types to couple themselves to the singleton.  They also invite types to couple their own logic through the singleton.  Think of the login and admin screens communicating through the singleton.  It acts as a vessel for facilitating hidden couplings as well.

And while I will concede that use of the pattern does not automatically constitute abuse, I will say that inviting it is problematic.  This holds doubly true when your only method of preventing abuse is vigilant, manual oversight of the entire codebase.

You Lose the Ability to Revisit Your Assumptions

I’ll close by offering a more philosophical cost of the singleton pattern that builds on the last three.  You have code with side effects that you have a hard time reasoning about.  Likewise, you can’t easily test it, and you create an outsize dependency on the singleton pattern instances.  What does all of that wind up meaning?

It means that changing your assumptions about the singleton itself becomes nearly impossible.  These things cast themselves in iron in your codebase and insert tentacles into every area of your code.  Imagine the horror if a business analyst came along and said something like, “Why can’t we just have two simultaneous log files?” or, “Why can’t we have sessions that stop and then restart later?”

In answer to these seemingly reasonable requests, the architect will turn red and splutter, “What?!  Do you know how much rework that would mean?”  When you use singletons, you had better make sure that there could really only ever be one log file, print spooler, session, etc. and that your instance of that will last the same length as the program.  Because if that assumption ever needs to change, you’ll find yourself in a world of pain, to the tune of a total rewrite.

I won’t call the singleton pattern evil or rail against it.  But I will tell you that it extracts a pretty high toll from you in the long run.

Comments:

  1. Although the singleton pattern isn’t without its down-sides, I think the sections about reasoning about the code and giving up testability are more related to the code violating the SOLID principles, mainly the Single Responsibility Principle and Dependency Inversion Principle. If the BuildSimpleOrder method took in an ILogger instance, or if the containing class took a dependency on it, some of the issues with the singleton pattern would become moot.

    In my experience, most of my issues dealing with singletons (usually via DI containers such as Ninject) are related to managing state. In other words, is this instance a distinct instance, or the same instance passed around to whoever needs one?

    Thanks for sharing your insights about this pattern!

  2. Tomaz Koritnik says:

    Everything you wrote is actually not the problem of the singleton but the problem of how you used it. In the examples above, singletons are used in a wrong way. In general, “Singleton” only means that some object, implementing a certain functionality, is instantiated only once in the system. This is actually very useful because you don’t need many copies of the same functionality in your system. So people that speak about avoiding singletons don’t really know what they are talking about. They don’t know how to use them and avoiding them will not really help them because the problem actually lies in their bad software design.

    1. Patrick Smacchia says:

      I agree but…
      AFAIK junior (and not so junior) developers see the singleton pattern the way it is described in the article with a static instance field and a private ctor and as far as I remember I also learnt it this way from the GoF book two decades ago.
      So better warn against all the problems this “implementation detail” provokes.

      1. R. T. Lessly says:

        I also learned about singletons from the GOF book. But I see GOF as only illustrating the basics concepts of the patterns discussed and not intending to provide any kind of robust, production-quality designs. Their example of the singleton, while possibly useful as-is for simple situations, is just a beginning point for a design based on the pattern (think of it as a proof-of-concept intended to just illustrate the basic concepts of the pattern). As has been alluded by myself and others, most who have taken the time reason about and design production-quality singletons have seen the shortcomings of a simple GOF implementation and have expanded upon it to introduce more robustness and configurability to the pattern.

  3. Andrea Zanchi says:

    Hi, so what would be the solution? Not using singletons? Strange, since Spring framework (to name one) manages singleton beans by default. This means EVERY instance injected will always be a singleton unless you declare it.
    Is Spring DI built on a ton of crap? I don’t think so, rather we have to follow the SOLID principles.

    1. Same with Angular. In fact the use of services (a singleton) is one of the reasons it is so testable.

      If your only tool is a hammer, every problem looks like at nail.

  4. No one cares and still go on using this pattern when it fills in.

  5. Eric Shaffer says:

    While Singletons are not the answer to every circumstance, they are very useful for things like shared configuration or external interfaces that might be used in multiple parts of an application.

    If you go with the approach of passing the instance around via parameters, you start polluting your other classes with a dependency on those interfaces which makes it much more difficult to reuse the code in other applications. You also have the added problem of storing lots of references to that object and if you ever need to change out that instance while the application is running, you can never be certain you will get all references updated cleanly. I would also recommend a way to clear the instance (static method) so you can force it to instantiate a new instance.

    Regarding the ability to write unit tests, a very simple way to work around that is to change the private _instance and constructor to protected and make as many of the properties and methods as you can virtual. You can create a subclass of the Singleton (with its constructor protected too) with its own way (static property or method) to create an instance of that specific subclass and sets the base class instance to that subclass instance. The subclass can then override any of the behavior you don’t like during a unit test (e.g. file IO, faking a call to an external interface, …).

    1. Patrick Smacchia says:

      “If you go with the approach of passing the instance around via parameters, you start polluting your other classes with a dependency on those interfaces which makes it much more difficult to reuse the code in other applications”

      Every classes that uses the singleton already depends on the interface ***anyway***.
      You can easily avoid the “parameter passing pollution” by injecting the singleton object in the inner classes ctor.

      new ClassThatDoesntDependOnSingleton(new ClassThatDependsOnSingleton(singletonObject))

  6. R. T. Lessly says:

    A singleton is a form of dependency injection. In the case of the logger, if you wished to test without the “side-effects” of logging you can just have the Logger singleton instantiate a NULL logger (i.e., a logger that does nothing). Practical singleton implementations (at least the ones that I write) should include some measure of configurability, e.g., identifying the concrete instance of the singleton in a config file or registering it during program startup. This is no different than how most dependency injection frameworks work. Even DI frameworks usually offer a singleton as one of their object creation options. A well-designed singleton class provides many of the same benefits as DI without the overhead of a DI framework.

    The statement in your article “BuildSimpleOrder now does something completely unrelated to building a simple order — it writes to a file.” is only partially correct. The thing that is completely unrelated to building an order is the idea of logging itself. The fact that it writes to a file (or does anything else for that matter) is a side-effect of the fact that you have created a dependency to Logger in the first place – an class that is totally unrelated to, and unnecessary for, the creation of an order. DI does not remove that dependency. At most, it provides a mechanism that may allow you to more easily nullify the effects of that dependency. But, as I said above, the same could be done with a well designed singleton.

    To better alleviate that dependency you could enlist the help of AOP. But even that is only a partial solution as AOP can only inject its behavior at certain well-defined points and does not support ad-hoc actions (e.g., writing log entries at any point in the code).

    One issue that I have with DI is that it can create interface “pollution” in your code, especially when being used as a substitute for singletons. I’ll explain what I mean by building off your logger example. Logging is a necessary fact of production quality code, so you are going to have some sort of dependency on a logger class or interface no matter what. But you want your logging to be as out of the way as possible so that it doesn’t get in the way of what your code is trying to do. To me, the idea of passing logger instances around as method arguments (one method of DI) clearly violates this goal – “why do I need to pass a logger instance just to build a simple Order instance?”. Even as constructor arguments or properties, it still “pollutes” the interface of a class by introducing something unrelated to what the class does. On the other hand, using a logging singleton minimizes this pollution by only introducing the Logger instance at exactly the point in the code where it is needed and no where else. This allows you to create cleaner code with a tighter, more coherent interface, lower overhead, and less boilerplate code.

    I am not opposed to DI, AOP, or Singletons – I have them all in my toolbox and use them as the situation dictates. I am always careful – and make sure my development team is also careful – to use such techniques appropriately. I don’t disagree with the idea that singletons can be abused, but that is true of almost anything. The example you cited regarding passing a flag via a the Logger singleton is an egregious abuse, to say the least, and an idea that the Lead Developer or Architect should squash immediately.

  7. Sander Pool says:

    Your arguments would carry more weight if you proposed a solution at the same time. You show a case where a singleton solves a common problem (you really do want a single logger for your application) and then argue why this is bad. Truly the argumentation wasn’t very strong to begin with but it falls apart because you don’t propose how to add logging to the Order class. Are you saying the solution is to not log in this case? It seems that is too costly in this case.

    Really the use of singletons has nothing to do with the fan-in and fan-out problem you illustrate. Nothing you said is caused by the fact we have a single global instance of the logger object. If the code had created its own logger instance it would still be a mystery what this object did. This is true for all re-use of APIs, be they functional or OO. What are we going to do, not use libraries at all? Are we only going to test ‘pure’ code that does not contain any calls to external code? Of course not. And stubbing out calls to external code is not a solution either because then you are no longer testing the code as it will run once integrated.

    It seems your real issue is that you feel testing is difficult. Luckily it is as it has paid my bills for a very long time. If the code under test requires being able to write to a file for logging then the test environment should let it. If that means that you need a dedicated test environment where these tests can run then so be it. I’ve written unit tests that require extensive external equipment setups to run. Without that equipment the tests would be meaningless and stubbing out those calls would make the tests meaningless. It also means the tests won’t run in a VM on a disconnected laptop somewhere. That is the cost of doing business beyond academic examples.

    Anyway, I digressed a bit, sorry. If you could explain a solution for your scenario above I would appreciate it. I don’t use singletons often but loggers are a good example of where they are useful. If there is truly a better way to do that I’d like to know about it.

  8. You need a better example than your BuildSimpleOrder(). If it is built to spec then logging is part of what it is supposed to do. If it is legacy code written long ago then you would be remiss to not look it over in order to determine what it actually does, never trust legacy code.

    A better example would have been simply something where the application was maintaining global state. A singleton logger can easily be created so that it can be configured and it certainly can be created so that its only function is logging. The problems you relate to the logger are more commonly found with a global memory singleton, singletons used in that fashion always grow to encompass more responsibility.

    Additionally, if you are working on code where random unrelated functionality has been added to a logging singleton then you have much bigger problems than the logging singleton, the creators of that code have taken away your paddle and your canoe.

Comments are closed.