16 Feb, 2011, David Haley wrote in the 21st comment:
Votes: 0
Well, let's look at a (non-contrived) example of why this might cause trouble. In some questing systems, quests might be related to one another; for example if you are in quest 1 and take action A, then quest 2 becomes available, but if you take action B then quest 3 becomes available.

In order to work this out and display things to players, you (potentially) need to be looking up multiple quests at a time. Yes, you could finagle it such that you build up state one quest at a time, but now your non-DB logic has to be more complicated in order to deal with (easily avoidable) idiosyncrasies of the DB API.

You identified another problem yourself: you find it brittle, but you wrote it yourself so it's ok (for now). But what if you have to pass this code to somebody else? For that matter, what if you have to revisit it in a year?

You have avoided passing a parameter to quest functions by storing it in a global variable. But you have apparently felt the brittle nature of this solution already. Is having to think about it really worth not passing around that single extra parameter?

Very frankly I could not condone this as a good usage of a global, because it limits quest look-ups to one-at-a-time, fragile operations. It makes API users think about things that are irrelevant (am I accessing stuff in the right or wrong order here?). It makes for relearning every time you revisit the implementation. All of this for nothing more than not having to pass a parameter to 6 functions.

If we were talking about an order of magnitude speed-up in a performance bottleneck, maybe we would allow ourselves some complexity because the gains are truly significant. Complexity is not necessarily bad, but complexity without sufficient payout is not good. In your example, I'm seeing plenty of complexity (and you apparently experienced it yourself) but I'm not seeing gain beyond not having to pass a parameter. Were there other things you found to be positive as a result of having a global?
16 Feb, 2011, Runter wrote in the 22nd comment:
Votes: 0
In the past when I dealt with quest systems I associated the data with something incidentally passed around, anyways. In my system quests were always associated with player(s) actively on the quest.

def quest_function(player)
player.quests
end

def quest_function_without_player(quests)
quests
end

quest_function(player)
quest_function_without_player(player.quests)


I dunno. I just don't see the problem with functions expecting the data passed they need. I also don't see how it's more convenient to do something like:

$quests = Array.new()

def quest_fun
$quests
end

def quest_fun_2
$quests
end

$quests = arbitrary_query_function
quest_fun()
quest_fun_2()
16 Feb, 2011, sankoachaea wrote in the 23rd comment:
Votes: 0
"Usually, you do not need to delete global variables; if your variable is going to have a short life, you should use a local variable."
1.3 Global Variables, Programming In Lua, 2nd Ed.

That's enough for me, but if you're looking for more – besides the concurrency issues David mentioned if globals can be accessed by multiple threads of execution (requiring synchronization that is all too often neglected) and the not-so-obvious-to-hobbyists problem of readability and non-locality (source code is easiest to understand when the scope of its individual elements are limited - global variables can be read or modified by any part of the program, making it difficult to remember or reason about every possible use) that have already been addressed, here are some points I haven't seen yet:

Constraint checking - A global variable can be get or set by any part of the program, and any rules regarding its use can be easily broken or forgotten. (In other words, get/set accessors are generally preferable over direct data access, and this is even more so for global data.) By extension, the lack of access control greatly hinders achieving security in situations where you may wish to run untrusted code (Who doesn't have this problem? Raise your hand, please.)

Memory allocation issues - Some environments have memory allocation schemes that make allocation of globals tricky. This is especially true in languages where "constructors" have side-effects other than allocation (because, in that case, you can express unsafe situations where two globals mutually depend on one another). Also, when dynamically linking modules, it can be unclear whether different libraries have their own instances of globals or whether the globals are shared.

Testing and confinement - Source that utilizes globals is somewhat more difficult to test because one cannot readily set up a 'clean' environment between runs. More generally, source that utilizes global services of any sort (e.g. reading and writing files or databases) that aren't explicitly provided to that source is difficult to test for the same reason. For communicating systems, the ability to test system invariants may require running more than one 'copy' of a system simultaneously, which is greatly hindered by any use of shared services - including global memory - that are not provided for sharing as part of the test.

Ever heard the phrase, "namespace pollution"? Global names are available everywhere. You may unknowingly end up using a global when you think you are using a local (by misspelling or forgetting to declare the local) or vice versa. Also, if you ever have to link together modules that have the same global variable names, if you are lucky, you will get linking errors. If you are unlucky, the linker will simply treat all uses of the same name as the same object. A program with many global variables often has tight couplings between some of those variables, and couplings between variables and functions. Grouping coupled items into cohesive units usually leads to better programs.

Sometimes the convenience of global variables outweighs the potential problems:
(a) In very small or one-off programs, especially of the 'plugin' sort where you're essentially writing a single object or short script for a larger system.
(b) When global variables represent facilities that truly are available throughout the program, their use simplifies the code.
© Some programming languages provide no support or minimal support for non-global variables.

Even still, it would probably be better to use an alternative like hidden globals or dependency injection (private 'static' variables in classes and 'static' variables in '.c' files and variables in anonymous namespaces in C++). In ANSI C++, the use of 'static' to declare a function/variable to be at file scope (opposite of 'extern') is deprecated in favor of anonymous namespaces (though still supported, and 'static' is still used to declare a variable within a function to be common to all invocations of the function, the opposite of automatic, and indicates that a variable class attribute (data member) is to be common to all objects of the class and also that a class method doesn't have a this pointer - instead, it acts like a free function rather than a member function).

These are really bad reasons to use globals:

"What's a 'local variable'?"
"What's a 'data member'?"
"I'm a slow typist. Globals save me keystrokes."
"I don't want to pass it around all the time."
"I'm not sure in what class this data belongs, so I'll make it global."

One of the major arguments against global variables is that they defy the basic OO concept of cohesive methods. With a global variable all of the accesses and operations on the variable are scattered around the code. By pulling all of the methods into a single class or module, they can be evaluated and maintained as a unit. If you find yourself thinking, "Gosh, I need this in a lot of different places (so I'll make it a global)," chances are, those different places are conceptually related, so you can create a class, a namespace or some other higher-level organization unit to represent that relationship.
17 Feb, 2011, Lyanic wrote in the 24th comment:
Votes: 0
Bravo, sankoachaea. *applauds*
17 Feb, 2011, sankoachaea wrote in the 25th comment:
Votes: 0
Runter said:
In the past when I dealt with quest systems I associated the data with something incidentally passed around, anyways. In my system quests were always associated with player(s) actively on the quest.

Smart! :cool:

Runter said:
I dunno. I just don't see the problem with functions expecting the data passed they need.

There isn't one. Unless you're passing a structure as a parameter. If you're passing a large data structure to a function, its usually better to pass by reference, and if the function isn't modifying an argument (of said passed structure) then pass by a const reference.

#include <iostream>

// define a structure
struct X
{
int m;
};

void function(X arg) // a function with a structure as a parameter
{
std::cout << arg.m << '\n'; // prints 42
}

int main()
{
X obj = {42}; // create an instance of type 'X'
function(obj); // pass 'X' to 'function()'
return 0;
}

void function(X& arg) // pass by reference
{
// etc
}

void function(const X& arg) // pass by const reference
{
// etc
}


also, by pointer parameter which is called a little differently

void function (X *arg) // by pointer parameter (or const X *arg if applicable)
{
// etc
}


X obj = {42};
function(&obj);
17 Feb, 2011, Runter wrote in the 26th comment:
Votes: 0
FWIW, You always pass by reference in Ruby. :p
17 Feb, 2011, David Haley wrote in the 27th comment:
Votes: 0
When you have a singleton pattern, it can – sometimes – make sense to just store the (instantiated) singleton in a global. It only exists once, after all, and it can avoid the somewhat awkward MyClass::GetSingleton() call. This is mainly about saving keystrokes, but if you're doing something in a very tight inner loop it can kindasortasometimes matter for performance too.

I'm not sure that I 100% buy the argument about access control, because a global that'd hidden behind getters/setters still suffers from problems of reuse etc. Yes, it does let you (maybe) enforce security w.r.t. untrusted code, depending on the language you're using. Having getters and setters would not prevent you from running into the problem we saw here, for example (reuse of an already in-use result set) – unless the getters/setters had additional state to track whether or not the result set was already in use (but then you've just increased complexity even more, and force clients to properly release their result set when finished).

I guess my view is that globals are iffy and dangerous enough that if you really need to use them, you should make it explicit that it's "dangerous" by forcing low-level access. In code I (and others I know) write, high-level access is considered safe, but when you run into low-level constructs it's a sign that something weird might be happening and you should pay attention. This requires some amount of discipline, naturally, when you write code, making sure to clearly separate module-user-friendly code from implementation details or efficiency decisions.
17 Feb, 2011, plamzi wrote in the 28th comment:
Votes: 0
In the specific example I gave, the language is C, so the closest thing to a "class" is my quest.c file, where I put a MYSQL ROW as a global. As I explained, this global is used to search through a Quest index which is persistent, and by most opinions here earns its global status. Because of the way I've constructed the quest system, with lots of nested functions (which look very clean, btw, and can be reused in different order), I believe the ROW global also earns its global status. I only need it to be available to the quest functions but there's no such thing as a quest class where it can be "this.current_quest_row", so it's global to everything. Think of the "current quest" as a top-level property of the class "quest", a property which is static while any of the class "methods" unfold.

Of course, I could have diligently bounced a reference to ROW around a dozen or so functions. But, like I've said before, that just seems to me to be more bureaucratic than necessary. I haven't heard anything so far that would compel me to revisit that code, which is working nicely and is completely stable. And, even if I'm writing in an object-oriented language, I'd still use (as I have) a top-level class variable to save me time from passing widespread vars around, from having to return result sets rather than one return value, etc.

I'm an average-speed typist. Over the past year, globals and other shortcuts have saved me enough keystrokes to make players believe that my MUD is run by a small company. Hell, yeah, I'd do it again.
17 Feb, 2011, Scandum wrote in the 29th comment:
Votes: 0
For my mud I use a global data structure called mud, so I've got mud->time, mud->first_player, mud->first_area, etc, about 80 nested variables total, and 1 global variable. It gives the ease of use of global variables, without the worry about rogue variables.
17 Feb, 2011, David Haley wrote in the 30th comment:
Votes: 0
Well, if there's one thing that's been made clear by people here, it's that speed of typing should never, ever be used as an excuse for something that can cause trouble. I'm surprised that you resented the laziness comment, because that is exactly what you are endorsing now. :tongue:
17 Feb, 2011, plamzi wrote in the 31st comment:
Votes: 0
David Haley said:
Well, if there's one thing that's been made clear by people here, it's that speed of typing should never, ever be used as an excuse for something that can cause trouble. I'm surprised that you resented the laziness comment, because that is exactly what you are endorsing now. :tongue:


Umm, it was a joke?
17 Feb, 2011, David Haley wrote in the 32nd comment:
Votes: 0
Umm, it looked like a not-so-joke statement. :smile: Recall that text as a medium kind of sucks.
17 Feb, 2011, Runter wrote in the 33rd comment:
Votes: 0
Just because C doesn't have classes doesn't mean you can't include data on structures…
17 Feb, 2011, sankoachaea wrote in the 34th comment:
Votes: 0
David Haley said:
I'm not sure that I 100% buy the argument about access control, because a global that'd hidden behind getters/setters still suffers from problems of reuse etc.


David! I really appreciate how much thought you give to this stuff. My post was already lengthy as-is, so I didn't want to start adding more.. that said, my caveat to hidden globals: this solution cages and localizes globals rather than tames them - you'll still get bitten when it comes to concurrency and modularization and testing/confinement, but at least all such problems will be localized and easy to repair, and there won't be linking problems.

Singleton patterns, stateful procedures and hidden globals are alternatives that still suffer in some of these areas. Context objects and dependency injection are probably the best options.. or a database. Singletons are a necessary evil – but even still, I prefer to access the get_singleton() (or whatever) method rarely, and pass references to the singleton around. That way, if the singleton ever multiplies, I have less code to refactor. I would not consider a singleton as a global variable unless the singleton exposed its internal variable through an accessor. Absent accessors, a singleton provides controlled access to the variable, greatly reducing the programming problems.

I think, all in all, we're very much in agreement - I tried to supply some supporting information to my claim, but the overall point stands - avoid using globals. :D

@Runter: I've not yet taken the plunge into Ruby (I sold my soul to Rio on Lua) but I want to – moreso from all your great examples. :] I tend to demonstrate things in C++ or pseudocode rather than languages I'm unfamiliar with.

@Scandum: Nothing wrong with that at all.

@Plamzi: That's terrible, really. As David said, speed of typing shouldn't be a factor in engineering decisions. Get that nifty Windows IDE that does most of the typing for you. Edit: It didn't look like a joke to me either.
17 Feb, 2011, David Haley wrote in the 35th comment:
Votes: 0
I agree with you Sanko; it's true that if at least access is put through a bottleneck, you'll have more control than if access is free-form. As you say, you still can get bitten, but you get bit less deeply perhaps. :wink:

I also like to pass a singleton around, if anything because it makes it easier to make code reentrant later down the line if it comes to be a necessity. It's a trivial cost now (just a parameter) and can avoid costly/annoying rewrites later. This is exactly what you said: "if the singleton ever multiplies, I have less code to refactor."
17 Feb, 2011, sankoachaea wrote in the 36th comment:
Votes: 0
Runter said:
Just because C doesn't have classes doesn't mean you can't include data on structures…


Who's/what comment prompted this?

David said:
It's a trivial cost now (just a parameter) and can avoid costly/annoying rewrites later.


I think this is indicative of an attitude more than anything else, one that is common among experienced programmers. Some people write what works, and some people write what's going to work down the line.

It just blows me away how much people minimalize this issue when our entire community is built on code that people wrote and passed on to others.

Edit: It wouldn't let me properly quote you in the 'editor'.
17 Feb, 2011, plamzi wrote in the 37th comment:
Votes: 0
Runter said:
Just because C doesn't have classes doesn't mean you can't include data on structures…


True, and that's a good prescription, as is most of what has been said about globals in this thread. But I'm an enthusiast. So when I look for a project for my MUD and think to myself "All the CircleMUD globals need to be put into neat structures, topically organized." my enthusiasm fades. :cry:

Then I end up plotting up a crafting system just to get my enthusiasm back up. And, chances are, I'll use more globals in that one just to get to a result faster… But that's just my slapdash coding style. I know it can lead down a thorny path for future coders, but hey, maybe one day I'll recruit someone who is enthused by the idea of putting stuff into structures.

David Haley said:
Umm, it looked like a not-so-joke statement. :smile: Recall that text as a medium kind of sucks.


My bad, should have put a smiley face but I have some aversion to emoticons, especially animated ones like they have here. :evil:

There's nothing wrong with text as a medium and I think if you'd read my post inside a book, the joke would have been more obvious. But in the context of a web forum, you need a highlighter for jokes. Like emoticons. If only they'd keep still…

But seriously, the point of my joke was to show that listing slow typing speed as an argument for using globals in the ironic list by sankoachaea is a straw man thingie. I don't think anyone uses globals for that reason. They do it either because they don't know better, or because it saves time. I know better, but it does save me time, so I use them. That's the point I was trying to make, and typing speed has nothing to do with it.
17 Feb, 2011, David Haley wrote in the 38th comment:
Votes: 0
sankoachaea said:
It just blows me away how much people minimalize this issue when our entire community is built on code that people wrote and passed on to others.

Honestly, I think that most of the problems with bad code in the world come from people thinking that a given piece of code would never end up being reused, and then nobody ever taking the time to fix it. :smile:

plamzi said:
There's nothing wrong with text as a medium

Well, I meant mainly that it's difficult to read the "subtext" (as it were) because I have no cues from tone, body language, expression, etc. You're right that if I had more context, things would be easier, but, well, such is the nature of forums…

I often think that 95% of forum arguments would be dispelled if we could just go have beer & burgers together or something, and talk about it there…
17 Feb, 2011, Tyche wrote in the 39th comment:
Votes: 0
Runter said:
I thought of you when he said it. :)


I was thinking of you. ;-)
http://www.mudbytes.net/index.php?a=file...

I don't think you need to justify your use of globals either.
17 Feb, 2011, Runter wrote in the 40th comment:
Votes: 0
Tyche said:
Runter said:
I thought of you when he said it. :)


I was thinking of you. ;-)
http://www.mudbytes.net/index.php?a=file...

I don't think you need to justify your use of globals either.


Well, heh heh. I'll admit there's a lot of bad stuff going on there! :p

But to be fair, I literally wrote most of the globals there within a week of learning Ruby.

edit: and fwiw the most up to date versions are cleaned up. I don't even like the global sigil in ruby.. :)
20.0/100