Condiciones globales. Esta frase causa miedo y dolor en el corazón de cada desarrollador que ha tenido la desgracia de enfrentar este fenómeno. ¿Ya has encontrado un comportamiento inesperado de la aplicación, sin comprender sus razones, como un caballero infeliz que intenta matar a Hydra con muchas cabezas? Terminas en un ciclo interminable de prueba y error, el 90% de las veces te preguntas ¿qué está pasando?
Todo esto puede ser consecuencias molestas de los globales: variables ocultas que cambian su estado en lugares desconocidos, por razones que aún no has descubierto.
¿Te gusta pasear en la oscuridad mientras intentas cambiar la aplicación? Por supuesto que no me gusta. Afortunadamente, tengo velas para ti:
- Primero, describiré lo que con mayor frecuencia llamamos estados globales. Este término no siempre se usa con precisión, por lo tanto, requiere aclaración.
- A continuación, descubriremos por qué los globales son perjudiciales para nuestra base de código.
- Luego explicaré cómo recortar el alcance de las variables globales para convertirlas en variables locales.
- Finalmente, hablaré sobre la encapsulación y por qué la cruzada contra las variables globales es solo una parte del gran problema.
Espero que este artículo explique todo lo que necesita saber sobre los estados globales. Si crees que me he perdido mucho y me odias por esto y no quieres verme más, escríbelo en los comentarios. Será agradable para mí, mis lectores y todos los que de repente aparecieron en esta página.
¿Estás listo, querido lector, para subirte a un caballo y conocer a tu enemigo? ¡Busca estos globos y haz que prueben el acero de nuestras espadas!
¿Qué es una condición?

Comencemos con lo básico para que sus desarrolladores se entiendan entre sí.
Un estado es una definición de un sistema o entidad. Los estados se encuentran en la vida real:
- Cuando la computadora está apagada, su estado está apagado.
- Cuando una taza de té está caliente, su condición es caliente.
En el desarrollo de software, algunas construcciones (como las variables) pueden tener estados. Digamos que la cadena "hola" o el número 11 no se consideran estados, son valores. Se convierten en estado cuando se unen a una variable y se colocan en la memoria.
<?php echo "hello";
Se pueden distinguir dos tipos de estados:
Estados variables: después de su inicialización, pueden cambiar durante la ejecución de su aplicación en cualquier momento.
<?php $lala = "hello";
Estados inmutables: no puede cambiar durante la ejecución. Usted asigna el primer estado a su variable y su valor no cambia posteriormente. Las "constantes" en la vida cotidiana se llaman ejemplos de estados inmutables:
<?php define("GREETING", "hello");
Ahora escuchemos una conversación hipotética entre Denis y Vasily, sus colegas desarrolladores:
- Dan! ¡Has creado variables globales en todas partes! ¡No se pueden cambiar sin que todo se rompa! Te matare
- Nifiga, Vasek! ¡Mis fortunas globales son increíbles! Puse mi alma en ellos, ¡estas son obras maestras! ¡Adoro a mis globales!
Muy a menudo, los desarrolladores llaman estados globales, variables globales o globales lo que deberían llamar estados mutables globales. Es decir, estados que pueden modificarse en el mayor de los
ámbitos disponibles para usted: en toda la aplicación.
Cuando una variable no tiene toda la aplicación como ámbito, estamos hablando de variables locales o locales. Existen en algunas áreas de visibilidad, menos que el área de toda la aplicación.
<?php namespace App\Ecommerce; $global = "I'm a mutable global variable!";
Podrías pensar: ¡qué conveniente es tener variables a las que puedas acceder desde cualquier lugar y cambiarlas! ¡Puedo transferir estados de una parte de la aplicación a otra! ¡No es necesario pasarlos por las funciones y escribir tanto código! ¡Salve, estado mutable global!
Si realmente lo pensabas, te recomiendo continuar leyendo.
¿Estados globales peores que la peste y el cólera?
El diagrama de enlaces más grande
Realidad: será más fácil para usted crear un diagrama de conexión de aplicación preciso si solo contiene configuraciones regionales con ámbitos pequeños y definidos, sin ningún tipo de globals.
Por qué
Digamos que tiene una aplicación grande con variables globales. Cada vez que necesite cambiar algo, debe:
- Recordemos que existen estos estados globales mutables.
- Calcule si afectarán el alcance que va a cambiar.
Por lo general, no necesita pensar en variables locales que se encuentran en otros ámbitos, pero haga lo que haga, siempre debe mantener en su cerebro cansado un lugar para estados mutables globales, ya que pueden afectar a todos los ámbitos.
Además, sus estados mutables globales pueden cambiar en cualquier lugar de la aplicación. Por lo general, uno debe preguntarse cuál es su estado actual. Esto significa que está obligado a buscar en toda la aplicación, tratando de calcular los valores de los globales en el ámbito modificable.
Eso no es todo. Si necesita cambiar el estado de los globales, entonces no se imaginará a qué alcance afectará. ¿Esto conducirá a un comportamiento inesperado de otra clase, método o función? Éxito en la búsqueda.
En resumen, combina todas las clases, métodos y funciones que usan el mismo estado global. No lo olvide: las
dependencias aumentan considerablemente la complejidad . ¿Te asusta? Debería ser Las pequeñas áreas específicas de visibilidad son muy útiles: no necesita tener en cuenta toda la aplicación, es suficiente recordar solo las áreas con las que trabaja.
La gente no rastrea de inmediato una gran cantidad de información. Cuando intentamos hacer esto, agotamos rápidamente el suministro de capacidades cognitivas, nos resulta difícil concentrarnos y comenzamos a crear errores y cosas estúpidas. Por eso es tan desagradable actuar en el ámbito global de su aplicación.
Colisiones de nombres globales
Hay dificultades para usar bibliotecas de terceros. Imagina que quieres usar esa biblioteca súper genial que colorea aleatoriamente cada personaje con un efecto de parpadeo. El sueño de cada desarrollador! Si esta biblioteca también usa globales que tienen los mismos nombres que los suyos, entonces disfrutará de colisiones de nombres. Su aplicación se bloqueará y adivinará los motivos, probablemente durante mucho tiempo:
- Primero, necesitará descubrir que su biblioteca utiliza variables globales.
- En segundo lugar, debe calcular qué variable se usó durante la ejecución: ¿la suya o las bibliotecas? ¡No es tan simple, los nombres son los mismos!
- En tercer lugar, como no puede cambiar la biblioteca usted mismo, debe cambiar el nombre de sus variables mutables globales. Si se usa en toda la aplicación, llorará.
En cada etapa, te arrancarás el pelo de la rabia y la desesperación. Pronto ya no necesitarás un peine. Es poco probable que este escenario te seduzca. Quizás alguien recordará que las bibliotecas de JavaScript Mootools, Underscore y jQuery siempre chocaban entre sí si no se colocaban en ámbitos más pequeños. ¡Ah, y el famoso objeto global
$
en jQuery!
Las pruebas se convertirán en una pesadilla.
Si aún no te he convencido, veamos la situación desde el punto de vista de las pruebas unitarias: ¿cómo se escriben las pruebas en presencia de variables globales? Dado que las pruebas pueden cambiar las variables globales, no sabe en qué prueba se encontraba el estado. Debe aislar las pruebas entre sí, y los estados globales las unen.
¿Alguna vez lo ha tenido para que las pruebas de aislamiento funcionen bien, y cuando ejecute todo el paquete, fallarán? No? Y lo tuve. Cada vez que recuerdo esto, sufro.
Problemas de concurrencia
Los estados globales variables pueden causar muchos problemas si necesita concurrencia. Cuando cambias el estado de los globales en varios hilos de ejecución, entonces enloquece en un poderoso
estado de la carrera .
Si eres un desarrollador de PHP, esto no te molesta, a menos que uses bibliotecas que te permitan crear paralelismo. Sin embargo, cuando
aprende un nuevo idioma en el cual el paralelismo es fácil de implementar, espero que recuerde mi prosa.
Evitar estados mutables globales

Si bien los estados mutables globales pueden causar muchos problemas, a veces son difíciles de evitar.
Tome la API REST: los puntos finales reciben algún tipo de solicitudes HTTP con parámetros y envían respuestas. Estos parámetros HTTP enviados al servidor se pueden exigir en muchos niveles de su aplicación. Es muy tentador hacer que estos parámetros sean globales al recibir una solicitud HTTP, modificándolos antes de enviar una respuesta. Agregue paralelismo en la parte superior de cada solicitud y la receta para desastres estará lista.
Los estados mutables globales también se pueden admitir directamente en implementaciones de lenguaje. Por ejemplo, en PHP hay
superglobales .
Si los globales provienen de algún lugar, ¿cómo lidiar con ellos? ¿Cómo refactorizar la aplicación de Denis, su compañero desarrollador que creó globales cuando fue posible, porque no ha leído nada sobre desarrollo en los últimos 20 años?
Argumentos de funciones
La forma más fácil de evitar los globales es pasar variables usando argumentos de función. Tome un ejemplo simple:
<?php namespace App; use Router\HttpRequest; use App\Product\ProductData; use App\Exceptions; class ProductController { public function createAction(HttpRequest $httpReq) { $productData = $httpReq->get("productData"); if (!$this->productModel->validateProduct($productData)) { return ValidationException(sprintf("The product %d is not valid", $productData["id"])); } $product = $this->productModel->createProduct($productData); } } class Product { public function createProduct(array $productData): Product { $productData["name"] = "SuperProduct".$productData["name"];
Como puede ver, la
$productData
del controlador, a través de una solicitud HTTP, pasa por diferentes niveles:
- El controlador recibió una solicitud HTTP.
- Parámetros pasados al modelo.
- Los parámetros pasaron al DAO .
- Los parámetros se guardan en la base de datos de la aplicación.
Podríamos hacer que esta matriz de parámetros sea global cuando la recuperemos de la solicitud HTTP. Parece más simple: no es necesario transferir datos a 4 funciones diferentes. Sin embargo, pasar parámetros como argumentos a funciones:
- Obviamente, mostrará que estas funciones usan la
$productData
.
- Obviamente mostrará qué funciones usan qué parámetros. Se puede ver que para
ProductDao::find
de la $productData
, solo $id
necesita $id
, y no todo.
Los globales hacen que el código sea menos comprensible y asocian métodos entre sí, lo cual es un precio muy alto por la ausencia casi total de ventajas.
Ya escuchas a Denis protestar: “¿Y si una función tiene tres o más argumentos? Si necesita agregar aún más, la complejidad de la función aumentará. ¿Y qué pasa con las variables, objetos y otras construcciones que se necesitan en todas partes? ¿Los pasará a cada función en la aplicación?
Las preguntas son justas, querido lector. Como
buen desarrollador , debe explicarle a Denis, usando sus habilidades de comunicación, esto es lo que:
“Denis, si tus funciones tienen demasiados argumentos, entonces las funciones mismas pueden ser un problema. Probablemente hacen demasiado, son responsables de demasiadas cosas. ¿No pensaste en dividirlos en funciones más pequeñas? " .
Sintiéndose como un orador en la Acrópolis de Atenas, continúa:
“Si necesita variables en muchas áreas de visibilidad, entonces este es un problema, y pronto hablaremos de ello. Pero si realmente los necesita, ¿qué hay de malo en pasarlos a través de argumentos de función? Sí, tendrá que escribirlos en el teclado, pero somos desarrolladores, nuestro trabajo es escribir código ".Puede parecer más complicado cuando tiene más argumentos (quizás esto sea así), pero repito, las ventajas superan a las desventajas: es mejor que el código sea lo más claro posible y no use estados mutables globales ocultos.
Objetos de contexto
Los objetos contextuales son aquellos que contienen datos definidos por algún contexto. Por lo general, estos datos se almacenan como una construcción de pares de claves, como una matriz asociativa en PHP. Tal objeto no tiene comportamiento, solo datos, similar
a un objeto de valor .
Un objeto de contexto puede reemplazar cualquier estado mutable global. Regrese al ejemplo de código anterior. En lugar de pasar datos de la solicitud a través de los niveles, podemos usar un objeto que encapsule estos datos.
El contexto será la consulta misma: otra consulta, otro contexto, otro conjunto de datos. Luego, el objeto de contexto se pasará a cualquier método que necesite estos datos.
Usted dice: "Es increíble y todo eso, pero ¿qué da?"
- Los datos se encapsulan en un objeto. La mayoría de las veces, su tarea será hacer que los datos sean inmutables, es decir, para que no pueda cambiar el estado, el valor de los datos en el objeto después de la inicialización.
- Obviamente, el contexto necesita los datos del objeto de contexto, ya que se transfiere a todas las funciones (o métodos) que necesitan estos datos.
- Esto resuelve el problema de concurrencia: si cada solicitud tiene su propio objeto de contexto, puede escribirlos o leerlos con seguridad en sus propios hilos de ejecución.
Pero todo en desarrollo tiene un precio. Los objetos de contexto pueden ser dañinos:
- Si observa los argumentos de la función, no sabrá qué datos hay en el objeto de contexto.
- Puede poner cualquier cosa en un objeto de contexto. Tenga cuidado de no poner demasiado, por ejemplo, toda la sesión del usuario, o incluso la mayoría de los datos de su aplicación. Y entonces esto puede suceder:
$context->getSession()->getUser()->getProfil()->getUsername()
. Rompe la ley de Deméter y tu maldición será una locura de complejidad.
- Cuanto más grande es el objeto de contexto, más difícil es descubrir qué datos y en qué ámbito utiliza.
En general, evitaría usar objetos contextuales en la medida de lo posible. Pueden causar muchas dudas. La inmutabilidad de los datos es una gran ventaja, pero no debemos olvidarnos de las deficiencias. Si está utilizando un objeto de contexto, asegúrese de que sea lo suficientemente pequeño y páselo a un ámbito pequeño y cuidadosamente definido.
Si antes de ejecutar un programa no tiene idea de cuántos estados se pasarán a sus funciones (por ejemplo, parámetros de una solicitud HTTP), los objetos de contexto pueden ser útiles. Por lo tanto, algunos de ellos los usan, recuerde, por ejemplo, el objeto
Request
en Symfony.
Inyección de dependencia
Otra buena alternativa a los estados mutables globales sería incrustar directamente los datos que necesita en el objeto justo cuando lo cree. Esta es la definición de inyección de dependencia: un conjunto de técnicas para incrustar objetos en sus componentes (clases).
¿Por qué exactamente la inyección de dependencia?
El objetivo es limitar el uso de sus variables, objetos u otras construcciones, y colocarlas en un alcance limitado. Si tiene dependencias incrustadas y, por lo tanto, solo puede actuar dentro del alcance de un objeto, será más fácil para usted averiguar en qué contexto se utilizan y por qué. ¡Sin angustia ni tormento!
La inyección de dependencia divide el ciclo de vida de la aplicación en dos fases importantes:
- Crear objetos de aplicación e implementar sus dependencias.
- Usa objetos para alcanzar tus objetivos.
Este enfoque aclara el código; no es necesario crear una instancia de todo en lugares aleatorios o, lo que es peor, usar objetos globales en todas partes.
Muchos marcos usan inyección de dependencia, a veces en esquemas bastante complejos, con archivos de configuración y un Contenedor de inyección de dependencia (DIC). Pero no es necesario complicar las cosas. Simplemente puede crear dependencias en un nivel e implementarlas en un nivel inferior. Por ejemplo, en el mundo Go, no conozco a nadie que use DIC. Simplemente cree las dependencias en el archivo principal con el código (main.go) y luego transfiéralas al siguiente nivel. También puede crear una instancia de todo en diferentes paquetes para indicar claramente que la "fase de inyección de dependencia" debe realizarse solo en este nivel específico. En Go, el alcance de los paquetes puede facilitar las cosas que en PHP, en el que los DIC se usan ampliamente en todos los marcos que conozco, incluidos Symfony y Laravel.
Implementación vía constructor o setters
Hay dos formas de inyectar dependencias: a través del constructor o establecedores. Aconsejo, si es posible, atenerse al primer método:
- Si necesita saber qué dependencias de clase, todo lo que tiene que hacer es encontrar un constructor. No es necesario buscar métodos dispersos por toda la clase.
- Establecer dependencias durante la instalación le dará confianza en la seguridad de usar el objeto.
Hablemos un poco sobre el último punto: esto se llama "imposición invariante". Al crear una instancia de un objeto e implementar sus dependencias, sabe que no importa lo que necesite su objeto, se configura correctamente. Y si usa setters, ¿cómo sabe que sus dependencias ya están configuradas al momento de usar el objeto? Puedes ir a la pila e intentar averiguar si se llamó a los setters, pero estoy seguro de que no quieres hacer esto.
Violación de encapsulación
Después de todo, la única diferencia entre los estados locales y globales es su alcance. Están limitados para los estados locales, y para global, toda la aplicación está disponible. Sin embargo, puede encontrar problemas específicos de los estados globales si usa estados locales. Por qué
¿Dijiste encapsulación?
El uso de estados globales eventualmente romperá la encapsulación, del mismo modo que usted puede romperla con los estados locales.
Comencemos desde el principio. ¿Qué nos dice
Wikipedia sobre la definición de encapsulación? El mecanismo del lenguaje para restringir el acceso directo a algunos componentes de un objeto. Restricción de acceso? Por qué
Bueno, como vimos anteriormente, es mucho más fácil razonar en el ámbito local que en el global. Los estados mutables globales están, por definición, disponibles en todas partes, ¡y esto está en contra de la encapsulación! No hay restricciones de acceso para usted.
Alcance creciente y fugas de estado

Imaginemos un estado en su propio ámbito pequeño. Desafortunadamente, con el tiempo, la aplicación crece, este estado local se pasa como un argumento a la función en toda la aplicación. Ahora su configuración regional se utiliza en muchos ámbitos, y en todos ellos se autoriza el acceso directo a la configuración regional. Ahora le resulta difícil calcular el estado exacto de la configuración regional sin tener en cuenta todas las áreas de visibilidad en las que existe y dónde se puede cambiar. Todo esto ya lo hemos visto con estados mutables globales.
Tome un ejemplo: el
modelo de dominio anémico puede aumentar el alcance de sus modelos mutables. De hecho, el modelo de dominio anémico divide los datos y el comportamiento de los objetos de su dominio en dos grupos: modelos (objetos solo con datos) y servicios (objetos solo con comportamiento). Muy a menudo, estos modelos se utilizarán en todos los servicios. Por lo tanto, es probable que algún tipo de modelo aumente constantemente el alcance. No entenderá qué modelo se utiliza en qué contexto, su estado cambiará y todos los mismos problemas caerán sobre usted.
Quiero transmitir una idea importante: si evita estados mutables globales, esto no significa que pueda relajarse, sostener un cóctel en una mano y presionar los botones con la otra, disfrutando de la vida y su código legendario. -,
, -, , .
, . , .
? - . , , -.
, - , , . , , — . , : , . .
.
.
Product
, , :
class Product { public function createProduct(array $productData): Product { $productData["name"] = "SuperProduct".$productData["name"];
$productData
. , , , .
, . , - ? , . .
, , . .
:
class Product { public function createProduct(array $productData): Product {
, ,
$productData
. , .
$productData
, , HTTP-.
, : «, ».
?

. , .
?
. , .
,
ShipmentDelay
, , , . , -,
ShipmentDelay
, , , . ? ,
DRY .
, , . : , , . , , , . , , .
?
, (, ), , , . , , , , .
. :
. , — . , .
, , .
, , . , . , , . , !