I open a script I did not write
And sigh or scream when I often find
That dreaded keyword which gives me such fright,
Perhaps I should not be so kind?
Why do they stack them a dozen high,
And why don’t they just put them in a struct,
When they are just three letters long I sigh
And think that I am really fucked.
Just put them in local scope,
Oh my god who wrote this shit
The rest is better I sincerely hope
Or I will have to refactor it.
It is of globals I write this ode,
So please keep them out of your code.
[hr][/hr]
I’m putting together a guideline for when globals are acceptable. I see them overused, especially by people not experienced or interested in code design, and I hope after reading this you’ll use them more strategically.
This is written with Max in mind but should be relevant whatever 3d package is used.
Why are globals bad?
[ol]
[li]Naming conflicts: These can be really nasty though infrequent. If you declare something a global, and someone else uses the same variable and doesn’t declare it a local (maybe in some small script or something, or they just forgot/commented it), they will be using an incorrect value, and/or so will you. This is especially a problem with people who use globals to track UI settings. You can prefix your global with a bunch of letters, but this is inconvenient to type and makes your script a big pain to read.
[/li][li]Tracking and debugging: You generally want your script to interface with another in very specific ways- you don’t want to set the same pool of globals with two scripts, usually, or use them as intermediates. This makes it very hard to track and debug code. Code design should be very obvious and explicit. If you need to store intermediary data, storing it as a bunch of globals is not ideal because it is not straightforward.
[/li][li]Readability: Globals make code hard to read. Declaring a bunch of globals, changing those globals, then using them, even in the same script, is not easy to read. Say you have ‘g_var = something.state’, and then later ‘if g_var then ()’, you need to find where you set g_var to read the code properly. And the most recent place it is set, since it may be set in various places.
[/li][li]Refactorability: If you don’t understand why refactoring code is necessary, you probably use globals too much. All the above factors influence one another, but also make refactoring a huge pain. Unnoticed dependencies may have appeared to do the global (not always directly using the global but can be consequences). Taking the global out/renaming it can cause bugs. Not being able to track or debug code means the errors you make during refactoring are more difficult to correct. Not being able to read code easily means understanding how to make a good refactor, and the rate of refactor, are both negativity affected.
[/li][/ol]
Global-addicts may not see these as especially pressing issues. But go back to global-ridden code you’ve written a few months ago that you haven’t been in since, and try to edit or add to it, or use it as a foundation for something. The vast majority of code development time is spent bugfixing and maintaining, not in the actual first writing. Saving a few minutes development time by using globals will cost you exponentially more later on. And not always your development time- often that of your coworkers or even people you don’t know who are going to use your code.
Here are scenarios when globals are required or useful:
[ol][li]execute strings: execute strings are in a global scope, with no way around it. So no matter where the ‘execute’ statement is, it will be global. Just be smart about it, and never use execute unless you really need to (which is rare).
[/li][li]Communication: You may not think of it as such, but functions are often in global scope as well, even without declaring them ‘global’ outright. It is much more common to communicate between code with functions than variables, so somehow people seem to think global functions are alright. While they are more acceptable, they should still be avoided. (The reason they are more acceptable is because functions are not modified outside of their definition, so it is a much more linear matter to see which function a statement is using, whereas a global variable can be fried, poached, or scrambled without you knowing). But you’ll need globals of some sort to communicate between two blocks of code who are children of global scope.
[/li][li]structs: You’ll still need some globals, obviously. The best way to minimize, or even eliminate, the negatives is to limit globals to structs. Using structs will help you eliminate global use almost entirely, except for the structs themselves. Think about this. A struct should describe an idea: string functions, functions for a tool, etc. These concepts are generally on a much higher level than functions or variables. For example, ‘setOptions’ or ‘facesToDelete’ are common function/variable names that can be used by many different scripts. So, to differentiate, you could say ‘skinFn_setOptions’/‘renderFn_setOptions’ and ‘g_toolA_facestoDelete’/‘g_toolB_facesToDelete’. You’ve now avoided naming conflicts but still have every other negative of globals. Instead, you could have a struct for skin/render functions, and toolA/toolB. This avoids naming conflicts (you should never have two ‘skinOps’ structs, for example). Tracking and debugging is much simpler because everything is much more centralized and organized. Readability is improved, as it uses a very standard ‘.’ which is the essence of classes in the first place and everyone understands. Plus no pointless prefices. And refactorability is innately improved because all the other problems are fixed- plus the better code design and layout implicitly lends itself to easier refactorability and maintenance.
[/li][/ol]
Here are scenarios when you should never use globals:
[ol]
[li]Storing UI settings: Never, ever, use globals to store UI settings. To remember settings through sessions, always store them in a struct. To read a UI, access the UI itself, or even better when communicating between two unique tools, communicate via data stored in structs.
[/li][li]As locals: If a variable doesn’t need to be global, never ever declare it as such. The only time it needs to be global is for communication between unrelated scopes. I see plenty of scripts with globals haphazardly declared. Very bad practice. If they are necessary, make them local, but more likely you can find a better way (it is rarely needed to declare a bunch of locals for a rollout or macro, for example).
[/li][li]Inside functions: There are very, very few instances where this rule can be broken. Think about this. If your function’s scope is not global, you should never need a global inside of that (just declare the local above the function and you can use it in the function… but be careful with this as well). So, you now have global vars being used in global functions. In which case you now have a ‘system’ of variables, functions, and scripts, in which case it should certainly NOT be in global scope. I don’t think I’ve ever found a case where globals must be used inside a function- there has always been a workaround, which ends up making much more sense than the global route.
[/li][/ol]
And as a little addendum, something mentioned above:
[ol]
[li]Pair tools/rollouts with structs: Something I’ve only started doing recently, but has made my code much simpler and straightforward. I have a tool with 5 rollouts. These rollouts refer to common variables and processes, but are all unique rollouts. One way is to use globals, this is probably most common. Don’t do this. Another potential way is to define a ‘master’ rollout, and then all rollouts refer to that. However, this is messy, and prone to initialization and persistence errors. It is also not very flexible- what if scopes or names change, or change the master?
[/li]The best way pairs structs with tools. The struct stores all the common variables and functions, and each rollout refers to the struct. What is even better is, you don’t need to call the variable in the struct when calling a function in the struct, if it uses its local variable. This is a deep topic and deserves its own article, but I hope this sparks ideas.
[/ol]
[hr][/hr]
I wrote this in two chunks so sorry for any incompleteness or discontinuity. Once I get some feedback and other suggestions I’ll put it on the Wiki. The mentioned tool/struct pairing article should be coming soon as well.