I always felt irritated whenever I had to maintain software with global variables. Global just annoy you because you have to or to know what is going on. Wondering about the usage and ways to skip the use of global I stumbled upon a blog post. The following is the gist of that blog post. However you can go to the post from the references section of this post to know more.
Why Global Variables Should Be Avoided
- 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.
- No Access Control or 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 (such as working with 3rd party plugins).
- Implicit coupling — 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.
- Concurrency issues — if globals can be accessed by multiple threads of execution, synchronization is necessary (and too-often neglected). When dynamically linking modules with globals, the composed system might not be thread-safe even if the two independent modules tested in dozens of different contexts were safe.
- 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.
- Memory allocation issues — Some environments have memory allocation schemes that make allocation of globals tricky. This isespecially 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.
Alternatives to Global Variables
- ContextObject — allows you to package up and abstract global dependencies then move them around in a program, effectively operating like global variables but far easier to override and manipulate locally. Unfortunately, most languages provide no support forContextObjects, which requires one “pass it around all the time”. Threading of a ContextObject through the code is aided significantly by DynamicScoping/SpecialVariables, or by syntactic sugar of the sort used for Haskell monads, or (to far a lesser degree) by use ofThreadLocalStorage. Related: ExplicitManagementOfImplicitContext.
- DependencyInjection — the ability to set up complex object graphs in a program greatly reduces the need to pass ‘variables’ around that carry global/context information. The strength of this approach is visible in paradigms that make much less use of globals, such asDataflowProgramming. Some languages (such as Java) have mature DependencyInjection frameworks that often work in a somewhat static manner (e.g. from a configuration file, or not integrating live objects) – and that alone is enough to match many common uses of ‘globals’. FirstClass support for DependencyInjection and construction of dataflow or object graphs additionally allows one to compose complex systems on the fly at runtime (allowing a means of composition for object primitives that is missing in traditional OO languages), and additionally allows an enormous range of optimizations wrgt. dead-code removal, partial evaluation, etc. making this a rather attractive alternative to globals.
- Hidden Globals — hidden globals have a well-defined access scope, and would include, for example, private ‘static’ variables in classes and ‘static’ variables in ‘.c’ files and variables in anonymous namespaces in C++. 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.
- Stateful Procedures — a global set of setters and getters or operations that act over what is, implicitly, underlying state. These suffer many of the problems associated with globals with regards to testing, concurrency, and allocation/intialization. However, they offer improved access control, opportunity for synchronization, and considerable ability to abstract away the implementation (e.g. one could put the global state into a database).
- SingletonPattern — construct an object globally, allow access to it via a stateful procedure. SingletonPattern offer convenient opportunity for one-time specialization of a global based on arguments and environment, and thus may serve well enough to abstract ‘resources’ that are truly part of the programming environment (e.g. monitors, speakers, console, etc.). However, SingletonPattern doesn’t offer anywhere near the flexibility offered by DependencyInjection or ContextObject, and are on par with stateful procedures with what they help the programmer control and the problems users will still face.
- Database or TupleSpace or DataDistributionService — sometimes global variables are used to represent data. This is especially the case for global maps, global hashtables, global lists. To a lesser degree, it is also the case for task queues. For these cases where the data truly is ‘global’, especially if any part of the program is dedicated to pushing pieces of this global data to external users, using a dedicated system will greatly simplify the program and likely make it more robust at the same time.