8

I have a medium-sized python program (~5000 lines of code), which I've built up over time, with no particular plan as I went ahead. The architecture I've ended up with consists of 5-6 large Singleton objects, each of which handle a certain aspect of my program, such as database comms, a web-server, data gathering client, and internal calculations.

I feel that using several singletons like this doesn't really make use of the true benefit of OO-programming, namely that it easily allows you to create numerous related objects. My singletons are also fairly dependent on each other, and if I grew the program a lot (say ~50 000 lines), I can see my approach becoming spaghetti code.

So I'm wondering if there is a better way of structuring my program. E.g. should my singletons actually be separate modules? But then how would I approach the attributes that are very neatly organised in my singleton objects? Are there other architecture options?

I'm not very familiar with design patterns (the GOF book is on my todo-list) so perhaps there are design and/or architectural patterns that would be better for my program?

funklute
  • 201
  • 2
  • 4
  • 2
    See [I've inherited 200K lines of spaghetti code — what now?](http://programmers.stackexchange.com/q/155488/6605), although your question is not a duplicate. – Arseni Mourzenko Oct 14 '15 at 08:38
  • 1
    I don't think singletons are your problem. Rather, it's probably convoluted interdependencies and needless statefulness. – usr Oct 14 '15 at 11:37
  • 2
    5000 lines is not really much, even in Python. I would call it "small", actually. I love small programs, so there's nothing wrong with that. Anyways: Ask yourself which effort is higher: Refactoring or Rewriting? – phresnel Oct 14 '15 at 12:24
  • 2
    You should also add [_Working Effectively With Legacy Code_ by Michael Feathers](http://c2.com/cgi/wiki?WorkingEffectivelyWithLegacyCode) to your reading list. He talks about many ways to safely break dependencies (including on singletons) to make your code more testable and more maintainable. – cbojar Oct 14 '15 at 12:56
  • @phresnel Calling 5000 lines of Python "medium-sized" seems entirely reasonable to me. For comparison, the web framework Django is only a couple of hundred thousand lines, and you can (and I frequently do) write scripts to do interesting and useful things in a few dozen lines. By orders of magnitude, a 5000-line application lies halfway between those extremes. – Mark Amery Oct 14 '15 at 17:06
  • @MarkAmery: Yes, there's no definition of "small", "medium" and "large". And there is not really a meaning in "lines of code". Anyways, personally, I can write decent code at a pace of 1000-10000 lines on a single weekend. How many weekends is "medium", how many weekends is "large"? If the Kernel Linux is large with about 10MLoC, does "medium" become 0.5MLoC+? I don't really know. But I don't think a program worth 200 EUR in pure working hours is medium; I could buy many medium-sized programs and full rights each month, then. But that's just my opinion. – phresnel Oct 19 '15 at 06:41

2 Answers2

23

Singletons are considered 'evil' by many, and while a singleton pattern has its uses they are few and far between... I've worked with several large codebases and have pretty much always managed to move them away from singletons.

The easiest way to eliminate singletons is:

  1. Introduce an interface on top of your singleton: this allows you to separate the contract from the implementation and reduces the coupling you have to the actual implementation
  2. Provide a setter on your singleton so you can set the singleton instance from a unittest: this allows you to 'swap out' the singleton instance in a unittest.
  3. Create unittests on the class you want to change: using a mock of the interface introduced in 1 and the setter introduced in 2 you can now write unittests on the class.
  4. Inject the singleton instead of getting it statically: in the class that depends on your singleton, have a way to inject that singleton (via the interface created in 1). You can use constructor injection, method-based setters.... Whatever works for you. This is the first time you actually touch the class you are refactoring! The tests introduced in 3 will help you refactor without breaking anything

You can avoid step 2 if you want to do it 'all in', but this approach is the most pure in the sense that you avoid changing any classes that do not have tests to check that nothing breaks. Personally, I find this to be a bit overkill but YMMV. The basic premise is that you move towards a class that has dependencies injected instead of fetched statically.

Also, even IF you have legitimate uses for a singleton you should STILL inject them. Singletons are about ensuring that only a single instance of a given class exists. The classic pattern with a globally-accessible static function achieves this, but unfortunately also moves us away from injecting that instance due to bad example code floating about the web. There are plenty of frameworks out there that can handle dependency injection for you and most of them will have a way to configure an object so that the same instance is reused throughout the application, effectively making this a singleton.

JDT
  • 6,302
  • 17
  • 32
11

Fear of singletons is greatly exaggerated. If you do need reliably, exactly one of several large constructs, there's nothing wrong with having them. (If you'd like to write static modules instead, and the language doesn't let you, then that may be a problem with the language, not your program.)

A common argument against singletons is that they are hard to mock for unit testing. But that applies only if you hardcode their class in your code. The singleton pattern is really concerned with making sure that only one instance of a specific class exists. No one stops you from having several implementations of a service interface, each of which is a singleton, and swap/inject/dynamically load them as needed for business and testing purposes.

Kilian Foth
  • 107,706
  • 45
  • 295
  • 310
  • 1
    Most often when I see a singleton being implemented there isn't a need for having exactly one instance. For example it doesn't matter how many DAOs I have as long as they all do participate in the same transaction. That seems to be what many people don't realize when they implement - needing one instance is different than having at most one instance. – SpaceTrucker Oct 14 '15 at 15:22
  • 3
    "Fear of singletons is greatly exaggerated" is simply untrue. As someone who has suffered many hundreds of hours of hair-pulling frustration when trying to test code with singletons in it, I can state categorically that it's hard to overstate just how bad singletons are in almost every context. Even your "making sure only one instance of a specific class exists" comment misses the point. If I have two tests that run in parallel, I might need two *different* instances of the class simultaneously. It's depressing that, in 2015, such bad advice still gets so many up votes. – David Arno Oct 15 '15 at 15:07
  • The problem with singletons, just like any other programming concept, is not that they are just flat out bad, it's over using them that's bad. There is nothing wrong with having a small handful of them as long ad they are properly maintained. – Jason Crosby Oct 21 '15 at 04:24
  • @JasonCrosby, it matters not whether you have 1 or 1,000 singletons in your code. What matters is whether any of them have state and thus hinder ease of testing. That is what makes them "flat out bad" anti-patterns . – David Arno Oct 21 '15 at 21:03