Por que o estado global é tão mau?

328

Antes de começarmos, deixe-me dizer que estou bem ciente dos conceitos de Abstração e Injeção de Dependência. Não preciso abrir meus olhos aqui.

Bem, a maioria de nós diz (muitas vezes) sem entender realmente: "Não use variáveis ​​globais" ou "Singletons são maus porque são globais". Mas o que é realmente tão ruim no estado global ameaçador?

Digamos que eu precise de uma configuração global para meu aplicativo, por exemplo, caminhos de pasta do sistema ou credenciais de banco de dados em todo o aplicativo.

Nesse caso, não vejo outra solução boa além de fornecer essas configurações em algum tipo de espaço global, que estará normalmente disponível para todo o aplicativo.

Eu sei que é ruim para abusar dela, mas é o espaço global realmente QUE mal? E se for, que boas alternativas existem?

Madara Uchiha
fonte
5
você pode usar uma classe de configuração que lida com o acesso a essas configurações. Eu acho que é uma boa prática ter um e apenas um lugar onde as definições de configuração são lidas a partir de arquivos de configuração e / ou bancos de dados, enquanto outros objetos os buscam nessa configuração. isso torna seu design mais limpo e previsível.
Hajo
25
Qual a diferença em usar um global? Seu "único e único lugar" soa muito desconfiado como um Singleton.
Madara Uchiha
130
O único mal real são os dogmas.
Pieter B
34
Usar o estado global com seus colegas programadores é como usar a mesma escova de dentes com seus amigos - você pode, mas nunca sabe quando alguém decidirá enfiá-lo na bunda.
ZiGi 15/05
5
O diabo é mau. O estado global não é mau nem bom. Pode ser muito útil ou prejudicial, dependendo de como você o usa. Não ouça os outros e apenas aprenda a programar corretamente.
annoying_squid

Respostas:

282

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 qualquer lugar de qualquer módulo, a saída de um método específico 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 acionar um efeito colateral que altera o valor do estado global compartilhado, você não saberá mais qual é o estado inicial ao 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.

No nível acadêmico, isso pode não parecer muito sério, mas ser capaz de realizar o código do teste de unidade é um passo importante no processo de comprovação de sua correção (ou pelo menos adequação à finalidade).

No mundo real, isso pode ter algumas consequências muito graves. Suponha que você tenha uma classe que preenche uma estrutura de dados global e uma classe diferente que consome os dados nessa estrutura de dados, alterando seu estado ou destruindo-os no processo. Se a classe do processador executar um método antes da conclusão da classe do preenchedor, o resultado é que a classe do processador provavelmente terá dados incompletos para processar e a estrutura de dados na qual a classe do preenchedor estava trabalhando pode ser corrompida ou destruída. O comportamento do programa nessas circunstâncias se torna completamente imprevisível e provavelmente levará a uma perda épica.

Além disso, o estado global prejudica a legibilidade do seu código. Se o seu código tem uma dependência externa que não é explicitamente introduzida no código, quem conseguir manter seu código precisará procurar por ele para descobrir de onde veio.

Quanto às alternativas existentes, bem, é impossível não ter um 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 com base nas regras de escopo do idioma que você está usando. Se um objeto em particular precisa de um estado específico, ele deve solicitá-lo explicitamente, passando-o como argumento para seu construtor ou por um método setter. Isso é conhecido como injeção de dependência.

Pode parecer bobagem 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 olhar o código isoladamente, ficará claro de que estado ele precisa e de onde ele vem. Ele também traz enormes benefícios com relação à flexibilidade do seu módulo de código e, portanto, às oportunidades de reutilizá-lo em diferentes contextos. Se o estado for passado e as alterações forem locais para o bloco de código, você poderá passar o estado que desejar (se for o tipo de dados correto) e fazer com que seu código o processe. O código escrito nesse estilo tende a parecer uma coleção de componentes vagamente 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.

Existem muitas outras razões pelas quais a transferência de estado é muito superior à dependência do estado global. Esta resposta não é de forma alguma abrangente. Você provavelmente poderia escrever um livro inteiro sobre por que o estado global é ruim.

GordonM
fonte
61
Basicamente, como qualquer pessoa pode mudar o estado, você não pode confiar nele.
Oded
21
@ Verdade A alternativa? Injeção de Dependência .
Rdlowrey #
12
Seu argumento principal não se aplica realmente a algo que é efetivamente somente leitura, como um objeto que representa uma configuração.
31712 frankc
53
Nada disso explica por que as variáveis ​​globais somente leitura são ruins ... sobre as quais o OP perguntou explicitamente. É uma resposta boa, caso contrário, fico surpreso que o OP tenha marcado como “aceito” quando ele abordou explicitamente um ponto 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). Não, não é. "Já se passaram duas décadas desde que foi apontado que o teste do programa pode demonstrar de forma convincente a presença de bugs, mas nunca pode demonstrar sua ausência. Depois de citar essa observação bem divulgada com devoção, o engenheiro de software retorna à ordem do dia e continua refinar suas estratégias de teste, assim como o alquimista de outrora, que continuou refinando suas purificações crisocósmicas ". - Djikstra, 1988. (Isso faz 4,5 décadas agora ...)
Mason Wheeler
135

O estado global mutável é mau por vários motivos:

  • Erros do estado global mutável - muitos erros complicados são causados ​​por mutabilidade. Os erros que podem ser causados ​​por mutações de qualquer lugar do programa são ainda mais complicados, pois geralmente é difícil rastrear a causa exata
  • Baixa testabilidade - se você tiver um estado global mutável, precisará configurá-lo para todos os testes que escrever. Isso torna os testes mais difíceis (e as pessoas sendo pessoas são, portanto, menos propensas a fazê-lo!). por exemplo, no caso de credenciais de banco de dados para todo o aplicativo, e se um teste precisar acessar um banco de dados de teste específico diferente de todo o resto?
  • Inflexibilidade - e se uma parte do código exigir um valor no estado global, mas outra parte exigir outro valor (por exemplo, um valor temporário durante uma transação)? De repente, você tem um pouco desagradável de refatoração em suas mãos
  • Impureza da função - funções "puras" (ou seja, aquelas em que o resultado depende apenas dos parâmetros de entrada e não têm efeitos colaterais) são muito mais fáceis de raciocinar e compor para criar programas maiores. As funções que lêem ou manipulam o estado global mutável são inerentemente impuras.
  • Compreensão do código - o comportamento do código que depende de muitas variáveis ​​globais mutáveis ​​é muito mais complicado de entender - você precisa entender o intervalo de possíveis interações com a variável global antes de poder raciocinar sobre o comportamento do código. Em algumas situações, esse problema pode se tornar intratável.
  • Problemas de simultaneidade - o estado global mutável geralmente requer alguma forma de bloqueio quando usado em uma situação simultânea. É muito difícil acertar (é uma causa de bugs) e adiciona consideravelmente mais complexidade ao seu código (difícil / caro de manter).
  • Desempenho - vários encadeamentos continuamente baseando-se no mesmo estado global causam contenção de cache e tornam o sistema mais lento.

Alternativas ao estado global mutável:

  • Parâmetros de função - geralmente ignorados, mas parametrizar melhor suas funções geralmente é a melhor maneira de evitar o estado global. Obriga você a resolver a importante questão conceitual: que informações essa função requer para fazer seu trabalho? Às vezes, faz sentido ter uma estrutura de dados chamada "Contexto" que pode ser transmitida por uma cadeia de funções que agrupa todas as informações relevantes.
  • Injeção de dependência - o mesmo que para os parâmetros de função, feito um pouco antes (na construção do objeto em vez de na invocação de função). Tenha cuidado, se suas dependências são objetos mutáveis, isso pode causar rapidamente os mesmos problemas que o estado global mutável .....
  • O estado global imutável é quase inofensivo - é efetivamente uma constante. Mas certifique-se de que é realmente uma constante e que você não será tentado a transformá-lo em um estado global mutável posteriormente!
  • Singletons imutáveis - praticamente o mesmo que estado global imutável, exceto que você pode adiar a instanciação até que sejam necessários. Útil para, por exemplo, grandes estruturas de dados fixas que precisam de um pré-cálculo caro e único. Singletons mutáveis ​​são obviamente equivalentes a um estado global mutável e, portanto, são maus :-)
  • Ligação dinâmica - disponível apenas em alguns idiomas como Common Lisp / Clojure, mas isso efetivamente permite vincular um valor a um escopo controlado (normalmente em uma base local de threads) que não afeta outros threads. Até certo ponto, essa é uma maneira "segura" de obter o mesmo efeito que uma variável global, pois você sabe que apenas o encadeamento atual de execução será afetado. Isso é particularmente útil no caso em que você possui vários encadeamentos, cada um manipulando transações independentes, por exemplo.
Mikera
fonte
11
Eu acho que passar um objeto de contexto por parâmetro de função ou injeção de dependência causaria problemas se o contexto fosse mutável, o mesmo problema que o uso de estado global mutável.
Alfredo Osorio
1
Todas as coisas boas e amém! Mas a pergunta é sobre o estado global imutável
MarkJ
@ Alfredo - muito verdadeiro. Embora não seja tão ruim, pois pelo menos o escopo pode ser um pouco controlado. Mas, em geral, é mais fácil resolver o problema, tornando imutáveis ​​os contextos e as dependências.
Mikera 12/05/12
2
+1 para mutável / imutável. Globais imutáveis ​​estão ok. Mesmo os que são preguiçosos carregados, mas nunca mudam. Obviamente, não exponha variáveis ​​globais, mas uma interface global ou API.
Jess
1
@giorgio A questão deixa claro que as variáveis ​​em questão obtêm seus valores na inicialização e nunca mudam posteriormente durante a execução do programa (pastas do sistema, credenciais do banco de dados). Ou seja, imutável, ele não muda depois de receber um valor. Pessoalmente, também uso a palavra "estado" porque pode ser diferente de uma execução para outra ou em uma máquina diferente. Pode haver palavras melhores.
MarkJ
62
  1. Como todo o maldito aplicativo pode usá-lo, é sempre incrivelmente difícil considerá-los novamente. Se você mudar alguma coisa com o seu global, todo o seu código precisará ser alterado. Isso é uma dor de cabeça para a manutenção - muito mais do que simplesmente poder grepsaber o nome do tipo para descobrir quais funções o utilizam.
  2. Eles são ruins porque introduzem dependências ocultas, que quebram o multithreading, o que é cada vez mais vital para cada vez mais aplicativos.
  3. O estado da variável global é sempre totalmente não confiável, porque todo o seu código pode estar fazendo algo para ele.
  4. Eles são realmente difíceis de testar.
  5. Eles dificultam a chamada da API. "Você deve se lembrar de ligar para SET_MAGIC_VARIABLE () antes de chamar a API" está apenas implorando para alguém esquecer de chamá-la. Isso torna o uso da API propenso a erros, causando bugs difíceis de encontrar. Ao usá-lo como um parâmetro regular, você força o chamador a fornecer um valor corretamente.

Basta passar uma referência para as funções que precisam. Não é tão difícil.

DeadMG
fonte
3
Bem, você pode ter uma classe de configuração global que encapsula o bloqueio e é projetada para mudar de estado a qualquer momento possível. Eu escolheria essa abordagem em vez de instanciar leitores de configuração de lugares 1000x no código. Mas sim, imprevisibilidade é a pior coisa sobre eles.
Coder
6
@ Codificador: Observe que a alternativa sã a uma global não é "leitores de configuração de lugares 1000x no código", mas um leitor de configuração, que cria um objeto de configuração, que métodos podem aceitar como parâmetro (-> injeção de dependência).
Sleske
2
Nitpicking: Por que é mais fácil grep para um tipo do que para um global? E a pergunta é globals cerca de somente leitura, para que o ponto 2 e 3 não são relevantes
MarkJ
2
@ MarkJ: Espero que ninguém, digamos, tenha sombreado o nome.
DeadMG
2
@DeadMG, Re "..é sempre completamente não confiável ..", o mesmo vale para injeção de dependência. Só porque você o transformou em um parâmetro, não significa que o obj tenha um estado fixo. Em algum lugar abaixo da função, você poderia fazer uma chamada para outra função que modifica o estado da variável injetada na parte superior.
Pacerier 19/09/17
34

Se você diz "estado", isso geralmente significa "estado mutável". E o estado mutável global é totalmente mau, porque significa que qualquer parte do programa pode influenciar qualquer outra parte (alterando o estado global).

Imagine depurar um programa desconhecido: você acha que a função A se comporta de certa maneira para determinados parâmetros de entrada, mas às vezes funciona de maneira diferente para os mesmos parâmetros. Você descobre que ele usa a variável global x .

Você procura lugares que modificam x e descobre que há cinco lugares que o modificam. Agora boa sorte descobrir em que casos a função A faz o que ...

sleske
fonte
Estado global tão imutável não é tão mau?
FrustratedWithFormsDesigner
50
Eu diria que o estado global imutável é a boa prática conhecida como 'constantes'.
Telastyn
6
Estado global imutável não é mau, é apenas mau :-). Ainda é problemático por causa do acoplamento introduzido (dificulta as alterações, a reutilização e o teste de unidade), mas cria muito menos problemas; portanto, em casos simples, geralmente é aceitável.
sleske
2
Se o IFF usa variáveis ​​globais, apenas um trecho de código deve modificá-lo. O resto é livre para ler. As questões de outras pessoas que a alteram não desaparecem com as funções de encapsulamento e acesso. Não é para isso que servem essas construções.
Phkahler
1
@ Pacerier: Sim, é difícil alterar uma interface amplamente usada, independentemente de ser usada como uma variável global ou local. No entanto, isso é independente do meu argumento, que é que a interação de diferentes partes do código é difícil de entender com vars globais.
sleske
9

Você meio que respondeu sua própria pergunta. Eles são difíceis de gerenciar quando 'abusados', mas podem ser úteis e [um pouco] previsíveis quando usados ​​adequadamente, por alguém que sabe como contê-los. A manutenção e as alterações nos globais geralmente são um pesadelo, agravadas à medida que o tamanho do aplicativo aumenta.

Programadores experientes que sabem a diferença entre os globais serem a única opção e a solução mais fácil, podem ter problemas mínimos ao usá-los. Mas os intermináveis ​​problemas possíveis que podem surgir com o uso deles requerem conselhos contra o uso deles.

editar: Para esclarecer o que quero dizer, os globais são imprevisíveis por natureza. Como em qualquer coisa imprevisível, você pode tomar medidas para conter a imprevisibilidade, mas sempre há limites para o que pode ser feito. Adicione a isso o incômodo de novos desenvolvedores ingressarem no projeto, tendo que lidar com variáveis ​​relativamente desconhecidas, as recomendações contra o uso de globais devem ser compreensíveis.

orourkek
fonte
9

Existem muitos problemas com Singletons - aqui estão os dois maiores problemas em minha mente.

  • Isso torna o teste de unidade problemático. O estado global pode ficar contaminado de um teste para o outro

  • Ela impõe a regra rígida "Um-e-um-único", que, mesmo que não possa mudar, muda repentinamente. Todo um código de utilidade que usou o objeto acessível globalmente precisa ser alterado.

Dito isto, a maioria dos sistemas precisa de grandes objetos globais. Esses são itens grandes e caros (por exemplo, gerenciadores de conexão com o banco de dados), ou mantêm informações gerais do estado (por exemplo, informações de bloqueio).

A alternativa para um Singleton é ter esses grandes objetos globais criados na inicialização e passados ​​como parâmetros para todas as classes ou métodos que precisam acessar esse objeto.

O problema aqui é que você acaba com um grande jogo de "passe-o-pacote". Você tem um gráfico de componentes e suas dependências, e algumas classes criam outras classes, e cada uma precisa conter vários componentes de dependência apenas porque seus componentes gerados (ou os componentes dos componentes gerados) precisam deles.

Você encontra novos problemas de manutenção. Um exemplo: De repente, seu componente "WidgetFactory", no fundo do gráfico, precisa de um objeto de timer que você deseja zombar. No entanto, "WidgetFactory" é criado por "WidgetBuilder", que faz parte de "WidgetCreationManager", e você precisa ter três classes que conheçam esse objeto de timer, mesmo que apenas uma realmente o use. Você deseja desistir e reverter para os Singletons e apenas tornar esse objeto de timer globalmente acessível.

Felizmente, esse é exatamente o problema resolvido por uma estrutura de injeção de dependência. Você pode simplesmente dizer à estrutura que classes ele precisa criar e usar a reflexão para descobrir o gráfico de dependência para você, e constrói automaticamente cada objeto quando necessário.

Portanto, em resumo, os singletons são ruins e a alternativa é usar uma estrutura de injeção de dependência.

Por acaso uso o Castelo Windsor, mas você é mimado pela escolha. Veja esta página em 2008 para obter uma lista das estruturas disponíveis.

Andrew Shepherd
fonte
8

Antes de tudo, para que a injeção de dependência seja "stateful", você precisará usar singletons, para que as pessoas que dizem que essa seja uma alternativa sejam enganadas. As pessoas usam objetos de contexto global o tempo todo ... Mesmo o estado da sessão, por exemplo, é essencialmente uma variável global. Passar tudo ao redor, seja por injeção de dependência ou não, nem sempre é a melhor solução. Atualmente, trabalho em um aplicativo muito grande que usa muitos objetos de contexto global (singletons injetados por meio de um contêiner de IoC) e nunca foi um problema para depurar. Especialmente com uma arquitetura orientada a eventos, pode ser preferível usar objetos de contexto global versus repassar o que mudou. Depende de quem você pergunta.

Qualquer coisa pode ser abusada e isso também depende do tipo de aplicativo. O uso de variáveis ​​estáticas, por exemplo, em um aplicativo Web é completamente diferente de um aplicativo de desktop. Se você pode evitar variáveis ​​globais, faça-o, mas às vezes elas têm seus usos. No mínimo, verifique se seus dados globais estão em um objeto contextual claro. Quanto à depuração, nada que uma pilha de chamadas e alguns pontos de interrupção não possam resolver.

Quero enfatizar que usar cegamente variáveis ​​globais é uma má ideia. As funções devem ser reutilizáveis ​​e não se importam de onde vêm os dados - a referência a variáveis ​​globais associa a função a uma entrada de dados específica. É por isso que deve ser repassado e por que a injeção de dependência pode ser útil, embora você ainda esteja lidando com um armazenamento de contexto centralizado (via singletons).

Btw ... Algumas pessoas pensam que a injeção de dependência é ruim, incluindo o criador do Linq, mas isso não impede as pessoas de usá-lo, inclusive eu. Em última análise, a experiência será o seu melhor professor. Há momentos para seguir regras e momentos para quebrá-las.

KingOfHypocrites
fonte
4

Como algumas outras respostas aqui fazem a distinção entre estado global mutável e imutável , eu gostaria de opinar que mesmo variáveis ​​/ configurações globais imutáveis costumam ser um aborrecimento .

Considere a pergunta:

... Digamos que preciso de uma configuração global para meu aplicativo, por exemplo, caminhos de pastas do sistema ou credenciais de banco de dados em todo o aplicativo. ...

Certo, para um programa pequeno, isso provavelmente não é um problema, mas assim que você faz isso com componentes em um sistema um pouco maior, escrever testes automatizados de repente se torna mais difícil porque todos os testes (~ executados no mesmo processo) devem funcionar com o mesmo valor de configuração global.

Se todos os dados de configuração forem transmitidos explicitamente, os componentes ficarão muito mais fáceis de testar e você nunca precisará se preocupar em como inicializar um valor de configuração global para vários testes, talvez até em paralelo.

Martin Ba
fonte
3

Bem, por um lado, você pode encontrar exatamente o mesmo problema que pode com os singletons. O que hoje parece uma "coisa global da qual preciso apenas de" irá repentinamente se transformar em algo de que você precisa mais no futuro.

Por exemplo, hoje você cria esse sistema de configuração global porque deseja uma configuração global para todo o sistema. Alguns anos depois, você faz a porta para outro sistema e alguém diz "ei, você sabe, isso poderia funcionar melhor se houvesse uma configuração global geral e uma configuração específica da plataforma". De repente, você tem todo esse trabalho para tornar suas estruturas globais não globais, para que você possa ter várias.

(Este não é um exemplo aleatório ... isso aconteceu com nosso sistema de configuração no projeto em que estou atualmente.)

Considerando que o custo de tornar algo não global geralmente é trivial, é bobagem fazê-lo. Você está apenas criando problemas futuros.

Steven Burnap
fonte
Seu exemplo não é o que significava OP. Você está falando claramente de instanciar configurações abstratas (apenas uma estrutura de dados do gerenciador de configuração, sem nenhuma chave de preferência específica do aplicativo) várias vezes porque deseja dividir todas as chaves em uma instância em execução do seu programa em várias instâncias da sua classe do gerenciador de configuração. (Por exemplo, cada uma dessas instâncias pode lidar com um arquivo de configuração, por exemplo).
Jo
3

O outro problema, curiosamente, é que eles dificultam o dimensionamento de um aplicativo, porque não são suficientemente "globais". O escopo de uma variável global é o processo.

Se você quiser escalar seu aplicativo usando vários processos ou executando em vários servidores, não poderá. Pelo menos até você fatorar todas as globais e substituí-las por algum outro mecanismo.

James Anderson
fonte
3

Por que o estado global é tão mau?

O estado global mutável é mau, porque é muito difícil para o nosso cérebro levar em consideração mais do que alguns parâmetros de cada vez e descobrir como eles se combinam tanto de uma perspectiva de tempo quanto de uma perspectiva de valor para afetar alguma coisa.

Portanto, somos muito ruins em depurar ou testar um objeto cujo comportamento tem mais de algumas razões externas para serem alteradas durante a execução de um programa. Muito menos quando tivermos que raciocinar sobre dezenas desses objetos juntos.

guillaume31
fonte
3

Os idiomas projetados para o design de sistemas seguros e robustos geralmente se livram completamente do estado mutável global . (Indiscutivelmente, isso significa que não há globais, pois objetos imutáveis ​​não são, de certo modo, com estado, pois nunca têm uma transição de estado.)

Joe-E é um exemplo, e David Wagner explica a decisão assim:

A análise de quem tem acesso a um objeto e o princípio do menor privilégio é subvertida quando os recursos são armazenados em variáveis ​​globais e, portanto, são potencialmente legíveis por qualquer parte do programa. Depois que um objeto está disponível globalmente, não é mais possível limitar o escopo da análise: o acesso ao objeto é um privilégio que não pode ser retido de nenhum código no programa. Joe-E evita esses problemas, verificando se o escopo global não contém recursos, apenas dados imutáveis.

Então, uma maneira de pensar sobre isso é

  1. A programação é um problema de raciocínio distribuído. Programadores em grandes projetos precisam dividir o programa em partes que possam ser discutidas pelos indivíduos.
  2. Quanto menor o escopo, mais fácil é argumentar. Isso é verdade tanto para os indivíduos quanto para as ferramentas de análise estática que tentam provar as propriedades de um sistema e os testes que precisam testar as propriedades de um sistema.
  3. Fontes significativas de autoridade que estão disponíveis globalmente tornam difícil entender as propriedades do sistema.

Portanto, o estado globalmente mutável torna mais difícil

  1. projetar sistemas robustos,
  2. mais difícil de provar as propriedades de um sistema e
  3. É mais difícil ter certeza de que seus testes estão sendo testados em um escopo semelhante ao seu ambiente de produção.

O estado mutável global é semelhante ao inferno da DLL . Com o tempo, diferentes partes de um sistema grande exigirão um comportamento sutilmente diferente das partes compartilhadas do estado mutável. Resolver o inferno da DLL e inconsistências compartilhadas de estado mutável requer uma coordenação em larga escala entre equipes diferentes. Esses problemas não ocorreriam se o estado global tivesse um escopo adequado para começar.

Mike Samuel
fonte
2

Globais não são tão ruins. Como afirmado em várias outras respostas, o verdadeiro problema com elas é que, hoje, o caminho de sua pasta global pode, amanhã, ser uma das várias, ou mesmo centenas. Se você estiver escrevendo um programa rápido e único, use globals se for mais fácil. Geralmente, porém, permitir múltiplos, mesmo quando você pensa que precisa apenas de um, é o caminho a percorrer. Não é agradável ter que reestruturar um grande programa complexo que de repente precisa conversar com dois bancos de dados.

Mas eles não prejudicam a confiabilidade. Quaisquer dados referenciados de vários locais do seu programa podem causar problemas se forem alterados inesperadamente. Os enumeradores engasgam quando a coleção que eles estão enumerando é alterada na enumeração intermediária. Eventos da fila de eventos podem fazer truques um com o outro. Threads sempre podem causar estragos. Qualquer coisa que não seja uma variável local ou um campo imutável é um problema. Globais são esse tipo de problema, mas você não vai consertar isso, tornando-os não globais.

Se você estiver prestes a gravar em um arquivo e o caminho da pasta for alterado, a alteração e a gravação precisarão ser sincronizadas. (Como uma das milhares de coisas que podem dar errado, digamos que você pegue o caminho, esse diretório será excluído, o caminho da pasta será alterado para um bom diretório e tente gravar no diretório excluído.) o caminho da pasta é global ou é um dos mil que o programa está usando no momento.

Há um problema real com campos que podem ser acessados ​​por diferentes eventos em uma fila, diferentes níveis de recursão ou diferentes threads. Para simplificar (e simplificar): variáveis ​​locais são boas e campos são ruins. Mas os ex-globais ainda serão campos, portanto essa questão (por mais importante que seja) não se aplica ao status de bem ou mal dos campos globais.

Adição: problemas de multithreading:

(Observe que você pode ter problemas semelhantes com uma fila de eventos ou chamadas recursivas, mas o multithreading é de longe o pior.) Considere o seguinte código:

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

Se filePathfor uma variável local ou algum tipo de constante, seu programa não falhará durante a execução porque filePathé nulo. A verificação sempre funciona. Nenhum outro encadeamento pode alterar seu valor. Caso contrário , não há garantias. Quando comecei a escrever programas multithread em Java, recebia NullPointerExceptions em linhas como essa o tempo todo. Qualqueroutro segmento pode alterar o valor a qualquer momento, e geralmente o fazem. Como várias outras respostas apontam, isso cria sérios problemas para o teste. A declaração acima pode funcionar um bilhão de vezes, passando por testes extensivos e abrangentes e explodir uma vez na produção. Os usuários não poderão reproduzir o problema, e isso não acontecerá novamente até que eles se convencam de que estão vendo as coisas e as esquecem.

Os Globals definitivamente têm esse problema, e se você pode eliminá-los completamente ou substituí-los por constantes ou variáveis ​​locais, isso é uma coisa muito boa. Se você tem código sem estado em execução em um servidor web, provavelmente pode. Normalmente, todos os seus problemas de multithreading podem ser assumidos pelo banco de dados.

Mas se o seu programa precisar se lembrar de uma ação do usuário para a próxima, você terá campos acessíveis por qualquer thread em execução. Mudar um campo global para um campo não global não ajudará na confiabilidade.

RalphChapin
fonte
1
Você pode esclarecer o que quer dizer com isso ?: "Qualquer coisa que não seja uma variável local ou um campo imutável é um problema. Globais são esse tipo de problema, mas você não vai consertar isso, tornando-os não globais".
Andrés F.
1
@AndresF .: Estendi minha resposta. Acho que estou adotando uma abordagem de área de trabalho, na qual a maioria das pessoas nesta página tem mais código do servidor com um banco de dados. "Global" pode significar coisas diferentes nesses casos.
RalphChapin
2

Estado é inevitável em qualquer aplicação real. Você pode agrupá-lo da maneira que quiser, mas uma planilha deve conter dados nas células. Você pode criar objetos de célula apenas com funções como interface, mas isso não restringe quantos lugares podem chamar um método na célula e alterar os dados. Você cria hierarquias de objetos inteiros para tentar ocultar interfaces para que outras partes do código não possamaltere os dados por padrão. Isso não impede que uma referência ao objeto contido seja transmitida arbitrariamente. Isso também não elimina problemas de concorrência por si só. Isso dificulta a proliferação do acesso aos dados, mas na verdade não elimina os problemas percebidos com os globais. Se alguém quiser modificar uma parte do estado, fará com que seja global ou através de uma API complexa (a última só desencorajará, não impedirá).

O verdadeiro motivo para não usar o armazenamento global é evitar colisões de nomes. Se você carregar vários módulos que declaram o mesmo nome global, você terá um comportamento indefinido (muito difícil de depurar porque os testes de unidade serão aprovados) ou um erro de vinculador (estou pensando em C - O vinculador avisa ou falha nisso?).

Se você deseja reutilizar o código, precisa conseguir pegar um módulo de outro lugar e não fazê-lo pisar acidentalmente na sua global porque eles usaram um com o mesmo nome. Ou, se você tiver sorte e receber um erro, não precisará alterar todas as referências em uma seção do código para evitar a colisão.

phkahler
fonte
1

Quando é fácil ver e acessar todo o estado global, os programadores invariavelmente acabam fazendo isso. O que você obtém é tácito e difícil de rastrear dependências (int blahblah significa que o array foo é válido em qualquer que seja). Essencialmente, torna quase impossível manter os invariantes do programa, pois tudo pode ser modificado independentemente. someInt tem um relacionamento entre otherInt, que é difícil de gerenciar e mais difícil de provar se você pode alterar diretamente a qualquer momento.

Dito isto, isso pode ser feito (quando era a única maneira em alguns sistemas), mas essas habilidades estão perdidas. Eles giram principalmente em torno de convenções de codificação e nomeação - o campo mudou por um bom motivo. Seu compilador e vinculador fazem um trabalho melhor ao verificar invariantes em dados protegidos / privados de classes / módulos do que depender de humanos para seguir um plano mestre e ler a fonte.

anon
fonte
1
"mas essas habilidades estão perdidas" ... ainda não completamente. Recentemente, trabalhei em uma casa de software que jura pelo "Clarion", uma ferramenta geradora de código que possui sua própria linguagem básica que carece de recursos, como passar argumentos para sub-rotinas ... Os desenvolvedores sentados não ficaram satisfeitos com sugestões sobre "mudar" ou "modernizar", finalmente me cansou das minhas observações e me retratou como deficiente e incompetente. Eu tive que sair ...
Louis Somers
1

Não vou dizer se as variáveis ​​globais são boas ou más, mas o que vou acrescentar à discussão é dizer que, se você não estiver usando o estado global, provavelmente estará gastando muita memória, especialmente quando você usa classes para armazenar suas dependências em campos.

Para o estado global, não existe esse problema, tudo é global.

Por exemplo: imagine um cenário a seguir: Você tem uma grade de 10x10 composta de classes "Quadro" e "Lado a lado".

Se você quiser fazê-lo da maneira OOP, provavelmente passará o objeto "Board" para cada "Tile". Digamos agora que "Tile" tenha 2 campos do tipo "byte" que armazenam suas coordenadas. A memória total necessária para uma máquina de 32 bits para um bloco seria (1 + 1 + 4 = 6) bytes: 1 para x coord, 1 para y coord e 4 para um ponteiro para o quadro. Isso fornece um total de 600 bytes para a configuração de 10x10 blocos

Agora, no caso em que a placa está no escopo global, um único objeto acessível a partir de cada bloco, você só precisará obter 2 bytes de memória por cada bloco, que são os bytes das coordenadas xey. Isso daria apenas 200 bytes.

Portanto, nesse caso, você recebe 1/3 do uso de memória se usar apenas o estado global.

Isso, além de outras coisas, acho que é uma razão pela qual o escopo global ainda permanece em linguagens de nível (relativamente) de baixo nível, como C ++

luke1985
fonte
1
Isso é altamente dependente do idioma. Nas linguagens em que os programas são executados persistentemente, pode ser verdade que você pode economizar memória usando espaço global e singletons em vez de instanciar muitas classes, mas mesmo esse argumento é instável. É tudo sobre prioridades. Em linguagens como PHP (que é executada uma vez por solicitação e os objetos não persistem), mesmo esse argumento é discutível.
Madara Uchiha 22/03
1
@MadaraUchiha Não, esse argumento não é instável de forma alguma. São fatos objetivos, a menos que alguma VM ou outra linguagem compilada faça alguma otimização de código grave com esse tipo de problema. Caso contrário, ainda é relevante. Mesmo com programas do lado do servidor que costumavam ser "one shot", a memória é reservada durante o tempo de execução. Com servidores de alta carga, esse pode ser um ponto crítico.
luke1985
0

Existem vários fatores a serem considerados no estado global:

  1. Espaço de memória do programador
  2. Globais imutáveis ​​/ escrever uma vez globais.
  3. Globais mutáveis
  4. Dependência de globais.

Quanto mais globais você tiver, maior a chance de introduzir duplicatas e, assim, quebrar as coisas quando as duplicatas ficarem fora de sincronia. Manter todos os globais em sua memória humana falível é necessário e dor.

Imutáveis ​​/ gravação uma vez geralmente são bons, mas cuidado com os erros na sequência de inicialização.

Globais mutáveis ​​são frequentemente confundidos com globos imutáveis…

Uma função que usa globals efetivamente possui parâmetros "ocultos" extras, dificultando a refatoração.

O estado global não é mau, mas tem um custo definido - use-o quando o benefício for superior ao custo.

jmoreno
fonte