The bug in the program reveals a poor understanding of object lifecycles by whoever wrote it. The `obj` argument to `simple` is not globally unique and so it makes a poor location to store global state information (a count of how often `simple` is called, in this example).<p>Never tie global state information to ephemeral objects whose lifetime may be smaller than what you want to track. In this case, they want to know how many times `simple` is called across the <i>program's</i> lifetime. Unless you can guarantee the `obj` argument or its `counter` member exists from before the first call to `simple` and through the last call to `simple` and is the only `obj` to ever be passed to `simple`, it is the wrong place to put the count information. And with those guarantees, you may as well remove `obj` as a parameter to both `simple` and `complex` and just treat it as a global.<p>State information needs to exist in objects or locations that last as long as that state information is relevant, no more, no less. If the information is about the overall program lifecycle, then a global can make sense. If you only need to know how many times `simple` was invoked with a particular `obj` instance, then tie it to the object passed in as the `obj` argument.
From the article> "Static Function Variable: In C inside a function, you can declare a static variable. You could consider this as a local variable but I don't since it's on the heap, and can be returned and modified outside of the functions. I absolutely avoid this except as a counter whose address I never return."<p>These variables are not on the heap. They are statically allocated. Their visibility is the only thing that differentiates them from global variables and static variables defined outside of functions.<p>I think such variables can be useful, if you need a simple way of keeping some persistent state within a function. Of course it's more state you are carrying around, so it's hard to defend in a code review as best practice.<p>Amusingly, you can modify such variables from outside the function, simply by getting your function to provide the modifying code with a pointer to the variable, eg by returning the variable's address. If you do that though you're probably creating a monster. In contrast I think returning the value of the static variable (which the author casts shade on in the quote above) seems absolutely fine.<p>Edit: I should have stated that the number one problem with this technique is that it's absolutely not thread safe for obvious reasons.
Global variables (in languages where they otherwise make sense and don't have footguns at initialization and whatnot) have two main problems:<p>1. They work against local reasoning as you analyze the code<p>2. The semantic lifetime for a bundle of data is rarely actually the lifetime of the program<p>The second of those is easy to guard against. Just give the bundle of data a name associated with its desired lifetime. If you really only need one of those lifetimes then globally allocate one of them (in most languages this is as cheap as independently handling a bunch of globals, baked into the binary in a low-level language). If necessary, give it a `reset()` method.<p>The first is a more interesting problem. Even if you bundle data into some sort of `HTTPRequest` lifetime or whatever, the fact that it's bundled still works against local reasoning as you try to use your various counters and loggers and what have you. It's the same battle between implicit and explicit parameters we've argued about for decades. I don't have any concrete advice, but anecdotally I see more bugs from biggish collections of heterogeneous data types than I do from passing everything around manually (just the subsets people actually need).
Global variables are a programming construct, which like other programming constructs is neither bad nor good. Except, due to the takeover of workplaces by the best practices cult, instead of reasoning about tradeoffs on a case by case basis (which is the core of coding and software engineering), we ascribe a sort of morality to programming concepts.<p>For example, global variables have drawbacks, but so does re-writing every function in a call-stack (that perhaps you don't control and get callbacks from).<p>Or if you are building a prototype, the magic of being able to access "anything from anywhere" (either via globals or context, which is effectively a global that's scoped to a callstack), increases your speed by 10x (since you don't have to change all the code to keep passing its own abstractions to itself as arguments!)<p>Functions with long signatures are tedious to call, create poor horizontal (which then spills over to vertical) code density. This impacts your ability to look at code and follow the logic at a glance, and perhaps spot major bugs in review. There's also fewer stuff for say copilot to fill in incorrectly, increasing your ability to use AI.<p>At the end, every program has global state, and use of almost every programming construct from function calls (which may stack overflow) or the modulus operator (which can cause division by zero), or sharing memory between threads (which can cause data races) requires respecting some invariants. Instead, programmers will go to lengths to avoid globals (like singletons or other made up abstractions -- all while claiming the abstractions originate in the problem domain) to represent global state, because someone on the internet said it's bad.
I think this article broadens the definition of global variable and then says "Look, the things I added to the definition aren't bad, so global variables aren't always bad."<p>If you just look at what people normally mean by global variable, then I don't think the article changes minds on that.
I find the concept of a context structure passed as the first parameter to all your functions with all your "globals" to be very compelling for this sort of stuff.
> The problem is data access. Nothing more, nothing less.<p>I agree with this, but the problem with global variables is precisely that they make bad data access patterns look easy and natural. Speaking from experience, it’s a lot easier to enforce a “no global variables” rule than explain to a new graduate why you won’t allow them to assign a variable in module X even though it’s OK in module Y.
> If you run the code you'll see 1 2 3 4 3 printed instead of 5.<p>I'm really confused, as this behaviour appears to be completely obvious to me.
<i>mutable</i> global variables are intrinsically incompatible with a multithreaded environment. Having mutable shared state is never the right solution, unless you basically can live with data races. And that's before taking maintainability in consideration too
I think the author forgot the most useful use case for globals, and that is variables that has to do with the context the program is running under such as command line arguments and environment variables (properly validated and if needed escaped).
Any program that uses a database has a very similar problem to global variables.<p>As Gilad Bracha has pointed out, types are antimodular, and your database schema can be considered one giant type that pervades your program, just like globals can be.<p>I don't think we have tools to compositionally solve this, across different programming languages.
> <i>The problem is data access. Nothing more, nothing less. There's a term for this that has nothing to do with global variables: "action at a distance."</i><p>I mean yes, using global variables is just one of the ways to cause action-at-a-distance and that is... apparently a big reveal?<p>Otherwise sure, there is no pattern that cannot be utilized 100% correctly and without introducing bugs. Theoretically. Now let's look at the practical aspects and how often indiscriminately using such tempting patterns like global variables -- and mutexes-when-we-are-not-sure and I-will-remember-not-to-mutate-through-this-pointer -- lead to bugs down the road.<p>The answer is: fairly often.<p>IMO the article would be better titled as "There is no pattern that a lazy or careless programmer cannot use to introduce a bug".
There are only a few use-cases where global variables make sense:<p>1. Thread container registry with mutex lock for garbage collection and inter-process communication (children know their thread ID)<p>2. volatile system register and memory DMA use in low level cpu/mcu (the compiler and or linker could pooch the hardware memory layout)<p>3. Performance optimized pre-cached shared-memory state-machines with non-blocking magic<p>4. OS Unikernels<p>Not sure I have seen many other valid use-cases that splatter language scopes with bad/naive designs. YMMV =3
This assertion made in the article invalidates the premise of same:<p><pre><code> With a little encapsulation, you can make globals error-proof ...</code></pre>
Globals are essential to track state/values of variables in intermediate step, for debug,
I create unique object on globals and store intermediate variables in it, so i can inspect values when something goes wrong, being only one unique object to maintain it will not override any existing vars thus not affecting existing programs
I've recently found it helpful to think of problems at the scope of an individual process, rather than a set of functions in a library or framework. This makes it much clearer when a global variable makes sense or not, and generally where to initialize things, and place state.
It's just like saying guns are not the problem.<p>Perhaps a unicorn doesn't die as soon as you first use a global var. But it has two .45s pointed to it cranium left and right. And at any random moment Danni DeVito will start blasting.
Global variables are fine when you read many times, but you write to them sparingly and when they are updated but the old value is read/used you have mechanisms to handle errors and retry
Please. Just don't.<p>Moving state into the global namespace and accessing it directly from that space makes it much more difficult to test, instrument and integrate.<p>Sure, if you're building disposable toy software, do whatever is easiest. But if you're building software for others to use, at least provide a context struct and pass that around when you can.<p>For those cases where this is challenging or impossible, please sequence your application or library initialization so that these globals are at least fungible/assignable at runtime.
The problem with global variables is that they imply that there's only one of <i>that thing</i>. This assumption - no matter how certain it seems - will inevitably be proven false in any sufficiently long-lived software component.
Agreed, global variables are fine up to a certain scale, especially if they're only used in the main file. They only really become a problem if you start modifying them from inside different files.<p>The real underlying problem is 'Spooky action at a distance'; it's not an issue that is specific to global variables. If you pass an instance between components by-reference and its properties get modified by multiple components, it can become difficult to track where the state changes originated and that can create very nasty, difficult-to-reproduce bugs. So this can happen even if your code is fully modularized; the issue is that passing instances by reference means that the properties of that instance behave similarly to global variables as they can be modified by multiple different components/files (without a single component being responsible for it).<p>That's partly where the motivation for functional programming comes from; it forces pass-by-value all the time to avoid all possibility of mutations. The core value is not unique to FP though; it comes from designing components such that they have a simple interface which requires mostly primitive types as parameters. Passing objects is OK too, so long as these objects only represent structured information and their references aren't being held onto for future transformation.<p>So for example, you can let components fully encapsulate all the 'instances' which they manage and only give those parent components INFORMATION about what they have to do (without trying to micromanage their child instances); I avoid passing instances or modules to each other as it generally indicates a leaky abstraction.<p>Sometimes it takes some creativity to find a solution which doesn't require instance-passing but when you find such solution, the benefits are usually significant and lasting. The focus should be on message-passing. Like when logging, the code will be easier to follow if all the errors from all the components bubble up to the main file (e.g. via events, streams, callbacks...) and are logged inside the main file because then any developer debugging the code can find the log inside the main file and then trade it down to its originating component.<p>Methods should be given information about what to do, they should not be given the tools to do their job... Like if you catch a taxi in real life, you don't bring a jerrycan of petrol and a steering wheel with you to give to the taxi driver. You just provide them with information; the address of your desired destination. You trust that the Taxi driver has all the tools they need to do the job.<p>If you do really want to pass an instance to another instance to manage, then the single-responsibility principle helps limit the complexity and possibility for spooky action. It should only be passed once to initialize and then the receiving component needs to have full control/responsibility for that child. I try to avoid as much as possible though.