Are they really? I agree that global variables are horrible in a language like C where they are truly “global” because of the lack of namespaces. But in a language like Python, they are tolerable because they are not truly global — they are restricted to the particular module. I am not saying they are great, but they are not as horrible as they are in, say, C. Of course, care must be taken to ensure that they are abused, but that is true of any programming construct.
If module global variables are considered truly horrible, the same can be said about instance variables inside classes. They are as good (or bad) as module globals in the sense that they represent shared state and can potentially be mutated by any method/member function in the class.
Of course the functional programmers among us will say that shared state of any kind is bad, and I agree. But we are not talking about that here, are we?
Some modules are unlike others. Try setting `sys.stdout` in one extremely local function, observe ripples everywhere. It's not common, fortunately; hacking `sys.path` is more common, and is sometimes done at import time.
With all its upsides, Python has a problematic global state story; it's easy to shoot yourself in the foot.
I am not saying that python globals are a great idea — just that they are not as horrible as they are in case of C and they they do have their uses — they just need to be used carefully. Of course, python makes it impossible to truly hide anything. In an ideal world, module level globals won’t be mutable outside the module.
> If module global variables are considered truly horrible, the same can be said about instance variables inside classes. They are as good (or bad) as module globals in the sense that they represent shared state and can potentially be mutated by any method/member function in the class.
Assuming Python-like modules, module-level globals are worse for encapsulation.
1. A function can access module-level state without having been explicitly passed a reference to it.
2. You can't create additional instances of module-level state. For example, if your app server uses a module-level global for the server listening socket, you can't run two app server instances in the same process.
> You can't create additional instances of module-level state. For example, if your app server uses a module-level global for the server listening socket, you can't run two app server instances in the same process.
So a global variable should not be used for such cases. That doesn’t mean it is inherently bad. People writing the code are supposed to think about what constructs to use for a given scenario.
You said that instance variables were just as bad as module globals. I was pointing out cases (w.r.t. software engineering, obviously, since that is what this thread is about) where module globals are worse.
Yes, programmers have to be aware of what they're doing, but that's true of any programming construct. It doesn't mean that we shouldn't identify the dangers.
Mutable instance variables are much less bad because their scope is much more limited: you can much more easily scan all the code that mutates them and check that invariants are upheld etc.
Global mutable variables are also awful for multithreading. Thread-locals help, but even they breaks horribly as soon as you use something like a thread pool.
> Mutable instance variables are much less bad because their scope is much more limited: you can much more easily scan all the code that mutates them and check that invariants are upheld etc.
You're right, assuming the module is not too large.
There's another point in favor of instance variables: it gives you the flexibility of multiple values in different parts of your code. As codebases grow, the danger of two unrelated places wanting different values for your global also grows.
Say a physics simulation has a global for gravity. This precludes, or at least unnecessarily complicates, the same process from having two separate simulations with different gravities (perhaps in different threads). This is especially bad in library code.
Agreed, and module globals shouldn’t be used for such cases. But for simpler cases, it may, in some cases, be better to use globals than create a singleton or some other convoluted solution.
To be clear, I am not saying globals should be used everywhere. There are a programming construct just like any other. When used carefully, they can greatly simplify code — but also have the potential to horribly complicate the code when abused. And that is true about any programming construct.
One thing that irritates me is the tendency to paint things black and white — eg. goto, globals, multiple inheritance etc. are bad. So “modern” programming languages try to eliminate them in a misguided attempt to keep programmers safe from themselves. I agree that these things are often misused, but a bad programmer will figure out a way to misuse anything — or do you think it is impossible to write bad code in something like, say, Go? Dumbing things down doesn’t prevent bad programmers from messing up, instead it causes the rest to write horrible code because of the lost power.
Mutable variable that have exactly one instance (globally) cause some of the same issues, which is why people might casually lump them in with "global variables". (It would be nice if everyone used more precise terminology, though.)
Well, there have been cases where I was asked to get rid of them by people reviewing my code — for no reason other than that global variables are “considered harmful”.
Just typical zealotry. So instead of globals we should use DependencyInjection, and now the app is magically better? For many projects it hardly matters, and I'm not going to over complicate things when there's no practical reason for it.
I am still on the fence about them. Yes, they cause problems (looking at you, powershell!). Yes, they can be used as a band-aid for thorny issues (Time constraints!). They are a tool, like any other programming construct.
Is making them properties and sticking them in a "GlobalVariablesByAnotherName" singleton and chucking that around better? Possibly. Possibly not.
I have found, however, that any code where someone has used global variables is hard to share with other developers. Maybe that's the crux of the matter.
IDE will show you where it's accessed, but it's of little use if there are two hundred fifty usages, and IDE won't fix the design choices that followed from the use of globals.
Any time I come across (or review) code which uses globals, even at the module level, they invariably make it hard to mock the related code in tests. Dependency injection and class (/instance) level state are an improvement pretty much always.
I would say yes they are, in general. The title of the post is an absolute, but as the author says in the second paragraph:
> As with all HeuristicRules, this is not a rule that applies 100% of the time. Code is generally clearer and easier to maintain when it does not use globals, but there are exceptions.
For me, the root of the problem with them is that they introduce coupling (or to put it another way, they break modularity). When you introduce global state, you introduce it everywhere. In reality there are probably only a handful of places that it relates to, but "is the global state involved here?" becomes a question you need to ask yourself whenever you write/update any part of the code.
The same argument can apply at more fine-grained levels. If you introduce a module-level variable, you need to keep it in mind whenever dealing with any code in that module. Similarly for class-level and instance-level variables.
There is a trade-off: ease-of-access and clarity vs the extra cognitive load you are introducing to every part of the scope. In my opinion, that balance determines whether a variable is good or bad. For any non-trivial piece of code, the sum almost always works out on the side of reducing the cognitive load.
It's funny how much I dislike the top two alternatives to globals, at least how they're typically implemented today. For me it's a case where the cure is worse than the disease.
I've worked with a codebase where the global state was passed around as a configuration object, and that made heavy liberal use of dependency inversion. No other code base I've seen was so obtuse...it was nearly impossible to know what a line of code did but looking at it.
Dependency injection and configuration objects certainly can be useful, but the bad design they seem to encourage makes me think very carefully about using them.
I'll play the devil's advocate. Global (shared) variables are fine when they represent non-instantiable program state and have descriptive names, with a clear semantic contract for mutating their state. Essentially, something like `the_world.rotation_speed` is a good example.
What if you, one day, want multiple worlds for some reason?
I'm developing a game in Unity, and the programming culture around it encourages globals and singletons to an unhealthy degree. I pushed back against them heavily in our project. As a result, it was very easy to add a previously unplanned split-screen mode one day, thanks to the codebase (e.g. the UI and input modules) not assuming that there's only ever one local player.
> What if you, one day, want multiple worlds for some reason?
Write code for the application you want, not the set of all applications you might hypothetically want someday. Incur the cost of new features after you decide you want them, not before.
In general I fully agree, YAGNI and all that, but from experience, mutable globals, unless used very sparingly, rigidify the codebase to a surprising degree and make it more error-prone, often for very little actual gain.
As with any software engineering guideline, there are exceptions. E.g. Java-style loggers are so ubiqitous and "not that mutable", that it's generally good for them to be global.
Edit: also from experience, suddenly wanting two of something where before there was only one, is not that uncommon.
In the basic case when you don't need to clone the world, you avoid the passing down on the call stack of what is truly a global variable, disguised as a local instance. As a casual reader of such code, you might be deluded that `the_world` in `func1` is something different from `the_world` in `func2`, when they are the same.
If you plan to "virtualise" stuff later, instantiation is certainly the way.
I'd rather readers of such code take the (IMO minor) extra effort to consider the possibility of the worlds being different rather than bake the assumption that they're the same into even more code. That's how globals spread around your codebase, little by little, until it's very hard to refactor away from them. Better to just say no from the start.
Also, if you have so many globals that passing them around as parameters / instance variables becomes truly tedious, that can be a valuable signal that the program is poorly structured.
Of course, none of this matters in small codebases, as long as they stay small.
This is how Redux (Flux implementation) in JavaScript works. Basically the whole application state is maintained in a single, global variable. Also, every possible action has a global string ID constant.
Coming from Java and its best practices it seemed really odd at first, but it makes state management and debugging so much easier.
What is with all this contrarianism against basic software best practices in tech circles these days? First the thread about how great copy pasting was, and now people praising the use of global variables. What's next - goto advocacy? Segmentation faults as the preferred error handling technique?
Both global variables and gotos are bad. On the other hand, the contortions some people go through to get the same effect without using the actual construct are often worse. That's the real point. Design and implementation decisions should be judged based on what they do, not their superficial appearance. If code is littered with singletons and often-thrown exceptions, it's worse than if the programmer had been honest about using globals and gotos.
If you actually encounter a situation where more self-documenting constructs don't work, sure, but usually that only happens due to deficiencies in your programming language.
> Segmentation faults as the preferred error handling technique?
Actually you should use SIGABRT, not SIGSEGV; see self-documenting.
I've wondered about the parallels between globals in code and open door immigration policies, etc. The left/right political axis may conceptually span across more than just politics and the current tech climate is more left.
I wouldn't say gotos are fine, but they're sometimes better than alternatives. I'll take a "goto" to an error-cleanup cascade at the end of a function over ten nested three-line functions, a mini state machine, or lots of little state variables (the most common alternatives) any day. If the language has destructors or deferreds or anything else that automatically triggers when leaving scope that's probably even better, but some older languages - notably C - don't, so dogma often leads to worse alternatives.
A variable us something that varies, mutates, changes value, call it what you will. Global mutable state (i'd say, any non-local mutable state) complicates things and is a defect attractor.
Global constants are fine. Something like pi is as global as it gets, and it's never a problem.
There's an intermediate case of "global config info" which is technically mutable but is only mutated during a program startup, and stays constant during execution.
That is presuming they stay constant forever. Which generally only makes sense for mathematical constants and type bounds.
Otherwise it is better to pass them in (via dependency injection or otherwise) encapsulated so that enhancing the code for them to become variable or have versions is easy.
I still have a hard time being dogmatic about this. For some simple multithreaded apps I'll use a global "STOP" variable, which I mutate when shutting down. All of the functions check this before attempting to gracefully exit.
I've done alternatives and I've never really seen the benefit.
The threads could take a pointer/reference to the "stop variable" and use the reference instead of accessing the global variable directly. That allows you to mitigate many problems of global variables. Sorry, if you already do this.
Honest question but what do I really gain in this scenario? Now all worker function arguments are N+1. I feel like it’s the waterbed affect and the complexity is simply pushed around elsewhere.
It seems like there’s simply a reasonable cognative “maximum” of global before it becomes unworkable. My gut tells me it’s between 1 and 4.
Gain? You could pass different pointers to each worker function, giving you a way to partially shutdown your process, or to shutdown some workers first, and workers they depend on later.
Of course, if you don’t need that, it’s not a gain, and a global is fine.
Globals also are fine if you are resource constrained (e.g. if you have a few kilobytes of RAM or even less) Getting your code running in it may trump everything else.
I think the main benefit is that every function relying on the stop variable is clearly defined as such, as opposed to having to find out manually who is reliant on it based on usage; its not so much moving complexit around as it is making things more explicit. im not sure how useful this is in practice though, for the common case
The only (relatively minor) downsides I can see are:
(A) it makes the code "singleton" by default: you can't instantiate two objects of the same class with two different values for the global argument
(B) unless the global argument is declared in the same class, it slightly obfuscates the fact that a class depends on the argument (compared to all dependencies being constructor parameters)
(C) it makes testing, especially multithreaded testing, more awkward and error-prone: tests have to set and reset global variables
It's easy enough to refactor (A) where needed, as long as you have control of the code, and (C) can be worked around with good enough test helpers.
I think one can reasonably argue both for and against the convenience outweighing these downsides.
Perhaps they are minor if there's one or two global variables. Once they're used liberally and there's plenty of them, the resulting bugs can become so elusive it can drive you mad.
A bug can be extremely hard to replicate, because it only arises from a very specific sequence of events that step-by-step flip the globals into an overall invalid state. And it's virtually impossible to determine "whodunnit".
It's a perfect recipe for "the app sometimes crashes on Thursdays" scenario.
Of course this is a bit of a red herring anyway... As the original commenter remarked already, command line flags may be stored globally (eg. as read-only fields), but won't - and shouldn't - be variables.
They're only given once, and their values aren't going to change throughout the program's running time. So they're essentially constants, which is fine.
I remember, in one of my earlier projects, we had code quality metric which used to run after each fortnightly build and integration. This tool used to count no. of global variables added to C and public variables added in C++ source code. Even if single such variable is reported, author of that code had to justify use of global/ public variables.
P.S. That doesn't mean there were no public/ global vars in legacy code. This practice started when code quality and system stability gradually started declining by their overuse
Global variables are fastest, least memory intensive way to transfer state. If you need thread safety, atomic variables are still faster and more efficient than any alternative.
You can create pseudo-namespaces in C like Global_ and avoid name clashes too.
If module global variables are considered truly horrible, the same can be said about instance variables inside classes. They are as good (or bad) as module globals in the sense that they represent shared state and can potentially be mutated by any method/member function in the class.
Of course the functional programmers among us will say that shared state of any kind is bad, and I agree. But we are not talking about that here, are we?