Muito brevemente, torna o estado do programa imprevisível.
Para elaborar, imagine que você tenha alguns objetos que usam a mesma variável global. Supondo que você não esteja usando uma fonte de aleatoriedade em nenhum dos módulos, a saída de um determinado método pode ser prevista (e, portanto, testada) se o estado do sistema for conhecido antes da execução do método.
No entanto, se um método em um dos objetos disparar um efeito colateral que altere o valor do estado global compartilhado, você não saberá mais qual é o estado inicial quando executar um método no outro objeto. Agora você não pode mais prever qual saída obterá quando executar o método e, portanto, não poderá testá-lo.
Em um nível acadêmico, isso pode não parecer tão sério, mas a capacidade de testar o código da unidade é um passo importante no processo de provar sua correção (ou pelo menos a adequação à finalidade).
No mundo real, isso pode ter algumas conseqüências muito sérias. Suponha que você tenha uma classe que preencha uma estrutura de dados global e uma classe diferente que consuma os dados nessa estrutura de dados, alterando seu estado ou destruindo-os no processo. Se a classe do processador executar um método antes que a classe populator termine, o resultado é que a classe do processador provavelmente terá dados incompletos para processar, e a estrutura de dados na qual a classe populadora estava trabalhando poderia ser corrompida ou destruída. O comportamento do programa nessas circunstâncias torna-se completamente imprevisível e provavelmente levará a perdas épicas.
Além disso, o estado global prejudica a legibilidade do seu código. Se o seu código tiver uma dependência externa que não seja explicitamente introduzida no código, quem conseguir a tarefa de manter seu código terá que procurá-lo para descobrir de onde veio.
Quanto a quais alternativas existem, bem é impossível não ter nenhum estado global, mas na prática geralmente é possível restringir o estado global a um único objeto que envolve todos os outros, e que nunca deve ser referenciado confiando em as regras de escopo da linguagem que você está usando. Se um objeto específico precisar de um estado particular, ele deverá explicitamente pedir por ele passado como um argumento para seu construtor ou por um método setter. Isso é conhecido como Injeção de Dependência.
Pode parecer bobo passar em um estado que você já pode acessar devido às regras de escopo de qualquer idioma que esteja usando, mas as vantagens são enormes. Agora, se alguém olha para o código isoladamente, fica claro o estado de que ele precisa e de onde vem. Ele também tem enormes benefícios em relação à flexibilidade do seu módulo de código e, portanto, as oportunidades de reutilizá-lo em diferentes contextos. Se o estado for passado e as alterações no estado forem locais para o bloco de código, você poderá passar em qualquer estado que desejar (se for o tipo de dados correto) e fazer com que seu código o processe. Código escrito neste estilo tende a ter a aparência de uma coleção de componentes fracamente associados que podem ser facilmente trocados. O código de um módulo não deve se importar de onde vem o estado, apenas como processá-lo. Se você passar o estado para um bloco de código, esse bloco de código pode existir isoladamente, o que não é o caso se você confiar no estado global.
Existem muitas outras razões pelas quais a aprovação do estado é muito superior à confiança no estado global. Esta resposta não é de forma abrangente. Você provavelmente poderia escrever um livro inteiro sobre por que o estado global é ruim.