What Are The Alternatives To Using Global Variables?
Global variables can be useful, but they can also be dangerous to use. Although they can seem convenient, they have a number of major drawbacks. This lesson outlines the reasons you may wish to find alternatives to using globals and then shows a number of options in LiveCode that allow sharing of data without making it fully global.
Why Globals Are Bad (Mostly)
Global variables in LiveCode are declared using the global command. They are variables which can be accessed from anywhere in the running application, i.e. any stack or object that is running on the same LiveCode engine. Globals are not associated with any object or script.
It often seems convenient to use globals as they generally require less initial coding than the alternatives, and seem simpler at first. Their disadvantages tend to emerge later on when something goes wrong and you are trying to debug the program, or when trying to change the code in the future. Here are what I consider the main problems with globals in LiveCode (and in all other languages):
1. Global Scope - Because global variables are not associated with any object, they can be accessed by any part of the program. This makes it difficult to figure out which parts of the program depend on a particular global, and what effects changing it might have.
2. No Access Control - Global variables can also be changed by any part of the program. This means that if the program breaks because a global has the wrong value, it can be hard to track down how this happened. This is a debugging headache, but can also cause security problems in some applications.
3. Lack of Encapsulation - If alternatives to global variables are used, it tends to lead to more modular code, as handlers will be grouped depending on which data sources they use. Extensive use of global variables often leads to monolithic scripts.
4. Namespace Problems - The scope of global variables means that each one must have a unique name in the context of the entire program. It is quite easy when coming back to the code after a few months to accidentally re-use the same global name for a totally different purpose. This can be very painful to track down. This namespace problem can also mean it's hard to come up with good, concise names for your global variables.
Globals are not always bad. Sometimes their convenience and efficiency can outweigh the above drawbacks. However, I have developed using LiveCode for 5 years and have only very occasionally used global variables in my own code.
What Are The Alternatives?
In LiveCode there are three basic alternatives to using global variables. It is possible to create any functionality you need in the language without using globals by using these techniques.
Use Local Variables Instead - This is the most obvious alternative, is more flexible than it seems, and often appears to be overlooked. This solution eliminates all the drawbacks mentioned above, although its still possible to have namespace issues in complex applications. There are a couple of ways to make a local variable do the job that was previously done by a global, these will be examined in the next step.
Use Custom Properties - A custom property in LiveCode is a piece of public data associated with an object (e.g. with a stack, button or group). This can be accessed anywhere in the application, solves the namespace problem and also helps a bit with some of the other issues mentioned above.
Use Virtual Custom Properties - A "virtual" custom property is a property of an object implemented using GetProp and SetProp handlers in the object script. This is a little more complex than using normal custom properties, but has some distinct advantages.
Use Local Variables Instead
If your global variable is only ever accessed from one script, make it a "script local" instead. This instantly safeguards your application from issues associated with global variables and requires no additional coding. In practice though, this situation is rare, as you probably wouldn't have used a global to store such data.
If the global variable is referenced in multiple scripts throughout your application, you can either use one of the custom property based approaches detailed below, or you can use a script local in the stack script, combined with a set of handlers to manipulate it. Let's suppose you have a global variable called gState, which is used in various places in your application. To remove this global, consider the following code:
local sState command setState pValue put pValue into sState end setState function getState return sState end getState
The idea is that the global variable gState is replaced by the local variable sState in the script of your main application stack. All references to the variable in the application can be replaced with calls to either setState or getState. It is possible to further augment this by adding extra handlers, for example an appendState command could allow data to be placed after the variable.
This is quite a useful technique and the convention of prefixing the handlers with "get" and "set" will make it quick and easy to understand how this code works in the future. If you need to debug the application, you can put a breakpoint in the setState routine to find out why the wrong value is being placed into it. Another advantage is that you can put extra logic in the getter and setter handlers. For example the getState handler can perform verification of the new value. It's also possible to hide the details of how the data is stored, allowing you to change it in the future. You could even store it in an external preferences file rather than a variable.
When it comes to refactoring or modifying your code, you can easily find all references to the variable sState by searching through the stack script. The idea is that only the setState and getState handlers access it, but in case others do by accident, you can easily track them down.
There are disadvantages with using getter and setter handlers in LiveCode. They can be nice and useful at times, but they are a little ugly to use and also you need to make sure that they are in the message path to avoid having to use things like Send and Value. Both of these two problems can be avoided by using the Virtual Custom Property approach described later.
Use Custom Properties Instead
If you are not familiar with custom properties in LiveCode and their use, there is an excellent lesson explaining them here: How Do I Define My Own Properties For An Object.
Any global can be replaced by a custom property, immediately removing the namespace pollution problem and reducing the impact of the other global variable issues.
Sticking with the gState example from the previous step, it is easy to simply replace all references to gState with a custom property, for example the uState of this card or the uState of this stack. No further code is required in theory.
The namespace problem is solved because the global data is now associated with a particular object (the card or stack, or something else). It is still possible to accidentally use a clashing property name elsewhere, but it's easier to write your code without having to come up with increasingly intricate names for global variables. Instead just make them properties of different objects.
The access control problem is not solved. However the access situation is better here, because if you need to debug the state data, you can add a SetProp handler and use this to catch all changes made to the data in the debugger (unless messages are locked).
Something to be careful of when using custom properties is that their values persist when you save a stack. This means that you can accidentally save your application with a pre-set value in a custom property which might result in it not behaving properly. This problem is avoided by using the virtual custom property technique described below.
Another issue with using custom properties which is common to both the simple method and the virtual custom property method is that of mutating the data. If you frequently want to place data before or after the global variable, or add numbers to it, you will need to use code patterns like this:
command appendToState pValue get the uState of me put pValue after it set the uState of me to it end appendToState
This is unlikely to be a problem unless you are carrying out a very large number of these operations, in which case it can be more efficient to use the script local solution described above.
Use Virtual Custom Properties
This method solves all the problems associated with global variables and doesn't suffer from the drawbacks of the two previous methods. However it does have a couple of problems of its own.
A virtual custom property is used in the same way as a normal custom property. The only difference is that it is implemented by creating a GetProp and SetProp handler, which then store the value in a script local (or in a real custom property, text file etc if required). Note that this method is almost identical to using a script local with getter and setter handlers, described above.
Here is how to replace the global gState with the virtual custom property uState of whichever object you like:
--global gState on mouseUp --put 1 into gState set the uState of me to 1 end mouseUp local sState setProp uState pValue put pValue into sState end uState getProp uState return sState end uState
Using virtual custom properties like this doesn't suffer the message path issues found with using getter and setter commands, as any object can set or get the property. It doesn't suffer the problem of accidentally saving the stack with a property pre-set, as the actual data storage is still a local variable, and it allows you to debug calls to the getProp and setProp handlers (and also put logic in them to do verification, pre-processing etc).
This is probably the best all-round method for eliminating global variables, however there are some drawbacks to think about. If you are frequently mutating the value, then using a script local variable could be better, as it allows you to easily add or subtract numbers from it, place text before or after it etc.
Another issue to watch out for is locked messages. If messages are locked, it means that your GetProp and SetProp handlers will be bypassed, and instead a real custom property with the same name will be set on the object. Another unexpected problem is infinite loops. These can be caused because GetProp and SetProp handlers go through the normal message path like Commands and Functions do, which is something that's quite easy to forget. An explanation of this is available in the GetProp documentation.
One final thing: it's not possible to pass multiple parameters to the GetProp and SetProp. This is possible with the getter and setter handlers, although is unlikely to be a problematic limitation.
Thank you for this very helpful discussion. However, I am puzzling over how to apply it to an array variable. As far as I can see, it is not practical to use getter and setter routines for an array variable (since the variable name itself is variable). That would appear to leave custom properties as the only option for a global array variable. Would I be correct?
The actual application I am thinking of is for an application's settings. I would like to store the settings as a json file, then read them into the array when the application loads. Obviously, they need to be available globally throughout the application, so some sort of global variable solution is required.
A settings variable of some sort is such a basic requirement for almost any application, so I wonder if there is some preferred method for handling it.
You could use a script local array variable on the stack script and use getter and setters to access it. Something like
function getPreference pName
command setPreference pName, pValue
put pValue into sPreferences[pName]
Alternatively you can use a custom property set with getProp and setProp handlers. If you are setting a custom property that belongs to a custom property set you use array notation in the definition e.g.
setProp cPreferences[pProperty] pValue
set the cPreferences[pProperty] of me to pValue
return the cPreferences[pProperty] of me
For more on custom property sets have a look at the Custom Property section of the User Guide. You can find this in the Help menu.
I hope that helps.