In the software engineering community, there's a near universal belief that using global state of any kind is evil because it can lead to undefined behavior and race conditions. It can also makes testing more difficult or impossible depending on the decided scope of the state.
I disagree that it's evil. In some types of projects, especially in low-level systems, global state is required. However, it should always be an absolute last resort.
The issue with global state is neither the state itself nor the application. The problem is developers using and abusing it.
Inserting global state into an application introduces continuous human overhead into the project since it isn’t possible to enforce rules for the variable or object via the compiler.
For your project to remain logical and maintainable in the long-term, everyone working on a project must know:
- That the state exists (and its name to prevent shadowing, if applicable)
- Whether or not the state is safe to access or modify and when
- Developer-determined conventions on when the state may be accessed and modified (most important for testing to remain possible)
- The state's acceptable values at any given time
- That the state must be cleared between tests running in the same memory space
Successful global state requires clear, up-to-date documentation accessible to all developers working on the project.
However, the best documentation is worthless if people don't read it. Some people won’t, so you must be vigilant with code reviews to ensure that your conventions aren’t abandoned.
If you fail to take responsibility for globals, your project will either cause headaches or fail entirely.
At my first job, there was a sore spot in internal testing that frustrated everybody. When our product was first introduced, it was difficult to install, so someone quickly hacked together some Python scripts that used the Robot Framework to set up servers for basic product testing.
First off, it was a perfect example of reinventing the wheel poorly (another sin) since a dev-ops tool could have accomplished the same task more effectively. Worst yet, the project made liberal use of global state to describe the devices-under-test (DUTs) that were being constructed.
It was a design disaster for the following reasons:
- The global state was used purely as a convenience (to avoiding passing data among functions).
- There were no limits or conventions on where the global state could be accessed or modified. All of the modules in the project accessed the same global dictionary.
- Global state was modified within functions that gave no indication data was being modified (side effects).
- The data being modified was a series of nested Python dictionaries, so there was no convenient way to access a schema of what data they contained. Any data could be appended on the fly too.
- Since state modifications were uncontained, no module was independent. It was impossible to test any of the code, let alone its critical path.
This setup code was run at the end of every single product build. We couldn’t submit code into the
develop branch until we got a full pass on builds and tests.
Unsurprisingly, the device setup failed about 10% of the time for all sorts of reasons, primarily exceptions from oversights and bad changes to the setup framework. We could have caught and fixed all of the errors before they happened if the code was testable.
It worked well enough, and we were conpletely swamped with work, so no one had the man-hours to fix it up. When we changed the way the product was installed, it was given a complete overhaul, but the overhead and effects of the poor design were felt for a long time.
Is the human overhead worth the benefit of introducing global state? That’s your call, but remember, bad decisions and mismanagement can topple a project.
- Avoid using global state whenever possible. Explore alternative design patterns first; there’s usually another way to accomplish your goal such that the data has more limited scope.
- Minimize the amount of global state in your application.
- Document your globals thoroughly, and ensure that documentation is readily available to anyone that needs it.
- Ensure that developers are following the conventions in code reviews.
- Never allow unrestricted global state into your project or you may as well call it quits…