[In this reprinted #altdevblogaday-opinion piece, CCP's lead technical artist Rob Galanakis rails against the "nefarious criminals of the software industry," global states that mingle with the rest of your code unnoticed.]
I've ripped off this title from a common trend on Raymond Chen of MSFT's blog. Here are a bunch of posts about it
I can scream it to the heavens but it doesn't mean people understand. Globals are bad
. Well, no shit Sherlock
. I don't need to write another blog post to say that. What I want to talk about is, what is a global.
It's very easy to see this code and face-palm:
global spam = list()
global eggs = dict()
global lastIndex = -1
But I'm going to talk about much more sinister types of globals, ones that mingle with the rest of your code possibly unnoticed. Globals living amongst us. No longer! Read on to find out how to spot these nefarious criminals of the software industry.
There are two classes of environment variable mutation: acceptable and condemning. There is no 'slightly wrong', there's only 'meh, I guess that's OK', and 'you are a terrible human being for doing this.'
- Acceptable use would be at the application level, where environment variables can be get or set with care, as something needs to configure global environment. Acceptable would also be setting persistent environment variables in cases where that is very clearly the intent and it is documented. Don't go setting environment variables willy-nilly, most especially persistent ones!
- Condemning would be the access of custom environment variables at the library level. Never, ever access environment variables within a module of library code (except, perhaps, to provide defaults). Always allow those values to be passed in. Accessing system environment variables in a library is, sometimes, an Acceptable Use. No library code should set an environment variable, ever.
See everything about Environment Variables and multiply by 2. Then apply the following:
- Commandline use is only acceptable at the entry point of an application. Nothing anywhere else should access the commandline args (except, perhaps to provide defaults).
- Nothing should ever mutate the commandline arguments. Ever!
get slightly (or more than slightly) offended when people call the Singleton a 'pattern.' Patterns are generally useful for discussing and analyzing code, and have a positive connotation.
Singletons are awful and should be avoided at all costs. They're just a global by another name -- if you wouldn't use a global, don't use a singleton! Singletons should only exist:
- at the application level (as a global), and only when absolutely necessary, such as an expensive-to-create object that does not have state. Or:
- in extremely performance-critical areas where there is absolutely no other way. Oh, there's also:
- where you want to write code that is unrefactorable and untestable.
So, if you decide you do need to use a global, remember, treat it as if it weren't a global and pass it around instead (ie, through dependency injection). But don't forget: singletons are globals too!
Module-level to you pythonistas, static to your C++/.NET'ers. It's true- if you're modifying state on a static class or module, you're using globals. The only place this ever belongs is generally for caching (and even then, I'd urge you to reconsider).
If you're modifying a module's state- and then you're acknowledging what you're doing by, like, having to call 'reload' to 'fix' the state, you're committing a sin against your fellow man. Remember, this includes stuff like 'monkeypatching' class or module-level methods in python.
The Golden Rule
The golden rule that I've come up with with globals is, if I can't predict the implications of modifying state, find a way not to modify state
. If something else you don't definitely know about is potentially relying on a certain state or value, don't change it
. Even better, get rid of the situation
. This means, you keep all globals and anything that could be considered a global (access to env vars, singletons, static state, commandline args) out of your libraries, entirely
The only place you want globals is at the highest level application logic. This is the only way you can design something where you know all the implications of the globals, and rigorously sticking to this design will improve the portability of your code greatly.
Agree? Disagree? Did I miss any pseudonymous globals that you've had to wrangle?
[This piece was reprinted from #AltDevBlogADay, a shared blog initiative started by @mike_acton devoted to giving game developers of all disciplines a place to motivate each other to write regularly about their personal game development passions.]