Most of us say, (often without really knowing),
"Do not use global variables"
Martin Fowler expresses in one of his books, Patterns of Enterprise Application Architecture , that
"any global variable is always guilty until proven otherwise"
- Why is it bad practice to use global variables?
- Are they really harmful?
- Or is it just
an irrationalprejudiced hatred on the part of the purists ?
Source: mundogeek.net - Global variables
Impact of change
The problem with global variables is that they create hidden dependencies. When it comes to large applications, you don't even know/remember/be clear about the objects you have and their relationships.
So you can't have a clear notion of how many objects are using your global variable. What if you want to change something about the global variable, for example, the meaning of each of its possible values, or its type? How many classes or compilation units will this change affect? If the amount is small, it may be worth making the change. If the impact will be large, it may be worth looking for another solution.
But what is the impact? Because a global variable can be used at any point in the code, it can be very difficult to measure it.
In addition, we always try to make a variable have the shortest lifetime possible, so that the amount of code that makes use of that variable is the minimum possible, and thus better understand its purpose, and who modifies it.
A global variable lasts for the duration of the program, and therefore, anyone can make use of the variable, either to read it, or even worse, to change its value, making it more difficult to know what value the variable will have at any given point in the program. .
destruction order
Another problem is the order of destruction. Variables are always destroyed in the reverse order of their creation, whether they are local or global/static variables (an exception is primitive types,
int
,enum
s, etc, which are never destroyed if they are global/static until the program ends).The problem is that it is difficult to know the construction order of global (or static) variables. In principle, it is indeterminate.
If all of your global/static variables are in a single compilation unit (i.e. you only have one
.cpp
), then the build order is the same as the write order (i.e. variables defined first are built first).But if you have more than one
.cpp
each with their own global/static variables, the global build order is indeterminate. Of course, the order in each compilation unit (each.cpp
) in particular is respected: if the global variableA
is defined beforeB
,A
it will be built beforeB
, but variables from others may enterA
andB
be initialized.cpp
. For example, if you have three drives with the following global/static variables:In the executable it could be created in this order (or in any other order as long as the relative order within each is respected
.cpp
):Why is this important? Because if there are relationships between the different static global objects, for example, that they use each other in their destructors, perhaps, in the destructor of a global variable, you use another global object from another compilation unit that happens to be already destroyed (by have been built later).
Hidden dependencies and test cases
I've tried to find the font I'm going to use in this example, but I can't find it (it was to exemplify the use of singletons anyway, although the example is applicable to global and static variables). Hidden dependencies also create new problems related to controlling the behavior of an object, if it depends on the state of a global variable.
Imagine that you have a payment system, and you want to test it to see how it works, since you need to make changes, and the code is from someone else (or yours, but from a few years ago). You open a new
main
, and call the corresponding function of your global object that provides a bank card payment service, and it turns out that you enter your data and they make a charge. How, in a simple test, I have used a production version? How can I do a simple payment test?After asking other coworkers, it turns out that you have to "set to true", a
bool
global that indicates whether we are in test mode or not, before starting the charging process. Your object that provides the payment service depends on another object that provides the payment mode, and that dependency happens invisibly to the programmer.In other words, global variables (or singletons), make it impossible to go into "test mode", since global variables cannot be replaced by "test" instances (unless you modify the code where said instance is created or defined). global variable, but we assume that the tests are done without modifying the mother code).
Solution
This is solved by what is called dependency injection , which consists of passing as a parameter all the dependencies that an object needs in its constructor or in the corresponding method. In this way, the programmer sees what has to happen to him, since he has to write it in code, saving developers a lot of time.
If there are too many global objects, and there are too many parameters in the functions that need them, you can always bundle your "global objects" into a factory -style class that constructs and returns the instance of the (mock) "global object" you want, passing the factory as a parameter to the objects that need said global object as a dependency.
If you switch to test mode, you can always create a test factory (which returns different versions of the same objects), and pass it as a parameter without having to modify the target class.
But is it always bad?
Not necessarily, there may be good uses for global variables. For example, constant values (the value of PI). Being a constant value, there is no risk of not knowing its value at a given point in the program due to any type of modification from another module. Also, constant values tend to be primitive and their definition is unlikely to change.
It is more comfortable, in this case, to use global variables so as not to have to pass the variables as parameters, simplifying the signatures of the functions.
Another may be non-intrusive "global" services, such as a logging class (save what happens in a file, which is usually optional and configurable in a program, and therefore does not affect the core behavior of the application), or
std::cout
,std::cin
orstd::cerr
, which are also global objects.Anything else, even if its lifetime almost coincides with that of the program, always pass it as a parameter. The variable could even be global in a module, only in it without anyone else having access, but in any case, the dependencies are always present as parameters.
Global variables are a bad idea for at least 5 reasons:
Another problem is that it is terribly difficult to track your changes. It is possible that at some point you create another global variable with the same name, and end up overwriting its value without realizing it, which would generate the most esoteric errors and the most difficult to debug.
In this question from the Software Engineering community you have more information:
https://softwareengineering.stackexchange.com/questions/148108/why-is-global-state-so-evil
Global variables are memory spaces accessible by any part of your program or any other program running in the same context as your application and therefore also having access to that memory space.
This is considered an anti -pattern or bad practice for several reasons. Here I mention some:
Your program will not be easy to reason.
The natural flow of the program can be lost between multiple updates to its dependencies.
Complexity increases
With a greater number of parties interacting in a common area, it will now be more difficult for you to predict the state of your application at each moment. This is very important for it to work without errors.
You can have unpredictable results
Derived from the above, imagine that you have a variable with a value and when you go to manipulate it you realize that it is not what you expected and that another part of the code altered said value. This can cause your program to crash completely or do something you don't want it to do. That it behaves in a predictable way is one of the requirements to have a good architecture.
creates insecurity
Your data is not private. There are situations where your program shares context with code written by someone else. A classic example of this is third-party widgets (twitter, facebook, etc) that are included in web pages. These scripts are executed in the same context as your application and therefore have access to all your global variables. Obviously it would not be a good idea to store any valuable data in that environment due to the risk that it could be obtained by someone else.
Someone else can break your code
Derived from the above, it may be that another programmer decides to use the same variable name as you (this actually happens very often). Since they are both trying to use the same memory region as their own this will cause your program to crash. This is the reason why, for example, almost all libraries on the web have a method
noConflict
or similar to be able to interoperate between them without creating conflicts.Other arguments may be that they violate referential transparency and do not help thread safety , making race conditions very difficult to detect. Imagine two threads of the same program trying to write a value to the same place at the same time.
I can think of many more, but I think the argument is quite convincing since all programming languages have artifacts and techniques to avoid such problems with very little effort, so it is not necessary to fall into such problematic patterns unless it is strictly necessary. .
I can give you an example that will help you understand better:
Imagine you have a toolbox in a common access area and you need to tighten a screw, you open the box and you can't find the screwdriver or the screwdriver is used and broken. The tools in the box are your global variables. In these conditions you will never be able to do your job.
Some of the problems with using global variables:
What are global variables?
I consider important first to have a brief concept of what a global variable is.
In computer programming, a global variable is a variable with global scope, which means that it is visible (therefore accessible) throughout the program, unless it is hidden. The set of all global variables is known as the global environment or global state. In compiled languages, global variables are usually static variables, the length of which (lifetime) is the entire execution of the program , although in interpreted languages (including command line interpreters), global variables are usually dynamically assigned when declared , since they are not known ahead of time.
In some programming languages, all variables are global or global by default, while in most modern programming languages variables have a limited scope, usually a lexical scope, although global variables are often available by declaring a variable. at the top level of the program. However, in other languages there are no global variables; these are generally modular programming languages that enforce a module structure or class-based object-oriented programming languages that enforce a class structure.
Are global variables bad?
The answer is yes, they are almost always bad . Although as we know, there is no rule that is 100% fulfilled and we will see why. What should be clear from the outset is that global variables should be avoided when they are unnecessary. But let's avoid it by using the appropriate alternative offered by the programming language we are using.
Why should global variables be avoided when they are not strictly necessary?
When we avoid globals, the code is generally clearer and easier to maintain, although as mentioned there are exceptions. On the contrary, its use usually brings very serious problems.
Let's look at just a few of the serious problems that arise when using global variables:
Non-locality - Source code is easier to understand when the scope of its individual elements is limited. Global variables can be read or changed by any part of the program, making it hard to remember or reason about every possible use.
No access control or constraint checking : A global variable can be obtained 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 important for global data.) By extension, the lack of access control makes it extremely difficult to achieve security in situations where which you want to run untrusted code (such as working with third-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 elements into cohesive units generally leads to better programs.
Concurrency issues : If global variables can be accessed by multiple threads, synchronization is necessary (and too often neglected). By dynamically binding modules to globals, the composite system might not be thread-safe even if the two independent modules tested in dozens of different contexts were thread-safe.
Namespace pollution : Global names are available everywhere. You may unknowingly end up using a global when you think you're using a local (due to misspelling or forgetting to declare the local) or vice versa. Also, if you ever have to link modules that have the same global variable names, if you're lucky, you'll get link errors. If you're 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 global allocation difficult. This is especially true in languages where "constructors" have side effects other than assignment (because, in that case, unsafe situations can be expressed where two globals depend on each other). Also, when dynamically linking modules, it may not be clear if different libraries have their own instances of globals or if globals are shared.
Testing and Confinement: The source using globals is somewhat more difficult to test because you can't easily establish a "clean" environment between runs. More generally, source using global services of any kind (for example, reading and writing files or databases) that are not explicitly provided to that source is difficult to test for the same reason. For communicating systems, the ability to test system invariants may require more than one "copy" of a system to run simultaneously, which is greatly hampered by the use of shared services - including global memory - that are not provided. to share as part of the test.
The little exceptions
There are cases where the convenience of global variables outweighs the potential problems mentioned above.
Let's imagine for example a very small or special program, especially of the 'plugin' type where you basically write a single object or stub for a larger system, using globals may be the simplest in this case.
When global variables represent facilities that are actually available throughout the program, their use simplifies the code.
Some programming languages provide no support or minimal support for non-global variables.
False alternatives to using globals or using them "believing" they are not
Algunas personas saltan a través de aros muy complicados para evitar el uso de globales. Muchos usos del SingletonPattern son apenas globales velados delgadamente. Si algo realmente debe ser global, hazlo global. No hagas algo complicado porque tal vez lo necesites algún día. Si existe una variable global, asumiría que se utiliza. Si se utiliza, hay métodos asociados con él. Colocar esos métodos en una sola clase y uno ha creado un singleton. Realmente es mejor especificar todas las reglas para el uso de una variable global en un lugar donde se pueden revisar por coherencia. El velo puede ser delgado, pero es valioso.
Incluso en los casos anteriores, es aconsejable considerar el uso de una de las Alternativas a Variables Globales para controlar el acceso a esta facilidad. Mientras que esto ayuda a prueba de futuro el código, por ejemplo, cuando su "pequeño" programa se convierte en uno muy grande - también puede simplificar problemas más inmediatos como probar el código o hacer que funcione correctamente en un entorno concurrente.
La declaración irreflexiva de variables se puede considerar un vicio de programación, en el cual podemos caer cuando empezamos a trabajar en un problema y cedemos a la tentación: "Necesito esa lista en muchos lugares diferentes. Declaro una Variable global ... y voila! ". Luego experimentamos que el número de globales comienza a ser inmanejable, entonces decidimos poner a todas las globales en una gran lista global de globales. A veces el globo explota cuando es demasiado tarde :)
Y es que el vicio ocurre porque agregar globales es muy fácil. Es fácil adquirir el hábito de declararlas. Es mucho más rápido que pensar en un buen diseño. Pero como se suele decir, lo barato sale caro. Es cierto que en algunas circunstancias simples, realmente la cosa más simple de hacer es usar una variable global. Pero cuando se trata de un programa complejo, una vez que se tiene una variable global, es mucho más difícil deshacerse de ella mientras pasa el tiempo y el programa crece. Nuestro código termina siendo dependiente de los posibles caprichos de dicha variable y arrojarnos resultados inesperados.
Realmente malas razones para usar variables globales
-¿Qué es una "variable local"? O sea, cuando no se entiende lo que es una variable local ni como funciona.
-¿Qué es un "miembro de datos"? Lo mismo, la ignorancia...
-"Soy una mecanógrafa lenta, los globales me guardan las pulsaciones de teclas". Pues ya ves :)
-"No quiero pasar parámetros todo el tiempo." No seas vago :)
-"No estoy seguro de a qué clase pertenecen estos datos, así que lo haré global." Vea las noticias, infórmese
Alternativas a variables globales
Las alternativas a variables globales son múltiples. Aunque es bueno señalar que escoger una alternativa supone a veces un paso delicado, en el sentido de que podemos estar optando por una alternativa que a lo mejor no tiene la capacidad de resolver esa situación concreta. Es el ejemplo típico mencionado más arriba en el caso de los SingletonPattern.
Veamos algunas de estas alternativas:
Objetos de Contexto: permiten agrupar y abstraer las dependencias globales y luego moverlas en un programa, funcionando efectivamente como variables globales pero mucho más fácil de anular y manipular localmente. Desafortunadamente, la mayoría de los lenguajes no ofrecen soporte para ContextObjects, que requiere "pasarlo todo el tiempo". Los hilos (Threading) de un ContextObject son ayudados por los alcances dinámicos (DynamicScoping) y las variables especiales (SpecialVariables).
Inyección de dependencia (DependencyInjection): La capacidad de configurar gráficos de objetos complejos en un programa reduce en gran medida la necesidad de pasar "variables" alrededor de las que llevan información global / de contexto. La fuerza de este enfoque es visible en paradigmas que hacen mucho menos uso de globales, como DataflowProgramming. Algunos lenguajes (como Java) tienen estructuras maduras de DependencyInjection que a menudo funcionan de manera algo estática (por ejemplo, a partir de un archivo de configuración, o no integran objetos vivos) y eso solo es suficiente para coincidir con muchos usos comunes de globales.
El soporte de primera clase (FirstClass) para DependencyInjection y la construcción de gráficos de flujo de datos o de objetos permite además componer sistemas complejos sobre la marcha en tiempo de ejecución (permitiendo un medio de composición para objetos primitivos que faltan en los lenguajes OO tradicionales) y además permite una enorme gama de optimizaciones, eliminación de códigos muertos, evaluación parcial, etc., lo que hace que esta sea una alternativa bastante atractiva a las globales.
Globales ocultas: las globales ocultas tienen un alcance de acceso bien definido e incluyen, por ejemplo, variables privadas 'estáticas' en clases y variables 'estáticas' en archivos '.c' y variables en espacios de nombres anónimos en C ++. Esta solución enjaula y localiza globales en lugar de domesticarlos - todavía se morderá cuando se trata de concurrencia y modularización y pruebas / confinamiento, pero al menos todos estos problemas serán localizados y fáciles de reparar, y no habrá problemas de vinculación .
Procedimientos de estado: Se trata de un conjunto global de setters y getters u operaciones que actúan sobre lo que es, implícitamente, el estado subyacente. Estos sufren muchos de los problemas asociados con los globales en lo que respecta a pruebas, concurrencia y asignación / intialización. Sin embargo, ofrecen un mejor control de acceso, la oportunidad de sincronización y una considerable capacidad de abstraer la implementación (por ejemplo, se podría poner el estado global en una base de datos).
SingletonPattern: Construye un objeto globalmente, permite el acceso a él a través de un procedimiento de estado. SingletonPattern ofrece la oportunidad conveniente para la especialización de una sola vez de un global basado en argumentos y el ambiente, y así puede servir bastante bien para abstraer los recursos que son verdaderamente parte del ambiente de programación (por ejemplo, monitores, altavoces, consola, etc.). Sin embargo, SingletonPattern no ofrece nunca la flexibilidad ofrecida por DependencyInjection o ContextObject, y está a la par con los procedimientos de estado en cuanto que ayudan al programador a controlar los problemas a los que los usuarios todavía se enfrentarán.
Base de datos o TupleSpace o DataDistributionService: A veces las variables globales se utilizan para representar datos. Esto es especialmente el caso de mapas globales, hashtables globales, listas globales. En menor grado, también es el caso de las colas de tareas. Para estos casos en los que los datos son realmente "globales", especialmente si cualquier parte del programa se dedica a empujar partes de estos datos globales a usuarios externos, el uso de un sistema dedicado simplificará en gran medida el programa y probablemente lo hará más robusto al mismo tiempo.
En Resumen
Usa las globales solamente cuando las puedas tener enjauladas (en un pequeño programa) y sean realmente necesarias en ese caso, o cuando no te quede ninguna otra alternativa.
The " bad practice " of using global variables can be easily fixed. It is placed in a Singleton (bad practice?), accessed with a synchronized getter and setter , and - saintly remedy - is no longer a bad practice. ;)
Although it is generally advisable to limit the scope of a variable as much as possible, there are cases where you simply need to save global variables because you are backing up global values. For example, a value in a database is no different than a global variable.
Thus, using a global variable should apply the same considerations that apply when using database values (transactions, atomize access, local copies, etc).
Global scope variables were only dangerous when there were no good ways to protect the integrity of their content. Modifiers like
volatile
, synchronizable getters and setters , types like AtomicInteger , ... are tools that allow you to give a variable the scope it needs.Bad practices only exist when the developer does not know what he is doing or why he is doing it. The worst practice is blindly following good practices without understanding why, or getting lost in avoiding alleged bad practices that might be the best way to solve a particular problem.
Some reflections...
Global variables tend to introduce errors.
If it is a global variable, it can be modified, otherwise it would be a constant. Now, if it can be modified, then it can be modified from anywhere in the code, so you can't control who modifies its value.
Debugging an error that can come from anywhere is usually a headache.
Finally, Global variables are not necessary. There is no single use case that can be covered exclusively by a global variable and not by some other structure. For example, in OOP you have
Singleton
, a pattern that, although not highly recommended, is a valid alternative.A good way to understand the danger and bane of global variables is by examining a typical example where it causes problems. I'm going to use C# for the examples, but the principle applies to any programming language.
Let's take the following example:
Let's say I've been asked to find the cause of an exception thrown above because it
mivariable
isnull
, even though it's never supposed to be. In this case, becausemivariable
it is not global, but rather a variable with a well-defined method input (a parameter), with modern tools like Visual Studio, it is quite easy to trace back the thread of method calls to investigate on which placemivariable
got corrupted. The point is that the scope ofmivariable
is limited, so I don't have to investigate countless places in my code where I might have changed the value ofmivariable
In contrast, what if I were to investigate the same error, but with it
mivariable
defined as a global variable:Now the complexity of my investigation is multiplied, because not only do I now have to find all the places where other parts of the code could have manipulated
mivariable
, and I'm sure there will be many, but I have to understand in what order all these other places are executed in order to know at what time do the values ofmivariable
. This is a tremendous headache.Similarly, not only is the investigation of a problem complicated when global variables are involved, but for the same reasons, the chances of introducing subtle flaws in the code due to misunderstanding where and when the variable is used are increased.
And of course, the more complex the project is, and the more code you have, the more it becomes a real problem. This type of code is what is known as spaghetti code because of how complicated it is to understand.
And worst of all, there is no justification for using global variables. Usually, whoever introduces them does so out of laziness and wanting to save a few lines of code. But the long-term cost of doing this is very great.
A teacher at school told us that global variables are very easily accessible, even by another application, this situation considerably increases the risk of loss or theft of information by malicious agents.
A variable must have the minimum necessary scope. I don't think there is any unreasonable hatred of global variables. Mind you, to have a global scope variable should be justified. It is not an anti-pattern to use a variable (or singleton) in its proper context. The problem comes when the context of the variable is extended unnecessarily. This means that parts of the code can access variables that are not their responsibility, either due to a design flaw or an attempt to reuse a resource, which can lead to a very dangerous problem of ambiguity in its use.
Something similar happens with a poor encapsulation of our classes, since we can give excessive visibility/access, affecting even a greater dependency that will affect us in future maintenance.