The Singleton Pattern: To be or not to be [used]?

Started by
79 comments, last by 21st Century Moose 11 years, 5 months ago

You're right, people in the games industry can't design code, generally, but more importantly they don't. There is good reason for this. If you're writing software that gets men to the moon then:

- Get the best programmers available.
- Define a rock solid coding standard.
- Design every last operation detail of the code before writing a single line.
- Code review, code review, code review.

Unfortunately very often none of these rules apply in the games industry. The only time i've ever seen code designed to any kind of level in the industry was when a new boss of mine tried to enforce it but ended up having to drop it. There simply isn't time in the games industry because:

- You're usually working through shorter development iterations.
- Management and investors these days want to see progress on your project more frequently than they used to. (related to the above)
- Your title is out in a year and no time can be afford to be "wasted" on writing design documentation that, as soon as the game hits the shelf, is irrelevant.

I will say, however, that designing code and having good OOP has its merits. I'm not trying to say that noone in the world should be writing OOP, but in the games industry and to be honest, even more importantly for the hobbyist, don't waste all your time wondering how to add something to your perfectly architected system.

The more time you spend working out how the hell you're going to integrate physics into your engine the more time you're not spending integrating physics into your engine. You have to find a good balance between writing a codebase that will be productive to work on and also decently written and unfortunately for C++ and OOP allow you to code yourself into a corner too often.

I'm not disagreeing with you here, i understand the necessity of OOP in software engineering. I'm just not convinced the games industry is a suitable domain for it. I've been full circle with writing game engines in my personal time and my current one is my most productive. Gradually i'm migrating it over to plain C with a few C++ niceties like templates here and there.


While historically this is certainly true, I think our industry is maturing to the point where we can start to evolve out of the "cowboy coding" mentality that marked our formative years. Consider MMOs, for example -- you can't expect to build a successful MMO with poor engineering practices. You are building a service more than you are building a product you can ship and forget, a service you'll need to maintain and extend for a decade (or more, hopefully). Sure, MMOs as we know them today will eventually become passe, but some of their core elements, such as massively-connected (or "social," as the kids like to call it days) systems are probably going to embed themselves in the culture of our products for a long time. Even primarily single-player games are starting to integrate those kinds of features.


At ArenaNet, for example, we do a very good job of hitting three out of four of your bullet points. Our automated testing and test tools can be greatly improved (and we're working on it actively), but we take hiring extremely seriously, do up-front design of new systems and RFCs for changes to old ones, and have a very rigorous set of coding standards. We've developed these polices because we have to actively maintain two services -- Guild Wars 1 and now Guild Wars 2 -- for the foreseeable future and now, post-launch, we don't have the luxury of a five-year development cycle to get updates and patches out in to the live environment, so we've had to make sure these practices are followed reasonably during development so that they become habitual and we still employ them even when scrambling to patch an exploit or bug. If we don't, we're going to be building a house of cards for the next seven years -- the failure of which could basically cost the company its existence.

To your last point about writing more "C-like:" I don't think that's actually counter to the idea of writing code that espouses good (possibly OO) design principles. Such code tends to be simpler and consequently easier to read and maintain, and can still involve things like clear responsibility segregation, implementation hiding, et cetera. Much of our code at work is, for example, very C-like in its straightforwardness, and I do the same in many of my own hobby projects. Clever, complicated template metaprogramming hackery and overzealous design-pattern boilerplate implementations that adhere dogmatically to the Rules of Proper Design are, practically speaking, the domain of the academic, the "I wonder if I could..." masturbatory excursions that serve mostly to stretch the language and boost the ego of the author. Certainly, interesting innovations come from such endeavors, but increasingly I find must of them inappropriate for production code on any kind of scale.
Advertisement
These are some things that I've been mulling over myself as I learn more and more about 'proper' coding. I can definitely see the benefits of all the rules and customs developed over the years, but it also strikes me that these things should be measured carefully. If I'm going to write something that needs to last for years to come then I had darn well better "do it right" because in the long run the amount of time saved will be enormous, but on the other hand I see things like "don't use C style things in C++" and in many cases I find myself asking "why the hell not?". It strikes me that rules or customs should be measured based on their effectiveness for the task at hand. In many (even most) cases there's good reasons, but sometimes an array is just a gorram array and using a vector is a waste of time both for the developer and for the CPU at runtime. If I need STL container functionality later it's not difficult to add.

A somewhat related issue, since you mentioned cowboy'ing, if I'm going to write something complex I'll usually sit down and bash it out as quickly as possible and then once its in place I can spot all the structural problems and go back over it (immediately) and write a stable system with all the bells and whistles. I don't want to get stuck spending extra hours writing something that's going to get scrapped or re-written anyway because a better design idea occurred or a structural problem emerged while the code was being written.

I guess what I mean is that I support the K.I.S.S. rule above all others. If something looks and feels complicated then I can't shake the feeling that I'm doing it wrong. I don't understand making something more complex than it needs to be, especially since it seems like complexity is easy to add and hard to remove. Sometimes I feel like the rules and culture are almost a language in and of themselves. It's like C extends to C++ and then C++ extends to 'proper' C++. I've seen criticism about using C++ as if it were C, but the fact is that vanilla C is an effective language as well. In terms of readability C-style is great, but I also find that it seems like the program likes it as well, if that makes any sense. Simple code just usually seems to run better. Algorithms, for instance. I ws looking into algorithms while familiarizing myself with the musty corners of STL and I saw that I could have used an algorithm in place of a loop for iterating through the values in a container and modifying them. Then, by chance, I looked at the code of the appropriate algorithm and saw that it was more or less exactly what I had already written. So all I would gain by implementing the loop with the algorithm is some excess stack framing. On the other hand, there are some situations where algorithms are more or less '****ing rad', to use the technical term, and in those places I gladly use them.

I don't really know where I'm going with this rambling. Like I said, it's just something that's been on my mind. Glad to see other people thinking about it as well.
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
It strikes me that rules or customs should be measured based on their effectiveness for the task at hand.[/quote]

**They are.**

"Don't use C style things in C++" is one of those things that has been done and found to cause issues due to odd interactions in the mixed codebase. It's one thing to question things, but many of these best practices are best practices for just this reason.
Probably the biggest issue with doing things the C way in C++ code is that C++ has exceptions. Some perfectly good C code compiled by a C++ compiler can become unsafe because all of the sudden you can't be sure that call to foo() won't unwind the stack on you (and even if it can't throw an exception now that doesn't mean someone won't change things later). Hence the recommendation to use things like RAII types. Following closely on that is the fact that many of those straight forward C style techniques are actually less secure than the C++ style equivalents.

METADATA:

There are many times when a program needs to store and reference data that is a result of processing and that can be used again. How will you store metadata? I like to use singletons for structural/constant data/flags/metadata(temporary) instances, when the complexity of creating new instances and having it loading its data from something/somewhere doesn't worth as simply acessing it straight like cache of processors is used and Windows Registry (always in memory).
I just use an ordinary class for this. And if at some point i need multiple different resources, I can just do that. If i don't, I don't. That's the point: LIMITING YOURSELF doesn't help you later. It will BLOCK you later. Without ever having given you any gain. You want just one: Instanciate just one. You want global access? Make it a global. Don't try to hide it behind some fancy terms. Be honest to yourself.
If that's not the help you're after then you're going to have to explain the problem better than what you have. - joanusdmentia

My Page davepermen.net | My Music on Bandcamp and on Soundcloud

You want just one: Instanciate just one. You want global access? Make it a global.

This is the right spirit. Avoiding useless extra instances of objects and getting the useful objects where are needed are two interlinked rather difficult problems; rather tragically, progressing on the former makes the latter harder.
Good solutions require good design and forethought, and often tedious code, for example lots of parameters and variables to pass objects around or checks of the presence and validity of objects that might or might not have been initialized, not to mention (in many cases) understanding and accepting the price of global variables: obviously the average mediocre programmer is attracted by singletons because

  • Singletons look clever and legitimate (a tie-wearing design pattern!) without entailing actual design effort. If it shouldn't have been a singleton, you'll find out when it's too late to change your mind cheaply, because you didn't consider different options and contingencies at the proper time.
  • Accessing singletons allows a strong subconscious denial (or intentional camouflage) that you are using a global variable, and a delusion that what you are doing is technically better (or at least more enterprisey).

Omae Wa Mou Shindeiru

enterprisey


Increasing visibility is a good thing, and it will surely make us more... visible. To do that we should, uh... develop our strategy and strategize our development. Implement solutions and solutionize implementations. Aggressively.

Jake1.gif

Edgy?
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

[quote name='davepermen' timestamp='1352645783' post='4999916']You want just one: Instanciate just one. You want global access? Make it a global.

This is the right spirit. Avoiding useless extra instances of objects and getting the useful objects where are needed are two interlinked rather difficult problems; rather tragically, progressing on the former makes the latter harder.
Good solutions require good design and forethought, and often tedious code, for example lots of parameters and variables to pass objects around or checks of the presence and validity of objects that might or might not have been initialized, not to mention (in many cases) understanding and accepting the price of global variables: obviously the average mediocre programmer is attracted by singletons because

  • Singletons look clever and legitimate (a tie-wearing design pattern!) without entailing actual design effort. If it shouldn't have been a singleton, you'll find out when it's too late to change your mind cheaply, because you didn't think consider different options and contingencies at the proper time.
  • Accessing singletons allows a strong subconscious denial (or intentional camouflage) that you are using a global variable, and a delusion that what you are doing is technically better (or at least more enterprisey).

[/quote]

What's the price of a global variable?
Welcome to 1973.
http://c2.com/cgi/wiki?GlobalVariablesConsideredHarmful
http://c2.com/cgi/wiki?GlobalVariablesAreBad
What's the price of a global variable?


Loss of access control. (Also violation of dogma.... j/k)

Globals can be reached by parts of the program that have no business reaching them and can result in serious design problems later on. It's a matter of whether or not you're working with people that can't handle the responsibility. Sadly this is a lot of people, from what I hear. Once things get complicated it can become nearly impossible to have reasonable state expectations for something with global accessibility.

That's a good article you got there, Hodge.
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

This topic is closed to new replies.

Advertisement