¿Por qué es tan malvado el estado global?

328

Antes de comenzar, permítanme decir que conozco bien los conceptos de abstracción e inyección de dependencia. No necesito mis ojos abiertos aquí.

Bueno, la mayoría de nosotros dice, (también) muchas veces sin comprender realmente, "No use variables globales" o "Los Singletons son malos porque son globales". Pero, ¿qué tiene de malo el ominoso estado global?

Digamos que necesito una configuración global para mi aplicación, por ejemplo, rutas de carpetas del sistema o credenciales de base de datos para toda la aplicación.

En ese caso, no veo ninguna buena solución que no sea proporcionar esta configuración en algún tipo de espacio global, que comúnmente estará disponible para toda la aplicación.

Sé que es malo abusar de él, pero ¿el espacio global es realmente TAN malo? Y si es así, ¿qué buenas alternativas hay?

Madara Uchiha
fuente
55
puede usar una clase de configuración que maneje el acceso a esas configuraciones. Creo que es una buena práctica tener un único lugar donde los ajustes de configuración se leen desde archivos de configuración y / o bases de datos mientras otros objetos los obtienen de esa configuración. hace que su diseño sea más limpio y predecible.
Hajo
25
¿Dónde está la diferencia con el uso de un global? Su "único lugar" suena sospechosamente como un Singleton.
Madara Uchiha
130
El único mal real son los dogmas.
Pieter B
34
Usar el estado global con tus compañeros programadores es como usar el mismo cepillo de dientes con tus amigos: puedes hacerlo pero nunca sabes cuándo alguien decidirá empujarlo por el trasero.
ziGi
55
El diablo es malvado. El estado global no es ni malo ni bueno. Puede ser muy útil o perjudicial dependiendo de cómo lo use. No escuches a los demás y solo aprende a programar adecuadamente.
annoying_squid

Respuestas:

282

Muy brevemente, hace que el estado del programa sea impredecible.

Para elaborar, imagine que tiene un par de objetos que usan la misma variable global. Suponiendo que no esté utilizando una fuente de aleatoriedad en ningún lugar dentro de ninguno de los módulos, la salida de un método en particular se puede predecir (y, por lo tanto, probar) si se conoce el estado del sistema antes de ejecutar el método.

Sin embargo, si un método en uno de los objetos desencadena un efecto secundario que cambia el valor del estado global compartido, entonces ya no sabe cuál es el estado inicial cuando ejecuta un método en el otro objeto. Ahora ya no puede predecir qué salida obtendrá cuando ejecute el método y, por lo tanto, no puede probarlo.

En un nivel académico, esto puede no sonar tan serio, pero ser capaz de unificar el código de prueba es un paso importante en el proceso de probar su corrección (o al menos la idoneidad para el propósito).

En el mundo real, esto puede tener algunas consecuencias muy graves. Suponga que tiene una clase que llena una estructura de datos global y una clase diferente que consume los datos en esa estructura de datos, cambiando su estado o destruyéndolos en el proceso. Si la clase de procesador ejecuta un método antes de que se complete la clase de población, el resultado es que la clase de procesador probablemente tendrá datos incompletos para procesar, y la estructura de datos en la que estaba trabajando la clase de población podría corromperse o destruirse. El comportamiento del programa en estas circunstancias se vuelve completamente impredecible y probablemente conducirá a una pérdida épica.

Además, el estado global perjudica la legibilidad de su código. Si su código tiene una dependencia externa que no se introduce explícitamente en el código, entonces quienquiera que tenga el trabajo de mantener su código tendrá que buscarlo para averiguar de dónde proviene.

En cuanto a qué alternativas existen, es imposible no tener un estado global en absoluto, pero en la práctica generalmente es posible restringir el estado global a un solo objeto que envuelva a todos los demás, y al que nunca se debe hacer referencia confiando en las reglas de alcance del lenguaje que estás usando. Si un objeto particular necesita un estado particular, entonces debe solicitarlo explícitamente al pasarlo como argumento a su constructor o mediante un método de establecimiento. Esto se conoce como inyección de dependencia.

Puede parecer una tontería pasar en un estado al que ya puede acceder debido a las reglas de alcance de cualquier idioma que esté utilizando, pero las ventajas son enormes. Ahora, si alguien mira el código de forma aislada, está claro qué estado necesita y de dónde viene. También tiene enormes beneficios con respecto a la flexibilidad de su módulo de código y, por lo tanto, las oportunidades para reutilizarlo en diferentes contextos. Si se pasa el estado y los cambios en el estado son locales para el bloque de código, puede pasar en cualquier estado que desee (si es el tipo de datos correcto) y hacer que su código lo procese. El código escrito en este estilo tiende a tener la apariencia de una colección de componentes poco asociados que pueden intercambiarse fácilmente. El código de un módulo no debería importar de dónde viene el estado, sino cómo procesarlo.

Hay muchas otras razones por las cuales pasar el estado es muy superior a depender del estado global. Esta respuesta no es de ninguna manera integral. Probablemente podrías escribir un libro completo sobre por qué el estado global es malo.

GordonM
fuente
61
Básicamente, debido a que cualquiera puede cambiar el estado, no puede confiar en él.
Finalizado
21
@Truth ¿La alternativa? Inyección de dependencia .
rdlowrey
12
Su argumento principal no se aplica realmente a algo que sea efectivamente de solo lectura, como un objeto que representa una configuración.
frankc
53
Nada de eso explica por qué las variables globales de solo lectura son malas ... sobre las cuales el OP preguntó explícitamente. Es una buena respuesta, me sorprende que el OP lo haya marcado como "aceptado" cuando abordó explícitamente un punto diferente.
Konrad Rudolph
20
being able to unit test code is a major step in the process of proving its correctness (or at least fitness for purpose). No, no lo es. "Han pasado dos décadas desde que se señaló que las pruebas de programas pueden demostrar de manera convincente la presencia de errores, pero nunca pueden demostrar su ausencia. Después de citar este comentario tan publicitado devotamente, el ingeniero de software vuelve al orden del día y continúa para refinar sus estrategias de prueba, al igual que el alquimista de antaño, que continuó refinando sus purificaciones crisocósmicas ". - Djikstra, 1988. (Eso hace 4.5 décadas ahora ...)
Mason Wheeler
135

El estado global mutable es malo por muchas razones:

  • Errores del estado global mutable: la mutabilidad causa muchos errores difíciles. Los errores que pueden ser causados ​​por la mutación desde cualquier parte del programa son incluso más complicados, ya que a menudo es difícil rastrear la causa exacta
  • Poca capacidad de prueba : si tiene un estado global mutable, deberá configurarlo para cualquier prueba que escriba. Esto hace que las pruebas sean más difíciles (¡y las personas que son personas tienen menos probabilidades de hacerlo!). Por ejemplo, en el caso de las credenciales de la base de datos de toda la aplicación, ¿qué sucede si una prueba necesita acceder a una base de datos de prueba específica diferente de todo lo demás?
  • Inflexibilidad : ¿qué sucede si una parte del código requiere un valor en el estado global, pero otra parte requiere otro valor (por ejemplo, un valor temporal durante una transacción)? De repente tienes un poco de refactorización en tus manos
  • Impureza de funciones : las funciones "puras" (es decir, aquellas en las que el resultado depende solo de los parámetros de entrada y no tienen efectos secundarios) son mucho más fáciles de razonar y componer para construir programas más grandes. Las funciones que leen o manipulan el estado global mutable son inherentemente impuras.
  • Comprensión del código: el comportamiento del código que depende de una gran cantidad de variables globales mutables es mucho más complicado de entender: debe comprender el rango de posibles interacciones con la variable global antes de poder razonar sobre el comportamiento del código. En algunas situaciones, este problema puede volverse intratable.
  • Problemas de concurrencia : el estado global mutable generalmente requiere alguna forma de bloqueo cuando se usa en una situación concurrente. Esto es muy difícil de corregir (es una causa de errores) y agrega considerablemente más complejidad a su código (difícil / costoso de mantener).
  • Rendimiento : varios subprocesos que se basan continuamente en el mismo estado global provocan contención de caché y ralentizarán su sistema en general.

Alternativas al estado global mutable:

  • Parámetros de función : a menudo se pasan por alto, pero parametrizar mejor sus funciones es a menudo la mejor manera de evitar el estado global. Te obliga a resolver la importante cuestión conceptual: ¿qué información requiere esta función para hacer su trabajo? A veces tiene sentido tener una estructura de datos llamada "Contexto" que se puede pasar por una cadena de funciones que envuelve toda la información relevante.
  • Inyección de dependencias : igual que para los parámetros de función, solo un poco antes (en la construcción del objeto en lugar de la invocación de la función). Sin embargo, tenga cuidado si sus dependencias son objetos mutables, esto puede causar rápidamente los mismos problemas que el estado global mutable .....
  • El estado global inmutable es en su mayoría inofensivo: es efectivamente una constante. ¡Pero asegúrese de que realmente sea una constante, y de que no se sienta tentado a convertirlo en un estado global mutable en un momento posterior!
  • Singletons inmutables : casi lo mismo que el estado global inmutable, excepto que puede diferir la creación de instancias hasta que sean necesarios. Útil para, por ejemplo, grandes estructuras de datos fijos que necesitan un cálculo previo único y costoso. Los singletons mutables son, por supuesto, equivalentes al estado global mutable y, por lo tanto, son malvados :-)
  • Enlace dinámico : solo está disponible en algunos idiomas como Common Lisp / Clojure, pero esto efectivamente le permite vincular un valor dentro de un alcance controlado (generalmente en una base de subproceso local) que no afecta a otros subprocesos. Hasta cierto punto, esta es una forma "segura" de obtener el mismo efecto que una variable global, ya que sabe que solo el hilo actual de ejecución se verá afectado. Esto es particularmente útil en el caso de que tenga varios subprocesos, cada uno de los cuales maneja transacciones independientes, por ejemplo.
mikera
fuente
11
Creo que pasar un objeto de contexto, ya sea por parámetro de función o inyección de dependencia, causaría problemas si el contexto es mutable, el mismo problema que usar el estado global mutable.
Alfredo Osorio
1
Todas las cosas buenas, y amén! Pero la pregunta es sobre el estado global inmutable
MarkJ
@Alfredo: muy cierto. Aunque no es tan malo ya que al menos el alcance puede controlarse de alguna manera. Pero en general, es más fácil resolver el problema haciendo que los contextos y las dependencias sean inmutables.
mikera
2
+1 para mutable / inmutable. Globales inmutables están bien. Incluso los que tienen carga lenta pero nunca cambian. Por supuesto, no exponga variables globales, sino una interfaz global o API.
Jess
1
@giorgio La pregunta deja en claro que las variables en cuestión obtienen sus valores al inicio y nunca cambian después durante la ejecución del programa (carpetas del sistema, credenciales de la base de datos). Es decir, inmutable, no cambia una vez que se le ha dado un valor. Personalmente, también uso la palabra "estado" porque puede ser diferente de una ejecución a otra, o en una máquina diferente. Puede haber mejores palabras.
MarkJ
62
  1. Como toda tu maldita aplicación puede estar usándola, siempre es increíblemente difícil volver a factorizarla. Si alguna vez cambia algo relacionado con su global, todo su código debe cambiar. Este es un dolor de cabeza de mantenimiento, mucho más que simplemente poder greppara el nombre del tipo averiguar qué funciones lo usan.
  2. Son malos porque introducen dependencias ocultas, que rompen el subprocesamiento múltiple, que es cada vez más vital para cada vez más aplicaciones.
  3. El estado de la variable global siempre es completamente poco confiable, porque todo su código podría estar haciéndole cualquier cosa.
  4. Son realmente difíciles de probar.
  5. Hacen que llamar a la API sea difícil. "Debes recordar llamar a SET_MAGIC_VARIABLE () antes de llamar a la API" es rogarle a alguien que se olvide de llamarlo. Hace que el uso de la API sea propenso a errores, lo que causa errores difíciles de encontrar. Al usarlo como un parámetro regular, obliga a la persona que llama a proporcionar un valor correctamente.

Simplemente pase una referencia a las funciones que lo necesitan. No es tan dificil.

DeadMG
fuente
3
Bueno, puede tener una clase de configuración global que encapsula el bloqueo, y está diseñada para cambiar el estado en cualquier momento posible. Elegiría este enfoque sobre la creación de instancias de lectores de configuración de 1000x lugares en el código. Pero sí, lo impredecible es lo peor de ellos.
Codificador
66
@Coder: Tenga en cuenta que la alternativa sensata a un global no es "lectores de configuración de 1000x lugares en el código", sino un lector de configuración, que crea un objeto de configuración, que los métodos pueden aceptar como parámetro (-> Inyección de dependencias).
sleske
2
Nitpicking: ¿Por qué es más fácil buscar un tipo que un global? Y la pregunta es acerca de las
variables
2
@ MarkJ: Espero que nadie, por ejemplo, haya sombreado el nombre.
DeadMG
2
@DeadMG, Re "... siempre es completamente poco confiable ...", lo mismo ocurre con la inyección de dependencia. El hecho de que lo haya convertido en un parámetro no significa que se garantice que el obj tenga un estado fijo. En algún lugar de la función, podría haber realizado una llamada a otra función que modifica el estado de la variable inyectada en la parte superior.
Pacerier
34

Si dice "estado", eso generalmente se toma como "estado mutable". Y el estado global mutable es totalmente malo, porque significa que cualquier parte del programa puede influir en cualquier otra parte (al cambiar el estado global).

Imagínese depurar un programa desconocido: encuentra que la función A se comporta de cierta manera para ciertos parámetros de entrada, pero a veces funciona de manera diferente para los mismos parámetros. Usted encuentra que usa la variable global x .

Busca lugares que modifiquen x , y encuentra que hay cinco lugares que lo modifican. Ahora buena suerte descubriendo en qué casos la función A hace qué ...

sleske
fuente
¿Tan inmutable el estado global no es tan malo?
FrustratedWithFormsDesigner
50
Yo diría que el estado global inmutable es la buena práctica conocida como 'constantes'.
Telastyn
66
El estado global inmutable no es malo, es malo :-). Todavía es problemático debido al acoplamiento que introduce (hace que los cambios, la reutilización y las pruebas unitarias sean más difíciles), pero crea muchos menos problemas, por lo que en casos simples generalmente es aceptable.
sleske
2
IFF uno usa variables globales, entonces solo un fragmento de código debe modificarlo. El resto es libre de leerlo. Los problemas de otros que lo cambian no desaparecen con las funciones de encapsulación y acceso. Para eso no son esas construcciones.
phkahler
1
@Pacerier: Sí, cambiar una interfaz ampliamente utilizada es difícil, sin importar si se usa como una variable global o local. Sin embargo, eso es independiente de mi punto, que es que la interacción de diferentes piezas de código es difícil de entender con variables globales.
sleske
9

Como que respondiste tu propia pregunta. Son difíciles de manejar cuando se 'abusan', pero pueden ser útiles y [algo] predecibles cuando se usan adecuadamente, por alguien que sabe cómo contenerlos. El mantenimiento y los cambios en / sobre las glóbulos generalmente es una pesadilla, que empeora a medida que aumenta el tamaño de la aplicación.

Los programadores experimentados que pueden notar la diferencia entre que los globales son la única opción, y que son la solución fácil, pueden tener problemas mínimos para usarlos. Pero los posibles problemas interminables que pueden surgir con su uso requieren el asesoramiento contra su uso.

editar: Para aclarar lo que quiero decir, los globales son impredecibles por naturaleza. Al igual que con cualquier cosa impredecible, puede tomar medidas para contener la imprevisibilidad, pero siempre hay límites para lo que se puede hacer. Además de la molestia de que los nuevos desarrolladores que se unan al proyecto tengan que lidiar con variables relativamente desconocidas, las recomendaciones contra el uso de globales deberían ser comprensibles.

orourkek
fuente
9

Hay muchos problemas con Singletons: estos son los dos problemas más grandes en mi mente.

  • Hace que las pruebas unitarias sean problemáticas. El estado global puede contaminarse de una prueba a la siguiente.

  • Aplica la estricta regla de "Uno y solo uno", que, aunque no podría cambiar, de repente lo hace. Todo un montón de código de utilidad que usaba el objeto accesible globalmente, entonces necesita ser alterado.

Dicho esto, la mayoría de los sistemas tienen cierta necesidad de Big Global Objects. Estos son elementos que son grandes y caros (por ejemplo, administradores de conexión de base de datos), o que contienen información generalizada del estado (por ejemplo, información de bloqueo).

La alternativa a un Singleton es tener estos Big Global Objects creados en el inicio y pasarlos como parámetros a todas las clases o métodos que necesitan acceso a este objeto.

El problema aquí es que terminas con un gran juego de "pasar el paquete". Tiene un gráfico de componentes y sus dependencias, y algunas clases crean otras clases, y cada una tiene que contener un montón de componentes de dependencia solo porque sus componentes generados (o los componentes de los componentes generados) los necesitan.

Te encuentras con nuevos problemas de mantenimiento. Un ejemplo: De repente, su componente "WidgetFactory", en lo profundo del gráfico, necesita un objeto de temporizador del que desee burlarse. Sin embargo, "WidgetFactory" es creado por "WidgetBuilder", que forma parte de "WidgetCreationManager", y debe tener tres clases que conozcan este objeto del temporizador aunque solo uno lo use realmente. Deseas darte por vencido y volver a Singletons, y simplemente hacer que este objeto de temporizador sea accesible globalmente.

Afortunadamente, este es exactamente el problema que se resuelve con un marco de inyección de dependencia. Simplemente puede decirle al marco qué clases necesita crear, y utiliza la reflexión para descubrir el gráfico de dependencia por usted, y construye automáticamente cada objeto cuando se necesitan.

Entonces, en resumen, los Singletons son malos, y la alternativa es usar un marco de Inyección de dependencias.

Por casualidad uso Castle Windsor, pero tienes muchas opciones. Consulte esta página de 2008 para obtener una lista de los marcos disponibles.

Andrew Shepherd
fuente
8

En primer lugar, para que la inyección de dependencia sea "con estado", necesitaría usar singletons, por lo que las personas que dicen que de alguna manera es una alternativa están equivocadas. Las personas usan objetos de contexto global todo el tiempo ... Incluso el estado de sesión, por ejemplo, es esencialmente una variable global. Pasar todo por la inyección de dependencia o no no siempre es la mejor solución. Actualmente trabajo en una aplicación muy grande que usa muchos objetos de contexto global (singletons inyectados a través de un contenedor IoC) y nunca ha sido un problema depurar. Especialmente con una arquitectura dirigida por eventos, se puede preferir usar objetos de contexto global en lugar de transmitir lo que haya cambiado. Depende de a quién le preguntes.

Se puede abusar de cualquier cosa y también depende del tipo de aplicación. El uso de variables estáticas, por ejemplo, en una aplicación web es completamente diferente a una aplicación de escritorio. Si puede evitar las variables globales, hágalo, pero a veces tienen sus usos. Como mínimo, asegúrese de que sus datos globales estén en un objeto contextual claro. En cuanto a la depuración, nada que una pila de llamadas y algunos puntos de interrupción no puedan resolver.

Quiero enfatizar que usar ciegamente variables globales es una mala idea. Las funciones deberían ser reutilizables y no debería importarles de dónde provienen los datos; en referencia a las variables globales, la función se combina con una entrada de datos específica. Es por eso que debe pasarse y por qué la inyección de dependencia puede ser útil, aunque todavía se trata de un almacén de contexto centralizado (a través de singletons).

Por cierto ... Algunas personas piensan que la inyección de dependencia es mala, incluido el creador de Linq, pero eso no impedirá que la gente lo use, incluido yo mismo. En definitiva, la experiencia será tu mejor maestro. Hay momentos para seguir reglas y tiempos para romperlos.

Rey de los hipócritas
fuente
4

Dado que algunas otras respuestas aquí hacen la distinción entre el estado global mutable e inmutable , me gustaría opinar que incluso las variables / configuraciones globales inmutables son a menudo una molestia .

Considere las preguntas:

... Digamos que necesito una configuración global para mi aplicación, por ejemplo, rutas de carpetas del sistema o credenciales de base de datos para toda la aplicación. ...

Correcto, para un programa pequeño, esto probablemente no sea un problema, pero tan pronto como lo haga con componentes en un sistema un poco más grande, escribir pruebas automatizadas de repente se vuelve más difícil porque todas las pruebas (que se ejecutan dentro del mismo proceso) deben funcionar con El mismo valor de configuración global.

Si todos los datos de configuración se pasan explícitamente, los componentes se vuelven mucho más fáciles de probar y nunca tendrá que preocuparse de cómo iniciar un valor de configuración global para múltiples pruebas, tal vez incluso en paralelo.

Martin Ba
fuente
3

Bueno, por un lado, puede encontrarse exactamente con el mismo problema que con singletons. Lo que hoy parece una "cosa global de la que solo necesito uno" de repente se convertirá en algo que necesita más en el futuro.

Por ejemplo, hoy crea este sistema de configuración global porque desea una configuración global para todo el sistema. Unos años más tarde, se transfiere a otro sistema y alguien dice "oye, ya sabes, esto podría funcionar mejor si hubiera una configuración global general y una configuración específica de la plataforma". De repente, tiene todo este trabajo para hacer que sus estructuras globales no sean globales, por lo que puede tener varias.

(Este no es un ejemplo aleatorio ... esto sucedió con nuestro sistema de configuración en el proyecto en el que estoy actualmente).

Teniendo en cuenta que el costo de hacer algo no global suele ser trivial, es una tontería hacerlo. Solo estás creando problemas futuros.

Steven Burnap
fuente
Su ejemplo no es lo que OP quiso decir. Está hablando claramente de crear instancias de configuraciones abstractas (solo una estructura de datos del administrador de configuración, sin ninguna clave de preferencia específica de la aplicación) varias veces porque desea dividir todas las claves en una instancia en ejecución de su programa en varias instancias de su clase de administrador de configuración. (Digamos, cada una de esas instancias podría manejar un archivo de configuración, por ejemplo).
Jo So
3

El otro problema curiosamente es que hacen que una aplicación sea difícil de escalar porque no son lo suficientemente "globales". El alcance de una variable global es el proceso.

Si desea escalar su aplicación utilizando múltiples procesos o ejecutándose en múltiples servidores, no puede hacerlo. Al menos no hasta que elimine todos los globales y los reemplace con algún otro mecanismo.

James Anderson
fuente
3

¿Por qué es tan malvado el estado global?

El estado global mutable es malo porque es muy difícil para nuestro cerebro tener en cuenta más de unos pocos parámetros a la vez y descubrir cómo se combinan desde una perspectiva de tiempo y una perspectiva de valor para afectar algo.

Por lo tanto, somos muy malos depurando o probando un objeto cuyo comportamiento tiene más de unas pocas razones externas para ser alterado durante la ejecución de un programa. Y mucho menos cuando tenemos que razonar sobre docenas de estos objetos tomados juntos.

guillaume31
fuente
3

Los lenguajes diseñados para un diseño de sistemas seguro y robusto a menudo eliminan por completo el estado mutable global . (Podría decirse que esto significa que no hay globales, ya que los objetos inmutables en cierto sentido no tienen estado ya que nunca tienen una transición de estado).

Joe-E es un ejemplo, y David Wagner explica la decisión así:

El análisis de quién tiene acceso a un objeto y el principio del privilegio mínimo se subvierten cuando las capacidades se almacenan en variables globales y, por lo tanto, cualquier parte del programa puede leerlas. Una vez que un objeto está disponible globalmente, ya no es posible limitar el alcance del análisis: el acceso al objeto es un privilegio que no se puede retener de ningún código en el programa. Joe-E evita estos problemas al verificar que el alcance global no contiene capacidades, solo datos inmutables.

Entonces, una forma de pensarlo es

  1. La programación es un problema de razonamiento distribuido. Los programadores en proyectos grandes necesitan dividir el programa en partes que puedan ser razonadas por individuos.
  2. Cuanto más pequeño es el alcance, más fácil es razonar. Esto es cierto tanto para las personas como para las herramientas de análisis estático que intentan probar las propiedades de un sistema y las pruebas que necesitan probar las propiedades de un sistema.
  3. Importantes fuentes de autoridad que están disponibles globalmente hacen que las propiedades del sistema sean difíciles de razonar.

Por lo tanto, el estado globalmente mutable hace que sea más difícil

  1. diseñar sistemas robustos,
  2. más difícil de probar las propiedades de un sistema, y
  3. Es más difícil estar seguro de que sus pruebas están probando en un ámbito similar a su entorno de producción.

El estado mutable global es similar al infierno de DLL . Con el tiempo, las diferentes partes de un sistema grande requerirán un comportamiento sutilmente diferente de las partes compartidas de estado mutable. Resolver el infierno de DLL y las inconsistencias de estado mutable compartidas requiere una coordinación a gran escala entre equipos dispares. Estos problemas no se producirían si el estado global hubiera tenido el alcance adecuado para comenzar.

Mike Samuel
fuente
2

Los globales no son tan malos. Como se indicó en varias otras respuestas, el verdadero problema con ellas es que, hoy, su ruta de carpeta global puede ser, mañana, una de varias, o incluso cientos. Si está escribiendo un programa rápido y único, use globales si es más fácil. En general, sin embargo, permitir múltiples, incluso cuando solo cree que necesita uno, es el camino a seguir. No es agradable tener que reestructurar un gran programa complejo que de repente necesita hablar con dos bases de datos.

Pero no perjudican la fiabilidad. Cualquier dato al que se haga referencia desde muchos lugares en su programa puede causar problemas si cambia inesperadamente. Los enumeradores se ahogan cuando la colección que están enumerando cambia a mitad de la enumeración. Los eventos de la cola de eventos pueden jugar trucos entre ellos. Los hilos siempre pueden causar havok. Cualquier cosa que no sea una variable local o un campo inalterable es un problema. Los problemas globales son este tipo de problema, pero no vas a solucionarlo haciéndolos no globales.

Si está a punto de escribir en un archivo y la ruta de la carpeta cambia, el cambio y la escritura deben sincronizarse. (Como una de las mil cosas que podrían salir mal, digamos que toma la ruta, luego ese directorio se elimina, luego la ruta de la carpeta se cambia a un buen directorio, luego intenta escribir en el directorio eliminado). El problema existe si la ruta de la carpeta es global o es una de las mil que el programa está usando actualmente.

Existe un problema real con los campos a los que pueden acceder diferentes eventos en una cola, diferentes niveles de recursión o diferentes subprocesos. Para hacerlo simple (y simplista): las variables locales son buenas y los campos son malos. Pero los antiguos niveles globales seguirán siendo campos, por lo que este problema (aunque de importancia crítica) no se aplica al estado Bueno o Malo de los campos globales.

Adición: Problemas de subprocesos múltiples:

(Tenga en cuenta que puede tener problemas similares con una cola de eventos o llamadas recursivas, pero el subproceso múltiple es, con mucho, el peor). Considere el siguiente código:

if (filePath != null)  text = filePath.getName();

Si filePathes una variable local o algún tipo de constante, su programa no fallará cuando se ejecute porque filePathes nulo. El cheque siempre funciona. Ningún otro hilo puede cambiar su valor. De lo contrario , no hay garantías. Cuando comencé a escribir programas multiproceso en Java, obtuve NullPointerExceptions en líneas como esta todo el tiempo. Algunaotro hilo puede cambiar el valor en cualquier momento, y a menudo lo hacen. Como señalan otras respuestas, esto crea serios problemas para las pruebas. La declaración anterior puede funcionar mil millones de veces, realizar pruebas exhaustivas y exhaustivas, y luego explotar una vez en producción. Los usuarios no podrán reproducir el problema, y ​​no volverá a suceder hasta que se hayan convencido de que estaban viendo cosas y lo hayan olvidado.

Los globales definitivamente tienen este problema, y ​​si puede eliminarlos por completo o reemplazarlos con constantes o variables locales, eso es algo muy bueno. Si tiene código sin estado ejecutándose en un servidor web, probablemente pueda. Por lo general, todos los problemas de subprocesos múltiples pueden ser asumidos por la base de datos.

Pero si su programa tiene que recordar cosas de una acción del usuario a la siguiente, tendrá campos accesibles por cualquier subproceso en ejecución. Cambiar un campo global a uno no global no ayudará a la confiabilidad.

RalphChapin
fuente
1
¿Puede aclarar lo que quiere decir con esto ?: "Cualquier cosa que no sea una variable local o un campo inalterable es un problema. Los globales son este tipo de problema, pero no lo solucionará haciéndolos no globales".
Andres F.
1
@AndresF .: extendí mi respuesta. Creo que estoy adoptando un enfoque de escritorio donde la mayoría de las personas en esta página son más código de servidor con una base de datos. "Global" puede significar cosas diferentes en estos casos.
RalphChapin
2

El estado es inevitable en cualquier aplicación real. Puede concluirlo de la manera que desee, pero una hoja de cálculo debe contener datos en las celdas. Puede crear objetos de celda con solo funciones como una interfaz, pero eso no restringe cuántos lugares pueden llamar a un método en la celda y cambiar los datos. Construye jerarquías de objetos completos para tratar de ocultar interfaces para que otras partes del código no puedancambia los datos por defecto. Eso no impide que una referencia al objeto que contiene se pase arbitrariamente. Tampoco nada de eso elimina los problemas de concurrencia por sí mismo. Sí hace que sea más difícil proliferar el acceso a los datos, pero en realidad no elimina los problemas percibidos con los globales. Si alguien quiere modificar una parte del estado, lo hará a la intemperie si es global o mediante una API compleja (la última solo desalentará, no evitará).

La verdadera razón para no usar el almacenamiento global es evitar las colisiones de nombres. Si carga varios módulos que declaran el mismo nombre global, o tiene un comportamiento indefinido (muy difícil de depurar porque las pruebas unitarias pasarán) o un error de enlazador (estoy pensando C - ¿Su enlazador advierte o falla en esto?).

Si desea reutilizar el código, debe poder tomar un módulo de otro lugar y no dejar que accidentalmente pise su global porque usaron uno con el mismo nombre. O si tiene suerte y obtiene un error, no desea tener que cambiar todas las referencias en una sección de código para evitar la colisión.

phkahler
fuente
1

Cuando es fácil ver y acceder a todo el estado global, los programadores terminan invariablemente haciéndolo. Lo que obtienes es tácito y las dependencias difíciles de rastrear (int blahblah significa que array foo es válido en lo que sea). Esencialmente, hace que sea casi imposible mantener invariantes del programa, ya que todo se puede girar de forma independiente. someInt tiene una relación entre otherInt, que es difícil de administrar y más difícil de probar si puede cambiar directamente en cualquier momento.

Dicho esto, se puede hacer (cuando era la única forma en algunos sistemas), pero esas habilidades se pierden. Giran principalmente en torno a las convenciones de codificación y nomenclatura: el campo ha avanzado por una buena razón. Su compilador y enlazador hacen un mejor trabajo al verificar invariantes en datos protegidos / privados de clases / módulos que confiar en que los humanos sigan un plan maestro y lean la fuente.

luego
fuente
1
"pero esas habilidades se pierden" ... aún no del todo. Hace poco trabajé en una empresa de software que utiliza "Clarion", una herramienta generadora de código que tiene su propio lenguaje básico que carece de características como pasar argumentos a subrutinas ... Los desarrolladores no estaban contentos con las sugerencias sobre "cambio" o "modernización", finalmente me cansé de mis comentarios y me describió como deficiente e incompetente. Tuve que irme ...
Louis Somers
1

No voy a decir si las variables globales son buenas o malas, pero lo que voy a agregar a la discusión es decir que si no está utilizando el estado global, probablemente esté desperdiciando mucha memoria, especialmente cuando usa classess para almacenar sus dependencias en campos.

Para el estado global, no existe tal problema, todo es global.

Por ejemplo: imagine un escenario siguiente: tiene una cuadrícula de 10x10 que está hecha de clases "Tablero" y "Azulejo".

Si desea hacerlo de la manera OOP, probablemente pasará el objeto "Tablero" a cada "Mosaico". Digamos ahora que "Tile" tiene 2 campos de tipo "byte" que almacenan su coordenada. La memoria total que necesitaría en una máquina de 32 bits para un mosaico sería (1 + 1 + 4 = 6) bytes: 1 para x coord, 1 para y coord y 4 para un puntero a la placa. Esto da un total de 600 bytes para la configuración de 10x10 mosaicos

Ahora, para el caso en que la placa está en el ámbito global, un solo objeto accesible desde cada mosaico solo tendría que obtener 2 bytes de memoria por cada mosaico, esos son los bytes de coordenadas x e y. Esto daría solo 200 bytes.

Entonces, en este caso, obtienes 1/3 del uso de la memoria si solo usas el estado global.

Esto, además de otras cosas, supongo que es una razón por la cual el alcance global aún permanece en lenguajes (relativamente) de bajo nivel como C ++

luke1985
fuente
1
Eso depende mucho del idioma. En los lenguajes donde los programas se ejecutan de manera persistente, puede ser cierto que puede ahorrar memoria utilizando el espacio global y los tonos simples en lugar de crear instancias de muchas clases, pero incluso ese argumento es inestable. Se trata de prioridades. En lenguajes como PHP (que se ejecuta una vez por solicitud y los objetos no persisten), incluso ese argumento es discutible.
Madara Uchiha
1
@MadaraUchiha No, este argumento no es inestable de ninguna manera. Esos son hechos objetivos, a menos que alguna VM u otro lenguaje compilado haga una optimización de código hardcore con este tipo de problema. De lo contrario, sigue siendo relevante. Incluso con los programas del lado del servidor que solían ser "one shot", la memoria está reservada durante el tiempo de ejecución. Con servidores de alta carga, este puede ser un punto crítico.
luke1985
0

Hay varios factores a considerar con el estado global:

  1. Espacio de memoria del programador
  2. Globales inmutables / escribir una vez globales.
  3. Globales mutables
  4. Dependencia de los globales.

Cuantos más globales tenga, mayor será la posibilidad de introducir duplicados y, por lo tanto, romper las cosas cuando los duplicados no estén sincronizados. Mantener todos los globales en su falsa memoria humana se vuelve necesario y doloroso.

Inmutables / escritura una vez generalmente están bien, pero tenga cuidado con los errores de secuencia de inicialización.

Globales mutables a menudo se confunden con globales inmutables ...

Una función que utiliza globals efectivamente tiene parámetros "ocultos" adicionales, lo que dificulta la refactorización.

El estado global no es malo, pero tiene un costo definido: úselo cuando el beneficio sea mayor que el costo.

jmoreno
fuente