5 ms·
My gripe with global state (i.e. singletons) is not testability, maintainability nor correctness. It's nothing more than bad object-oriented design, or lack the
by yuvadam 15y ago
My gripe with global state (i.e. singletons) is not testability, maintainability nor correctness. It's nothing more than bad object-oriented design, or lack thereof.
I've seen this time and again - even this week, as I am doing some major refactoring in old code - singletons are used exactly where someone was too lazy (or too busy) in order to do a proper design and review. I cannot count the number of proverbial SomethingManager singleton classes I've seen.
At most, singletons should be used in a single point of entry to a module. From there on, even if other classes should be used once, their singularity will be derived from that single point of entry that contains them.
Rule of thumb - stop using singletons. In 99% of the cases you will see a much better design exists.
- jriddycuz 15y agoIsn't it bad design because it hinders testability, maintainability, and correctness? In general, whether we are talking about procedural, OO, functional, or whatever kind of programming, code is easier to reason about and test and use if the effects of its individual units (functions, classes, modules, &c.) depend largely on the inputs to them. In functional languages this is called "referential transparency," but its value applies to OOP as well. Using things like singletons creates hidden dependencies on things that can change over the lifetime of the program without those changes being made obvious to clients of that unit of code. Good design nails things down to explicit parameters.
- Zak 15y agoCalling something bad design without discussing the actual problems it causes is mere name-calling or unsupported opinion. I submit that if you can't make a claim that something is hard to maintain, unreliable, hard to understand, incorrect, fragile, etc... then it is not bad design. The claim that it is "bad" because it doesn't fit in to a certain paradigm is at best a valid claim that it is inconsistent with the rest of the codebase and therefore hard to work with. At worst, it's cargo-cult engineering; doing something a certain way because it's said to be "good" without understanding why.
- nostrademons 15y ago"I submit that if you can't make a claim that something is hard to maintain, unreliable, hard to understand, incorrect, fragile, etc... then it is not bad design." That's not really true either - it just means that the claimant probably lacks the experience to recognize whether or not it's bad design. Even people who are right for the wrong reasons can still be right.
- Zak 15y agoThat's a valid point, but in that case the claim is no more reliable than random chance.
- ebiester 15y agoSince you handwaved over "a much better design exists," I thought I'd give an example. So, you have an app deployed to seven customers. The typical pattern is you develop an app for a first customer. It is then sold to subsequent customers, doing the least possible to make it work. In this case, as I have seen in the past, you have a properties file, saying which classes to use and which configuration options the customer has available. These are then loaded up, as global state, so that your code can ask, "Does this application have Widget Processing capabilities?" and gets the answer for its code path. Consider: if (AppProperties.getInstance().hasWidgetProcessing() { process(widget); } Now, the problems here are pretty obvious. Since you're depending on a single file, your testing is a bear. Getting to the various codepaths is nigh impossible. Ideologically, it's bad design because your software has a bunch of if or switch statements in the code to decide what it should really do. Now, what would make this code better? Well, you could start with taking all your Widget Processing capabilities and putting them in their own object or package. Then, instead of the above, your code looks like this: public class WidgetImpl implements Widget { private WidgetProcessor widgetProcessor; public void process() { widgetProcessor.process(this) } public void setWidgetProcessor(WidgetProcessor wp) { this.widgetProcessor = wp; } } In your main code: widget.process(); WidgetProcessor has two (or more) implementations, in this case: RealWidgetProcessor and NullWidgetProcessor. NullWidgetProcessor just returns without doing anything. Now, how does your Widget get the proper widget processor? Dependency injection. http://code.google.com/p/google-guice/wiki/Motivation?tm=6 http://code.google.com/p/google-guice/wiki/Motivation?tm=6 has a pretty good explanation. Improvements on the design are welcomed, as always. :) Though for simplicity's sake I was trying to keep code to a minimum. Now, speaking of code, my main issue with dependency injection is that the code baloons, especially in java. DI as a pattern makes me feel like there's something missing in current language implementations that would make this more elegant. Aspect-oriented programming was one try at it, but it seems that the cure is worse than the disease in that case.
- ebiester 15y agoI should mention that another alternative is to write a domain specific language, which is what a Lisper or Rubyist would advocate. That way, you put together each instance like play-doh. (a la http://weblog.jamisbuck.org/2008/11/9/legos-play-doh-and-programming http://weblog.jamisbuck.org/2008/11/9/legos-play-doh-and-pro... )