8 ms·
I Like and Use Global Variables
- antithesis-nl 2y ago> For example, counter() keeps increments consistent No, not in the face of any sort of concurrency it doesn't...
- smackeyacky 2y agoIt kinda does but perhaps not entirely. The increment will always end up in the right place afterwards but internally if you expect it to be +1 in the same thread you’ll sometimes be wrong
- masklinn 2y ago> The increment will always end up in the right place afterwards That is completely untrue.
- qzzi 2y agoIn the right place, but maybe at the wrong time. You can expect +1 and always be right, when the value should be +10.
- OskarS 2y agoNo, this is absolutely not true at all. Calling this function from multiple threads is undefined behaviour in C++ (unless synchronized using some other mechanism), you get NO guarantees what so ever on program behaviour. Best case scenario is that the loads and stores are interleaved, which leads to multiple threads returning the same value when calling counter(), which will guarantee crashes elsewhere in the program (the purpose of functions like these is to produce UNIQUE values, after all). But it's undefined behaviour in any case, it's just unacceptable to put in a C/C++ code base.
- levodelellis 2y agoI'm the author of the article. How many comments are you going to write about how my usage is bad citing multi-threads when I said multi threading is out of scope? And how do you not understand this would be a how-to use thread locals correctly when you are dealing with multiple threads.
- OskarS 2y agoI'm sorry you feel offended, but you did write an article online with a deliberately provocative title. If you do that, you need to be able to deal with criticism. And not considering concurrency when mutating globals in C/C++ is not acceptable (never mind good) practice. You can say "threads are out of scope" till your blue in the face, but if you write an article with the thesis "globals are good, actually", you have to be able to deal with people saying "they're dangerous because of thread safety". That is a legitimate criticism of your thesis. In addition, my other criticisms of your code (overflow and properly scoping statics) have nothing to do with concurrency.
- levodelellis 2y agoHas anyone told you to never use atomics? Have you not heard lockless programming is hard? (I saw this recently https://wiki.libsdl.org/SDL2/CategoryAtomic https://wiki.libsdl.org/SDL2/CategoryAtomic,) Have you written multi-threaded programs? I written two large ones. The fact you're suggesting non-experts use atomic is insanity. As well as criticizing 'overflow' in an article showing minimum easy to understand code to be read by people using completely different languages
- atoav 2y agoThe problem is however that the internalized advice is shortened to "don't use global variables" and not "avoid global mutable variables when using concurrency". Constant global variables can be very useful. Mutable global variables can be totally fine in a singlethreaded (e.g. typical embedded) program. I agree that you should still use them with caution, but every advice that says: "don't do $X" should come with instructions under which circumstances it is valid and under which it isn't — and how to get the intended behavior instead with e.g. message queues, locks or whatnot.
- ossobuco 2y ago> No, not in the face of any sort of concurrency it doesn't... I fail to see how does that relate to the article in question. The author provided an example for which counter() works well, he didn't claim that the same "pattern" would be good for 100% of the use cases.
- OskarS 2y agoThere are many reasons why global variables are bad, but extremely high on the list (if not first) is concurrency issues. The fact that the author think it's acceptable in any C or C++ code base to put in code like: static int prv_counter; int counter() { return ++prv_counter; } Is insanity. Like, this is programmer malpractice. This function can only ever be called from one thread (not to mention: using `int` instead of `int64_t` is also a trivial mistake, this easily overflows). This is the kind of thing that enters a code base, works fine for long enough that everyone forgets about it, then causes horrible security issues and crashes. The idea of saying this is "good use" of a global variable is... this person should not be giving advice on good coding. If you want to do this (and you shouldn't, because global state is bad for 14 other reasons), at the very least, make it thread safe and not trivially overflowing: static std::atomic<int64_t> prv_counter { 0 }; int64_t counter() { return 1 + prv_counter.fetch_add(1, std::memory_order_relaxed); } (the 1+ is because the original author used pre-increment instead of post-increment) Like, this is not awesome, and you shouldn't do it, but it's at least not a total disaster. EDIT: actually, this is also bad, because the `prv_counter` is not really private at all. The better way to do that would be: int64_t counter() { static std::atomic<int64_t> prv_counter { 0 }; return 1 + prv_counter.fetch_add(1, std::memory_order_relaxed); } Three different serious issues in two lines of code, fun!
- ossobuco 2y agoExcept the author's example is single threaded, so for that specific case your implementation of counter is needlessly complex and would actually be confusing. The point is that there is no solution that works for all use cases. If you always attempt to write fully generalized code like that you'll end up with tons of unnecessary complexity. Solve the problem at hand, not some hypothetical. The author even specifies a rule that covers your case: "If you're using threads, global and static variables should be thread local. If they are not then the discussion becomes about sharing data across threads and synchronization, which is a different topic."
- qzzi 2y agoThe point is obviously that the counter is centralized, and it relates to the previous example where is no concurrency. The need for synchronization when sharing data across threads is mentioned just below that.
- flohofwoe 2y agoThe trend to overgeneralize is the same problem as 'global mutable state considered harmful' ;) In a well designed code base, only a very small part of the code should need to worry about concurrency and parallelism.
- IshKebab 2y ago"Cool. Now we need two work lists." This is what you should do if you must use globals, but it doesn't really give any advantages.
- shoo 2y agocool, now we want to run N independent test scenarios, and why not run them in parallel, so I guess we want N work lists
- BoingBoomTschak 2y agoThe hatred of global variables for the sake of it is something that Common Lisp and its earmuffs un-brainwashed in me, thankfully.
- sshine 2y agoThere is something beautiful about global, mutable state: Your program must be really small and scoped for this to make sense. Also, kudos for providing so many corner cases to avoid that are non-trivial to formulate. It feels like one of those "it's not impossible to do right" cases. I'll just cite one evaluation point from the post: > It's extremely easy to use incorrectly.
- turtles3 2y agoI agree, but this > Your program must be really small and scoped for this to make sense. to me suggests that it's not really global state if your program has to stay small and scoped. In a sense, your program has just become the context boundary for the state, instead of a function, or class, or database. I realise that this line of argument effectively leads to the idea that no state is global, but perhaps that gives us a better way to understand the claim that 'global variables can work', which they undoubtedly can. It's fine for a program (or a thread, as in the original article) to be the context which bounds a variable's scope.
- moffkalast 2y agoWell it's still technically a global state if it's a collection of scoped singletons, it's not much different than having a map of object names and their data as one big global variable, it's just formatted slightly more practically.
- Cthulhu_ 2y agoI've seen code for emulators that is very long functions and a lot of globals; I'd also argue they're OK if your program flow is very stable / predictable and synchronous, and especially older consoles and game engines have predictable phases. Getting information from your "tick" to your "render" stage is a lot easier if you have global state.
- pjc50 2y agoEmulators are often emulating "hardware globals", things like machine registers or GPU state where in the thing being emulated there really is only one instance.
- Vanit 2y agoYou may as well just use a singleton pattern if you're going to do this, and at least that's easier to maintain if your use cases change.
- pinoy420 2y agoNot sure how that is any different besides construction of an object rather than a file with globals?
- jwarden 2y agoA singleton object can encapsulate the global state, converting global variables to private fields. How would this be different? Because a counter singleton can for example disallow directly setting the count field, only allowing the count to be incremented through a method.
- int_19h 2y agoYou don't need objects for that kind of encapsulation, just don't export your variables across module / unit boundaries.
- jwarden 2y agoYes good point. The module/unit acts as the singleton instance, in a sense, though that might be the incorrect way to put it. In any case, I think variables that are “global” but encapsulated in this way lose the potential for harm we associate with a global variable the whole program may be directly reading and writing.
- n0w 2y agoI think the suggestion is to encapsulate global mutable state behind a strict interface (if you want global mutable state)
- chikere232 2y agoSingletons are just globals for people who have learnt "globals are bad" but lack a deeper understanding
- metayrnc 2y agoI think the hatred of global variables comes from the fact that they are hard to use correctly. This article also makes the point that to use correctly you need to follow a bunch of rules.
- MortyWaves 2y agoI’m normally open minded about what I read posted here but that article does nothing but frustrate me. I can’t count the amount of times I’ve dealt with legacy spaghetti code that used global variables and the pain and suffering that results from that.
- deleted 2y ago[deleted]
- matheweis 2y agoExactly the issue; inevitably someone will forget to follow those rules, at which point esoteric bugs will have been introduced. I think it’s somewhat similar to unchecked memory access. Used correctly it works just fine and offers extremely high performance. Unfortunately, history has shown that over enough time mistakes will be made. As an industry we’re now to the point of actively denouncing entire languages over the risks of unchecked memory access. Using software development patterns that rely on the engineers to follow a bunch of rules to do things correctly will eventually burn you. Better to avoid them entirely if at all possible.
- levodelellis 2y agoThere's plenty of things in programming that are "hard to use correctly". I seen many codebases that you must clone an object because passing it in a function because there was so much spaghetti data structures that you'd end up accidentally mutating something you didn't mean
- forrestthewoods 2y agoHard disagree. Except for, begrudgingly, logging those are all bad uses and you shouldn't do them. I find the append_work especially egregious. Never ever use a bloody global for that! Goodness gracious me. What an absolute and utter waste that provides zero utility. If you have a job system just pass a damn handle to the job queue. Good lord. If everyone spent a little effort to not use globals the world would be a much better place. The value add of globals when they are mildly useful is so inconsequential such that it's basically never worth the effort.
- progx 2y agoI Like and Use Global Variables, then I grew up. ;-)
- Cthulhu_ 2y agoSo did I, and then I did a stint in pico-8, stopped worrying and started loving the globals. It's my code, tiny, sloppy, single threaded, nobody reads it, it just needs to do a job. Not going to use it in anything I get paid for though, lol. At one point there was a commonly accepted "singleton" pattern for things like logging but that's just globals with extra steps. In Go I just create services in my main and pass them around where needed.
- flohofwoe 2y agoI liked and used global variables, then I grew up and never used them, then I got more experienced and started to use them again in places where it makes sense ;) (same with goto btw)
- flohofwoe 2y agoIMHO mutable global state is totally fine if it is scoped to the current module or source file. Of course then it's not really 'global' anymore ;) Immutable global state is fine to be accessible from the whole code base. Pure functions are best of course, but you can't build real-world things entirely from pure functions. The problematic approach lives somewhere inbetween, passing context/object references into deep callstacks isn't really a good solution for anything.
- louthy 2y ago> Pure functions are best of course, but you can't build real-world things entirely from pure functions. That's just plain wrong. There's even a language built entirely around pure functions: Haskell.
- flohofwoe 2y agoHow do you talk to typical OS-, IO- or GUI-APIs (which all essentially represent global mutable state) in Haskell with pure functional code?
- khana 2y ago[dead]
- pjc50 2y ago> If you're using threads, global and static variables should be thread local. If they are not then the discussion becomes about sharing data across threads and synchronization, which is a different topic Most languages make global and static variables thread-global by default, and making them thread-local is more work. I can see why, but that piece of language design causes a lot of global variable problems. Also: you can simplify a lot of problems by deciding that something is going to be limited to n=1, whether that's variables or threads, and then a business reason comes along where you really want to have n=2. Suddenly every global is a source of regret.
- matheweis 2y ago> A Few Rules For Using Globals: > If you change observable state, restore it I’m sorry but no. Humans are human and mistakes will be made. I’ve lost count of the number of esoteric bugs I’ve had to track down due to global state being changed and not put back properly. If you have to qualify a pattern with rules that are easily forgotten or open to corner case bugs, it’s far better to just not use that pattern in the first place.
- thom 2y agoThe author shows encapsulation of global state elsewhere. I’m not sure why they wouldn’t use RAII for the log level stuff so it was automatically rolled back.
- Salgat 2y agoYeah, I like how the OP basically recreates scoped variables by applying all these strict rules, instead of just using scoped variables. For example, with DI, instead of a global variable, we just use a singleton/scoped dependency. Why? Because we can enforce those rules implicitly without hoping everyone pinky promises to use the global variables correctly.
- levodelellis 2y agoIf you want to write code to show me what you're talking about (best if I can run it) I'll tell you why or why not. I can tell you right now I dislike DI (and singletons) for reasons I can't cover in a single post
- tialaramex 2y agoJust two of the "rules" is enough to see that there's no sense here: 1. It should be hard or impossible to use incorrectly. For example, counter() keeps increments consistent. 2. If you change observable state, restore it. That can be summarised as "To prevent mistakes: Don't make any mistakes". It made lots of sense once I saw this was by a C++ programmer, C++ is the language with, as a prominent C++ practitioner put it: False Positives for the question: Is this a valid program? If you're used to a language which gaslights you by having the compiler not emit any diagnostics whatsoever and just calmly handing you a nonsensical output executable because what you wrote was subtly wrong obviously global variables seem fine, what's not to like? You just have to be inhumanly competent at all times, which was the baseline requirement for the entire language.
- deleted 2y ago[deleted]
- Ragnarork 2y agoI commend author for not shying away from writing such a take on one of the generally assumed consensus about code. However this just underlines again why globals are usually good to avoid. It takes rigor not to make mistakes that can completely mess up with program state. I also strongly disagree with the benefits author advances for having global. Code feels actually less spaghetti when things are properly scoped and not accessible from everywhere, which hides extremely well dependencies and makes it way harder to reason about the code. One famous exception to that is indeed logging (which in itself is based on global state when printing to stdout or stderr anyway). And also > on account of how few places your object can be stored to you can store things in a single place, just once, without the need for globals.
- banthar 2y agoYou can make globals thread safe by using thread locals. You can make methods using them reentrant by carefully saving and restoring state. What about exceptions? Any exception from `process()` is going to leave this global state in a total mess.
- levodelellis 2y agoI should have talked more about that in the article. I mentioned defer, making functions reentrant, etc, but languages with exceptions and without defer can make things much harder. I tried to make it clear the global state should be accessible from a handful of functions or within a file/module
- NooneAtAll3 2y agoauthor, if you are here let's = let us, it is for suggestions lets = I let, he-she-it lets, it's the verb form
- deleted 2y ago[deleted]
- MattPalmer1086 2y agoWell sure, if you're a great programmer you can do all kinds of things that are potentially dangerous, like using globals or lots of goto for control flow. We stick to these kinds of rules because most people are not great programmers all the time. It's just mostly better to do the safe and boring thing most of the time.
- zombot 2y agoThere will always be people who have invincibly bad taste.