Globals

Started by
12 comments, last by Stainless 10 years ago

Some time ago I worked on a (professional) project in which I was surprised to see quite a lot of globals. There were classes like CRender, CPhysics, CClient and so on, and there was file globals.h that tied together all these classes by declaring GRender, GPhysics, GClient. Now, the entire code relied upon these globals and often even a small change in gpu texture class header caused recompilation of a big part of the codebase (which was medium in size but the total recompilation time was around 1 min).

We've heard all these stories about globals being bad and personally I prefer to write code a different way but I must say that working with all these globals was very easy - where ever I was in the code I could always easily access the client's, render's and so on, properties.

So, what's your opinion?

Advertisement

Instead of bare globals, I prefer the service locator pattern. This prevents recompiling a lot when making changes. But I personally prefer passing the objects via parameters, or on construction. Or, when for example all deratives of the class object need access to some object, create a protected method getSomeObject(). Simply using globals is not easy to maintain as you said, so I should avoid them where possible.

I agree however that easy access via for example globals can be nice to program with, that's why I sometimes use a service locator where it isn't necessary, but only for small projects.

Didn't know the locator pattern before. Sounds like a nice idea although... in itself it needs a global variable :). Or be implemented as a singleton. But hey, can't we just implement CRender, CClient and CPhysics as singletons in the first place? Instead of declaring them global why not just have them declared in their respective files?

Global variables represent an implicit hidden dependency.

As such they add complexity everywhere you use them. Do it poorly at your own peril.

Didn't know the locator pattern before. Sounds like a nice idea although... in itself it needs a global variable :). Or be implemented as a singleton. But hey, can't we just implement CRender, CClient and CPhysics as singletons in the first place? Instead of declaring them global why not just have them declared in their respective files?

A singleton means "There can only be one, ever, and it is absolutely mandated and enforced".

A global variable means a shared, well-known instance, but you can still create more if you want.

Don't confuse the two.


Singleton is an anti pattern. Don't do it. Generally don't talk about it except to tell people not to do it. Someone mentions "singleton" and a massive flame war erupts about if they are pure evil, a minor evil, and if maybe some case might be an acceptable evil. Lets not do that.

For your examples, a singleton renderer breaks all kinds of visual effects, breaks several kinds of features like potentially picture-in-picture or certain multi-pass effects or cases where you want a second rendering for your own purposes. A singleton client means you cannot have a second client for any reason, not even as an AI player or a network player or as a stand-in for testing. A physics singleton means you cannot run a separate private physics simulation on any tiny thing for any reason.

When you write a singleton you are almost certainly doing something wrong.

Usually you just want a well-known instance.

Logging is oft-cited as a candidate for a singleton; it should instead be a well-known instance since making it a singleton means I cannot create my own private logging functionality for a class or object instance. Application variables like command line parameters and directory paths and such is sometimes cited as a singleton, but that prohibits me from making my own version for any other purpose, such as custom tools that use the engine, so a well-known instance is preferred. Even a "service locator" system that finds the well-known instance should not be a singleton since it limits me from making my own if I need it; use a global variable, not a singleton, so I can create another if I need it.

If a game is carefully designed and rules are followed, you can have global objects that work okay. By themselves they are not necessarily fatal to the implementation.

If you do decide to share things through globals, you must be absolutely strict about what systems can modify it, when they can modify it, and about what systems can read from it an when reading is prohibited.

Failure to enforce strict controls on global variables leads to all kinds of incredibly nasty bugs, which is why they are called "evil" and should generally be avoided.

Almost every time I am lazy and make something a global, I end up eventually needing to make it non-global.

I'm also not a big fan of the service locator pattern, though I do use it in places. It makes it hard to understand the dependencies, and you can only have one of a particular service.

Best to just declare/pass down dependencies explicitly in most cases.

I can think of two problems with using globals that haven't been mentioned:
(1) Using globals makes it hard to reason about your code. If you want to make some change to a global object, you may end up having no idea how many parts of the code will need to know about the change. If the change is a change in semantics, even if a small one, it could be hard to convince yourself that it won't break anything, so now you have to test it, and chances are your tests will miss something.
(2) Using globals makes it hard to reuse some part of the code in another project. You never bothered to define a clean interface for your Physics module, so now if you want to use it somewhere else, you may have to spend a lot of time disentangling it from other parts of the code that it really didn't have any business using (the rendering system because there was a ray-scene intersection facility there, or the sound system in response to things hitting each other...), or you'll end up importing the whole code to the new project, even if it doesn't really need to render anything or make any sounds.

I fight with (1) at work. Our most important program has one global god object with a fairly generic name (something like `Game' if it were a video game). Any part of the code can call a global function to get the one instance of the god object. It is not a singleton, but in practice it is impossible to have more than one of these monsters because the code assumes a single instance in so many places (static local variables in some methods, using other global objects...). There are parts of the code that are so complicated that nobody really knows how they work or what they would do in particular scenarios, but nobody dares change them, because you would have to think about how every other part of the code would be affected by your changes, and we are talking about millions of lines of code.

I had a serious case of (2) with my chess program in the 90s and it was a horrible experience, but I learned to value having clear interfaces between parts of the code. Ideally you can take parts of your code and treat them as libraries, with well-defined APIs, some documentation, and maybe even their own versioning. Even if you never end up using one of these libraries in another project, the one project where you do use it will end up being easier to work with because you defined clear interfaces.

Didn't know the locator pattern before. Sounds like a nice idea although... in itself it needs a global variable :). Or be implemented as a singleton.

Or it can be not-evil and be neither of those.
If it's needed from program startup until shutdown, then make it a local variable inside main.

Globals are fine if you're writing a simple example, or if you really don't care at all about the long-term maintainability I the code. If the code is being rushed together to ship some product, and then will be thrown away, never to be touched again, then hacky globals are fine.
Otherwise, decades of software engineering practice and study has produced whole books on how to write software that is actually reusable and maintainable, and they all involve some kind of compartmentalization, or: not using globals! :P


I must say that working with all these globals was very easy

That's kind of the problem in a nutshell though. Its so easy, you end up doing in all sorts of places that later on you'll wish you hadn't.

I like the fact that its hard to access my physics code from within my renderer, because it stops me cutting corners that will bite me later on.

Multithreading with globals is also a very dangerous thing.

If all your data resides in isolated blobbs of object instances, you can be very sure that you can churn through each blobb in a seperate thread without any risk of race conditions. But if you use globals, you have that single data point that is accessed by all blobbs which prevents you from easily parallizing stuff.

I don't think the ease of access is worth the messy code you'll wind up with when the project gets rolling. Granted it can be a pain when you have to go back and edit your constructors to pass in variables you forgot about earlier, but things make so much more sense when only having access to what you need.

Your authority is not recognized in Fort Kick-ass http://www.newvoxel.com

This topic is closed to new replies.

Advertisement