Variables are placeholders in memory that store the values and the results of computations. There are five types of variables: constants, global variables, class variables, instance variables, and local variables. Each of the variable types has a different scope, use, and lifetime.
In this tutorial, we’ll explain global variables.
2. What Are Global Variables?
A global variable is a variable that is declared outside any function and is accessible to all routines in our program. So, its scope is global: it’s initialized when we run our software and lasts while the program is running.
We mostly declare global variables at the top of a module file. Any function we define at any point in the program can change any global variable.
For example, in the following Python code snippet, companyName is a global variable:
companyName = "My Company" def printName(): print(companyName)
The function printName() can access the global variable companyName even though it is declared outside the function.
3. Why Do We Use Global Variables?
We can use global variables for many purposes such as for storing constant literal as that improves our program’s consistency.
Further, since we can access global variables from any function in a program, we need to declare them only once, which shortens our code.
Therefore, global variables make coding easier and reduce the time to production.
4. What Are the Problems With Global Variables?
However, they don’t come without shortcomings.
4.1. Unintentional Changes
The major problem with them is that any change in their values is propagated to the entire program. Such a change is often done by mistake or is a side effect of some other action. In turn, that can result in subtle programming errors that cause unexpected behavior.
For example, let’s say that we have a function storeUsername() that updates the global variable username and another function checkAccess() that uses the global variable username to check the user access for a resource. Further, let’s say the user inputs their username in a language set other than the one we use and the function storeUsername() doesn’t check that. Then, we could get an unexpected result since checkAccess() may or may not fail:
username = "Admin" def storeUsername(name): username = name def checkAccess(resource): if username = 'Admin': return True else: return False
We can find it extremely difficult to track down such errors. On the contrary, we can easily track down similar errors that involve local variables. This is so because local variables have only local scope.
4.2. Global Variables Reduce Modularity and Flexibility
We make the code less modular, less flexible, and less scalable when we use global variables.
For example, if two modules share a global variable, we can’t modify one without considering how that affects the other.
Additionally, global variables often hide design flaws since they allow us to deliver the code quickly. For that reason, we often don’t perform any checks on global variables, which can lead to many unforeseen errors later in our code’s life cycle.
Scaling can also lead to an unintended change in a global variable that introduces a bug. Because of the global scope, we can find it very difficult to trace back the error in the code. That could result in code leaks, downtime, and loss of productivity.
5. How to Avoid Global Variables
Let’s check some alternatives to global variables.
5.1. The Complete-Function Approach
Instead of using global variables to share data between a function and its caller, we can write functions so that they receive all the required information at the input and return all their results to the caller. That way, we provide all the inputs needed by the function to run. And no other entity can change any of this function’s input in any way. This way, we can track any error to its source.
For example, let’s say we have a global variable a and functions func1(), func2() and func3(). Further, the functions func1() and func2() update a, and func3() divides its input by it. If any of the former two functions set a to 0, we’ll get a division-by-zero error in func3() but wouldn’t be sure what causes it:
a=1 def func1(x): a = x def func2(x): a = x def func3(b): return b / a
In contrast, if we pass all the variables, including a, as arguments, we’ll know that any division-by-zero error is due to func3() getting zero as its second argument:
def func1(a, x): a = x def func2(a, x): a = x def func3(b, a): return b / a
5.2. Dependency Injection
We can bridge any dependency using an explicit input to a function. For instance, let’s consider a function that sends a message to a queue after some processing. Instead of keeping queue credentials as global variables, we can pass them to the function at the input. That way, we meet all dependencies for this function prior to its execution so that it behaves exactly as intended.
We should encapsulate data as much as possible. That means that we should define all the attributes that a class needs inside that class and use explicit methods to access or modify them.
If we store some data in global variables, we risk unintentional changes that lead to errors that are hard to trace.
5.4. The Singleton Design Pattern
Furthermore, we can initialize objects only once and then use them only within a context. We can achieve this using the singleton design pattern wherein, we can declare a singleton class and ensure that only one object is created and initialized once.
In this article, we talked about global variables. We should use them sparingly since they can introduce bugs and reduce our code’s modularity. However, we can safely use global variables to share constants across functions.