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 cualquier lugar dentro de cualquiera de los módulos, entonces la salida de un método particular puede predecirse (y, por lo tanto, probarse) 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 sabrá cuál es el estado de inicio 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 parecer tan serio, pero el hecho de poder unificar el código de prueba es un paso importante en el proceso de demostrar su corrección (o al menos la idoneidad para un propósito).
En el mundo real, esto puede tener algunas consecuencias muy serias. Supongamos 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 populador, 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 populador 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 daña la legibilidad de su código. Si su código tiene una dependencia externa que no se introduce explícitamente en el código, quienquiera que tenga la tarea de mantener su código tendrá que buscarlo para averiguar de dónde proviene.
En cuanto a qué alternativas existen, bueno, es imposible no tener ningún estado global, pero en la práctica, generalmente es posible restringir el estado global a un solo objeto que envuelve a todos los demás, y al que nunca se debe hacer referencia al confiar en Las reglas de alcance del lenguaje que estás usando. Si un objeto en particular necesita un estado en particular, entonces debe solicitarlo explícitamente al pasarlo como un 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 se puede acceder debido a las reglas de alcance del idioma que esté usando, pero las ventajas son enormes. Ahora, si alguien mira el código de forma aislada, queda claro qué estado necesita y de dónde proviene. También tiene enormes beneficios en cuanto 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 al estado son locales al bloque de código, entonces puede pasar el estado que desee (si es el tipo de datos correcto) y hacer que el código lo procese. El código escrito en este estilo tiende a tener la apariencia de una colección de componentes asociados sueltos que pueden intercambiarse fácilmente. El código de un módulo no debería importar de dónde viene el estado, solo cómo procesarlo. Si pasa el estado a un bloque de código, ese bloque de código puede existir de forma aislada, no es así si confía en el estado global.
Hay muchas otras razones por las que pasar el estado es muy superior a depender del estado global. Esta respuesta no es de ninguna manera completa. Probablemente podrías escribir un libro completo sobre por qué el estado global es malo.