Live data from Hacker News

Global variables are not the problem

codestyleandtaste.com

111–120 of 165 posts

Re: Global variables are not the problem

#111

Earlier quoted context omitted.

I could not initially reply to you. Your comment rubbed me the wrong way, because I had no intention of trying to degrade anyone, and frankly, I was offended. But I thought better of my hasty and emotional response. I would rather take a deep breath, re-focus, re-engage, and be educated in a thoughtful dialog than get into a mud slinging contest. I am always willing to be enlightened.

A tip, in your profile you can set a delay which is a number of minutes before your comments will become visible to other people. Mine is set to 2 right now. This gives you time to edit your comment (helpful for some longer ones) but also to write some garbage response and then think better and delete it before anyone's the wiser. It's also helpful to give you time to re-read a mostly good, but maybe not polite, resp…

That is a good tip.

Re: Global variables are not the problem

#112

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.

That's exactly why I used this specific example. I seen many code bases that use clone to avoid mutation problems so I wrote this specifically to show it can become a problem too. I wrote a better article on globals. I plan on posting it next week

This seems more an issue with not understanding structuralClone, than one of understanding globals or lack thereof. There’s nothing wrong with the example, it does exactly what the code says it should — if you want counter to be “global” then structuralClone isn’t the function you want to call. The bug isn’t in how counter was in obj, the bug is in calling structuralClone when its behaviour wasn’t wanted.

With that said, it seems obvious that if you want to globally count the calls, then that count shouldn’t live in an argument where you (the function) don’t control its lifetime or how global it actually is. Simple has no say over what object obj.counter points to, it could trivially be a value type passed into that particular call, so if you know you want a global count then of course storing it in the argument is the wrong choice.

Global has two conflated meanings: global lifetime (ie lifetime of the whole program) and global access (which the article states). Simple needs global lifetime but not global access.

You rarely ”need” global access, although for things like a logger it can be convenient. Often you do need global lifetime.

Re: Global variables are not the problem

#113
post #61
post #36

Global variables (in languages where they otherwise make sense and don't have footguns at initialization and whatnot) have two main problems: 1. They work against local reasoning as you analyze the code 2. The semantic lifetime for a bundle of data is rarely actually the lifetime of the program The second of those is easy to guard against. Just give the bundle of data a name associated with its desired lifetime. If y…

I don't think #1 is necessarily true. Take a common case for a global variable, a metric your app is exposing via prometheus. You have some global variable representing its value. Libraries like to hide the global variable sometimes with cuteness like MetricsRegistey.get_metric("foo") but they're globally readable and writable state. And in your function you do a little metric.observe_event() to increment your counte…

Of course #1 is not necessarily true, it depends on one's coding style, and using globals for application-scoped services like logging/metrics is tentatively fine... although I also think that if we're going to dedicate globals almost exclusively to this use, they probably should have dynamic scoping.

On the other hand, I have seen quite a lot of parsing/compilers' code from the seventies and eighties and let me tell you: for some reason, having interfaces between lexer and parser, or lexer and buffered reader, or whatever else to be "call void NextToken(void), it updates global variables TokenKind, TokenNumber, TokenText to store the info about the freshly produced token" was immensely popular. This has gotten slightly less popular but even today e.g. Golang's scanner has method next() that updates scanner's "current token"-related fields and returns nothing. I don't know why: I've written several LL(1) recursive-descent parsers that explicitly pass the current token around the parsing functions and it works perfectly fine.

Re: Global variables are not the problem

#115
post #89
post #83

Earlier quoted context omitted.

That's only needed if you use multiple threads. In a single thread global vars are just fine, and much simplier than passing around ctx

And do you always know beyond any reasonable doubt that your code will be single-threaded for all time? Because the moment this changes, you're in for a world of pain.

Wrapping the globals into to a struct context #ifdef MULTI-THREADED and adding this ctx for each call as first call is a matter of minutes. I've done this multiple times.

Much worse is protecting concurrent writes, eg to an object or hash table

Re: Global variables are not the problem

#116
post #35

Earlier quoted context omitted.

I like the diagnosis. My JS is terrible, but it seems like once you make the counter a global variable it is just better to change it to have an atomic dedicated count function. So instead of incrementing the counter in simple, a globalCount() function gets called that isolates the state. Something like { let i = 0; var counter = function (){ console.log(++i); } } Then call counter() to count & log and document that…

How is a globally-scoped closure not a global variable?

Because there is no intent, need or reason to vary it.

Nearly everything in RAM is technically a global variable to someone who is keen enough. Programming depends on a sort-of honour system not to treat things like variables if it is hinted that they shouldn't and it doesn't make sense to. Otherwise we can all start making calls to scanmem & friends.

Re: Global variables are not the problem

#117

Earlier quoted context omitted.

Hard disagree. If I have 500 functions, I don't want to extrapolate out the overhead of passing a state object around to all of them. That's a waste of effort, and frankly makes me think you want to code using an FP paradigm even in imperative languages. Module-level and thread-level "globals" are fine. You gain nothing (other than some smug ivory tower sense of superiority) by making your functions pure and passing…

Yes, you gain testability. If your global state contains something that runs in prod but should not run in a testing environment (e.g. a database connection), your global variable based code is now untestable. Dependency Injection is popular for a very good reason.

This sounds like a design deficiency.

If you have something that should only run in testing, perhaps your test harness should set the global variable appropriately, no?

Re: Global variables are not the problem

#118

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.

That just seems like globals with extra steps. Suddenly if your context structure has a weird value in it, you’ll have to check every function to see who messed it up.

First, that's true for globals as well. Second, with "context structure" pattern, the modifications to it are usually done by copying this structure, modifying some fields in the copy and passing the copy downwards, which severely limits the impact radius and simplifies tracking down which function messed it up: it's either something above you in the call stack or one of the very few (hopefully) functions that changes this context by-reference, with intent to apply such changes globally.

Re: Global variables are not the problem

#119
post #90

Earlier quoted context omitted.

Hard disagree. If I have 500 functions, I don't want to extrapolate out the overhead of passing a state object around to all of them. That's a waste of effort, and frankly makes me think you want to code using an FP paradigm even in imperative languages. Module-level and thread-level "globals" are fine. You gain nothing (other than some smug ivory tower sense of superiority) by making your functions pure and passing…

I got into this argument with my former coworkers. Huge legacy codebase. Important information (such as the current tenant of our multi-tenant app) was hidden away in thread-local vars. This made code really hard to understand for newcomers because you just had to know that you'd have to set certain variables before calling certain other functions. Writing tests was also much more difficult and verbose. None of these…

You know what helps manage all this complexity and keep the state internally and externally consistent?

Encapsulation. Provide methods for state manipulation that keep the application state in a known good configuration. App level, module level or thread level.

Use your test harness to control this state.

If you take a step back I think you’ll realize it’s six of one, half dozen of the other. Except this way doesn’t require manually passing an object into every function in your codebase.

Re: Global variables are not the problem

#120
Please. Just don't.

Moving state into the global namespace and accessing it directly from that space makes it much more difficult to test, instrument and integrate.

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.

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.

Post reply on HN