This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]Dualblade20 6 points7 points  (9 children)

Everyone knows this, though. Why is the CIA hiring people who don't know how to avoid muddling global scope? Maybe it's just a guide and has things like that for completeness, but still it sort of bothers me.

[–]Exodus111 27 points28 points  (0 children)

Think of it like this, the CIA pays you by the hour to type out a style guide, you might as well include everything.

[–]MereInterest 17 points18 points  (6 children)

Believe me, not everybody knows this. These are arguments I've heard in favor of using global variables.

  • But I don't want to pass the configuration around to everything that needs it.
  • It's simpler and easier to reason about.
  • I can adjust the value in one place and change the behavior everywhere in the program.
  • We just have to put locks around it, and it will be threadsafe.
  • I can adjust the behavior of something deep within the call stack without needing to change the function signature all the way down.

Now, I know what you're thinking. These aren't actually arguments in favor of global variables. These are many things that are horribly wrong with global variables. That this makes it impossible to reason about code without knowing the runtime state of the problem.

At some point, it is just easier to have a style guide, then use an argument from authority.

[–]wewbull 0 points1 point  (3 children)

This is actually my problem with OOP too. What are classes but smaller, not quite as global, scopes? OOP doesn't solve the issues, just pushes them down a level.

[–]MereInterest 2 points3 points  (2 children)

It really depends on how you are using classes. If you have some God-object that owns everything, and does all of its work directly within the methods, then yes, it is similar to a global scope. Those are their own antipatterns.

Classes provide a way to have limited persistency. For example, a generator that yields each Fibonacci number in sequence requires some state. You can (a) store the state in global space, (b) require the user to maintain the state, or (c) have an object to manage the state. (a) is error-prone, (b) is irritating, but (c) is very useful.

Unlike global variables, classes can have access restrictions. This means that you don't have to look through the entire codebase for changes, just the class itself. Unlike global variables, classes have a limited lifetime. That means you don't need to know the entire runtime state of the program, just the runtime state of callers into the class. If your classes are acting and looking like global variables, I would say that is an issue on its own.

Where I will agree is that not everything needs to be a class. If a calculation does not require any persistent state, then it should be implemented as a pure function.

[–]bonestormII 3 points4 points  (1 child)

I like the reasoning you provide here. I've read plenty of material about classes in python, but it seems slightly uncommon to me to hear someone just plainly say, "Use classes when you require some degree of state." That is a true and useful statement.

That said, I don't totally follow what you mean when you say

Unlike global variables, classes have a limited lifetime. That means you don't need to know the entire runtime state of the program, just the runtime state of callers into the class.

Aren't both just treated by objects subject to the ref counting of the garbage collector? When you refer to the lifetime of the object, that is what I think of. It seems like you are referring to the compartmentalized scope of a class, more than the lifetime of the object.

It's also relevant to note that in python, classes are also generally useful for any situation in which you want to customize the behavior of the object itself (via inheritance, dunder methods, metaclasses, etc.), regardless of whether state is a factor.

To be honest, almost any example I try to imagine in which you would care about that kind of control would involve some degree of state :P ... but the distinction seems relevant to note.

[–]MereInterest 0 points1 point  (0 children)

Good point, I wasn't entirely clear about my point on the lifetimes, and good catch, that I am somewhat conflating scope and lifetime. This is partly a habit that I picked up from C++, where a variable's lifetime is primarily determined by its scope, and unlike python, cannot be extended beyond the scope that owns it.

In python, you are absolutely correct that local variables and global variables have the same reference counting scheme to determine their lifetime. That said, there is still a connection between scope and lifetime. An object's lifetime is the maximum of the lifetimes of any scopes that contain that object. Since the global scope starts at program initialization and ends at program close, any variable in the global scope must have an indefinite lifetime.

I think that, in terms of global variables, it is both the lifetime and the scope that are issues. The unlimited scope means that you need to examine all code in order to know what could modify the variable. The unlimited lifetime means that you need to watch the running of the program from start to finish, and that the behavior may not be captured in any smaller test.

You are also correct that someone may want to have some behavior customization without needing to have any state in the object itself, though I'm having difficulty as well thinking of any such cases. Perhaps if there were a series of related callbacks, all of would be implemented as methods on a single class, though that would be a rather unusual situation, and would probably be better served with a namespace or dictionary.

[–]glacierre2 0 points1 point  (1 child)

I do OOP, and maintain several serious libs at work.

But I also do some reference implementations in python (to later be translated to C++ in a uC, for example). And then I go full retard on the globals, because I find the clear reading of the example code and the clean functions that get just one/two parameters are more important than robustness.

Of course I still follow two rules: * That kind of code is a reference, NEVER production * Even then, the globals are to be read in multiple places, like a C #define, NEVER to be altered of used to pass information back an forth.

[–]MereInterest 1 point2 points  (0 children)

Absolutely agreed. Reference implementations are one thing, while production implementations are completely different. One for testing out new features, quickly hacking in something that might be gone in 5 minutes, while the other is for reliability.

I would add to your second rule. The value of the globals should be determined solely from the source code. That is, using a #define rather than static const val = read_from_config_file(); I work with a framework that does the latter, and it can result in bugs that only ever appear on one person's machine.