Avoid Global Variables, Environment Variables, and Singletons

Update: There is a newer version of this page on the singleton anti-pattern on my new website; however, the new article does not touch upon global variables or environment variables, and thus I will keep up this old page for the time being.

This advice should be obvious. All three of these involve shared, mutable state. Whenever shared mutable state is involved, it is easy for components to step on each other's toes.

Global Variables

This article, entitled "Global Variables are Bad", spells out plenty of reasons why you should avoid using global variables. I won't repeat all of the reasons here -- you should read the linked article --, but just to reiterate some of the most important (or what I found to be the most important) reasons to avoid global variables:
  1. Global variables can be modified by any component, so if something used to work and then a new component is introduced that inappropriately modifies the value of the global variable, everything can break just as the result of one single component modifying a global variable when it should not.
  2. Components that are supposedly separate may end up interacting with each other through a global variable, thus two components that validate in tests when used separately may fail when the components are linked together in the same environment (where their reference to the global variable resolves to the same global variable).
  3. Global variables are accessible to all threads of execution. Using globals makes code automatically non-reentrant. Unless appropriate locking is used, the code will also not be thread-safe. Even if appropriate locking is used, locking reduces parallelism and incurs overhead, so a design that does not require any locking is superior. And even if communication and synchronization would be needed without a global variable, the use of global variables makes the need for this synchronization non-obvious and extremely error prone, because you have to track down every single possible case where the global variable is referenced and ensure that each and every one of those places locks it appropriately.

Environment Variables

Environment variables are global variables, but on a grander scale. Whereas global variables generally refer to variables that are scoped to a process, an environment variable is scoped to multiple processes, generally in the context of a login session or terminal session. All of the reasons to avoid global variables apply to environment variables. In addition, environment variables are not user-friendly and not cross-platform; it is not appropriate to require users to set environment variables, especially when the manner of setting the environment variable varies not only from platform to platform (e.g. Windows vs. Linux) but also from configuration to configuration on a single platform (e.g. BASH vs. CSH vs. some other login shell).

Environment variables are overused and abused for configuration purposes. Using the default native preferences/settings/configuration mechanism (registry on Windows, PLIST files in ~/Library/Preferences or NSUserDefaults on Mac OS X, and GConf on Linux) or a set of configuration files is a better and more user-friendly way of configuring software. For C++ programmers, the Qt Framework provides a QSettings class which makes it easy to  load/store preference data in the default preferred native mechanism but in a cross-platform manner. For Java programmers, the Preferences class provides equivalent functionality, making it easy to load/store configuration data without resorting to environment variables.

Singletons

Singletons are global variables in disguise. Somehow Singletons got into a design course, and now -- although most programmers automaticaly avoid global variables -- many think that Singletons have an exalted and overused place in the list of design patterns. The Dependency Injection Myth, the Root Cause of Singletons, Singletons are Pathological Liars, and Why Singletons are Evil all give excellent reasons to avoid the Singleton anti-pattern (yes, it is an anti-pattern). The solution to the singleton anti-pattern is to use dependency injection.

Dependency injection can be done without a dependency injection framework, simply by making your singletons instantiable like normal classes (eliminate the private constructor, the static singleton instance variable, and the singleton static construction function), instantiating only one instance of that class in your main function, and then passing it to whatever objects need it using some interface that your singleton class implements. Although dependency injection is completely doable without a dependency injection framework, using a dependency injection framework will make your life much simpler and easier, so you might as well go for it. If you are coding in Java, then Guice is definitely the way to go.

If you'd like to know more about Google Guice, you may be interested in watching the following two videos about the Google Guice dependency injection framework. Even if you are not interested in using the Google Guice framework, you may be interested in watching these videos as they explain why singletons (with private constructors and static construction methods) are bad, as well as what dependency injection is, and how the use of dependency injection solves the problem of using the singleton anti-pattern.

Google Techtalk: Java on Guice - Dependency Injection the Java Way

Google I/O 2009: Big Modular Java with Google Juice

Comments