Como as globais são diferentes de um banco de dados?

250

Acabei de me deparar com essa pergunta antiga, perguntando o que há de tão ruim no estado global, e a resposta aceita mais votada afirma que você não pode confiar em nenhum código que funcione com variáveis ​​globais, porque algum outro código em outro lugar pode aparecer e modificar sua valor e então você não sabe qual será o comportamento do seu código, porque os dados são diferentes! Mas quando olho para isso, não posso deixar de pensar que essa é uma explicação realmente fraca, porque é que isso é diferente de trabalhar com dados armazenados em um banco de dados?

Quando seu programa está trabalhando com dados de um banco de dados, você não se importa se outro código do seu sistema está alterando-o ou mesmo se um programa completamente diferente está alterando-o. Você não se importa com o que são os dados; esse é o ponto inteiro. Tudo o que importa é que seu código lide corretamente com os dados encontrados. (Obviamente, estou discutindo a questão muitas vezes espinhosa do armazenamento em cache aqui, mas vamos ignorá-lo por enquanto.)

Mas se os dados com os quais você está trabalhando são provenientes de uma fonte externa sobre a qual seu código não tem controle, como um banco de dados (ou entrada do usuário ou soquete de rede ou arquivo etc.) e não há nada errado com isso, então como os dados globais dentro do próprio código - sobre os quais seu programa tem um controle muito maior - de alguma forma são ruins quando obviamente são bem menos ruins do que coisas perfeitamente normais que ninguém vê como um problema?

Mason Wheeler
fonte
117
É bom ver membros veteranos desafiar os dogmas um pouco ...
svidgen
10
Em um aplicativo, você geralmente fornece um meio para acessar o banco de dados, que é passado para funções que desejam acessar o banco de dados. Você não faz isso com variáveis ​​globais, simplesmente sabe que elas estão à mão. Essa é a principal diferença aqui.
245 Andy
45
O estado global é como ter um único banco de dados com uma única tabela com uma única linha com infinitas colunas acessadas simultaneamente por um número arbitrário de aplicativos.
BevynQ
42
Bancos de dados também são maus.
Stig Hemmer
27
É divertido "inverter" o argumento que você faz aqui e seguir na outra direção. Uma estrutura que possui um ponteiro para outra estrutura é logicamente apenas uma chave estrangeira em uma linha de uma tabela que move para outra linha de outra tabela. Como o trabalho com qualquer código, incluindo listas vinculadas a pé, é diferente da manipulação de dados em um banco de dados? Resposta: não é. Pergunta: por que manipulamos estruturas de dados na memória e estruturas de dados no banco de dados usando ferramentas tão diferentes? Resposta: Eu realmente não sei! Parece mais um acidente da história do que um bom design.
Eric Lippert

Respostas:

118

Primeiro, eu diria que a resposta que você vincula para exagerar essa questão em particular e que o principal mal do estado global é que ele introduz acoplamentos de maneiras imprevisíveis que podem dificultar a alteração do comportamento do seu sistema no futuro.

Mas, aprofundando-se nessa questão, existem diferenças entre o estado global em um aplicativo orientado a objetos típico e o estado mantido em um banco de dados. Resumidamente, os mais importantes são:

  • Os sistemas orientados a objetos permitem substituir um objeto por uma classe de objeto diferente, desde que seja um subtipo do tipo original. Isso permite que o comportamento seja alterado, não apenas dados .

  • O estado global em um aplicativo normalmente não fornece as garantias consistentes de um banco de dados - não há transações nas quais você vê um estado consistente, nenhuma atualização atômica etc.

Além disso, podemos ver o estado do banco de dados como um mal necessário; é impossível eliminá-lo de nossos sistemas. O estado global, no entanto, é desnecessário. Nós podemos eliminá-lo completamente. Mesmo que os problemas com um banco de dados sejam igualmente ruins, ainda podemos eliminar alguns dos problemas em potencial e uma solução parcial é melhor do que nenhuma solução.

Jules
fonte
44
Eu acho que o ponto da consistência é realmente o principal motivo: quando variáveis ​​globais são usadas no código, geralmente não há como saber quando elas são realmente inicializadas. As dependências entre os módulos estão profundamente ocultas na sequência de chamadas, e coisas simples, como trocar duas chamadas, podem produzir bugs realmente desagradáveis, porque de repente alguma variável global não é mais inicializada corretamente quando é usada pela primeira vez. Pelo menos é esse o problema que tenho com o código legado com o qual preciso trabalhar e que torna a refatoração um pesadelo.
Cmaster # 24/16
24
@DavidHammen Na verdade, eu trabalhei na simulação de estado mundial para um jogo online, que está claramente na categoria de aplicativo que você está falando, e mesmo lá eu não usaria (e não usei) o estado global para isso. Mesmo que alguns ganhos de eficiência possam ser obtidos usando o estado global, a questão é que o estado global não é escalável . Torna-se difícil de usar quando você passa de uma arquitetura de thread único para multi-thread. Torna-se ineficiente quando você muda para uma arquitetura NUMA. Torna-se impossível quando você muda para uma arquitetura distribuída. O papel que citam datas a partir de ...
Jules
24
1993. Esses problemas eram menos problemáticos na época. Os autores estavam trabalhando em um sistema de processador único, simulando interações de 1.000 objetos. Em um sistema moderno, você provavelmente executaria uma simulação desse tipo em pelo menos um sistema de núcleo duplo, mas provavelmente haveria pelo menos 6 núcleos em um único sistema. Para problemas maiores ainda, você o executaria em um cluster. Para esse tipo de mudança, você deve evitar o estado global porque o estado global não pode ser efetivamente compartilhado.
Jules
19
Eu acho que chamar o estado do banco de dados de "mal necessário" é um pouco exagerado. Quero dizer, desde quando o estado se tornou mau? Estado é o objetivo inteiro de um banco de dados. Estado é informação. Sem estado, tudo que você tem são operadores. De que servem os operadores sem algo para operar? Esse estado tem que ir a algum lugar. No final das contas, a programação funcional é apenas um meio para atingir um fim e, sem o estado de mutação, não faria sentido fazer nada. É como um padeiro chamando o bolo de um mal necessário - não é mau. É o ponto principal da coisa.
J ...
5
@DavidHammen "ainda existe um objeto que sabe pelo menos um pouco sobre todos os objetos do jogo" Não é necessariamente verdade. Uma técnica importante na simulação distribuída moderna é tirar proveito da localidade e fazer aproximações para que objetos distantes não precisem saber de tudo ao longe, apenas quais dados são fornecidos a eles pelos proprietários desses objetos distantes.
JAB
75

Primeiro, quais são os problemas com variáveis ​​globais, com base na resposta aceita à pergunta que você vinculou?

Muito brevemente, torna o estado do programa imprevisível.

Os bancos de dados são, na grande maioria das vezes, compatíveis com ACID. O ACID aborda especificamente os problemas subjacentes que tornariam um armazenamento de dados imprevisível ou não confiável.

Além disso, o estado global prejudica a legibilidade do seu código.

Isso ocorre porque as variáveis ​​globais existem em um escopo distante de seu uso, talvez até em um arquivo diferente. Ao usar um banco de dados, você está usando um conjunto de registros ou objeto ORM local para o código que está lendo (ou deveria ser).

Os drivers de banco de dados geralmente fornecem uma interface consistente e compreensível para acessar dados iguais, independentemente do domínio do problema. Quando você obtém dados de um banco de dados, seu programa possui uma cópia dos dados. As atualizações são atômicas. Contraste para variáveis ​​globais, em que vários threads ou métodos podem estar operando no mesmo pedaço de dados sem atomicidade, a menos que você adicione a sincronização. As atualizações dos dados são imprevisíveis e difíceis de rastrear. As atualizações podem ser intercaladas, causando exemplos de livros didáticos padrão de corrupção de dados multithread (por exemplo, incrementos intercalados).

Os bancos de dados geralmente modelam dados diferentes das variáveis ​​globais, mas deixando de lado por um momento, os bancos de dados são projetados desde o início para serem um armazenamento de dados compatível com ACID que atenua muitas das preocupações com variáveis ​​globais.


fonte
4
+1 O que você está dizendo é que os bancos de dados têm transações, possibilitando a leitura e gravação de várias partes do estado global atomicamente. Bom ponto, que só pode ser contornado usando variáveis ​​globais para cada informação completamente independente.
L0b0 25/05
1
As transações @ l0b0 são o mecanismo que atinge a maioria dos objetivos do ACID, correto. Mas a própria interface do banco de dados torna o código mais claro, trazendo os dados para um escopo mais local. Pense em usar um JDBC RecordSet com um bloco try-with-resources ou uma função ORM que obtém um pedaço de dados usando uma única chamada de função. Compare isso com o gerenciamento de dados distantes do código que você está lendo em um local global.
1
Portanto, seria bom usar variáveis ​​globais se eu copiar o valor para uma variável local (com um mutex) no início da função, modificar a variável local e depois copiar o valor para a variável global no final de a função? (... ele perguntou retoricamente.)
RM
1
@RM Ele mencionou dois pontos. O que você jogou fora pode abordar o primeiro (estado do programa imprevisível), mas não aborda o segundo (a legibilidade do seu código). De fato, isso pode piorar ainda mais a legibilidade do seu programa: P.
Riwalk 26/05
1
@ RM Sua função seria executada de forma consistente, sim. Mas você teria a questão de saber se alguma outra coisa modificou a variável global nesse meio tempo e essa modificação foi mais importante do que o que você está escrevendo para ela. Bancos de dados também podem ter o mesmo problema, é claro.
Graham
45

Eu daria algumas observações:

Sim, um banco de dados é um estado global.

De fato, é um estado super global, como você apontou. É universal! Seu escopo envolve qualquer coisa ou qualquer pessoa que se conecte ao banco de dados. E suspeito que muitas pessoas com anos de experiência possam contar histórias de horror sobre como "coisas estranhas" nos dados levaram a "comportamento inesperado" em um ou mais aplicativos relevantes ...

Uma das possíveis conseqüências do uso de uma variável global é que dois "módulos" distintos usarão essa variável para seus próprios propósitos distintos. E nessa medida, uma tabela de banco de dados não é diferente. Pode ser vítima do mesmo problema.

Hmm ... Aqui está a coisa:

Se um módulo não operar extrinsecamente de alguma forma, não fará nada.

Um módulo útil pode receber dados ou pode encontrá- los. E, pode retornar dados ou pode modificar o estado. Mas, se não interagir de alguma maneira com o mundo externo, poderá não fazer nada.

Agora, nossa preferência é receber dados e retornar dados. A maioria dos módulos é simplesmente mais fácil de escrever se eles puderem ser escritos com total desconsideração pelo que o mundo exterior está fazendo. Mas, finalmente, algo precisa encontrar os dados e modificar esse estado externo global.

Além disso, em aplicativos do mundo real, os dados existem para que possam ser lidos e atualizados por várias operações. Alguns problemas são evitados por bloqueios e transações. Porém, impedir que essas operações entrem em conflito umas com as outras em princípio , no final do dia, envolve simplesmente um pensamento cuidadoso. (E cometer erros ...)

Mas também, geralmente não estamos trabalhando diretamente com o estado global.

A menos que o aplicativo esteja na camada de dados (no SQL ou o que for), os objetos com os quais nossos módulos trabalham são na verdade cópias do estado global compartilhado. Podemos fazer o que quisermos, sem qualquer impacto no estado compartilhado real.

E, nos casos em que precisamos alterar esse estado global, sob a suposição de que os dados que recebemos não foram alterados, geralmente podemos executar o mesmo tipo de bloqueio que faríamos em nossos globais locais.

E, finalmente, que costumamos fazer coisas diferentes com bases de dados do que pode com globals impertinentes.

Um global impertinente e quebrado se parece com isso:

Int32 counter = 0;

public someMethod() {
  for (counter = 0; counter < whatever; counter++) {
    // do other stuff.
  }
}

public otherMethod() {
  for (counter = 100; counter < whatever; counter--) {
    // do other stuff.
  }
}

Simplesmente não usamos bancos de dados para coisas em processo / operacionais como essa. E pode ser a natureza lenta do banco de dados e a relativa conveniência de uma variável simples que nos detém: nossa interação lenta e desajeitada com os bancos de dados simplesmente os torna maus candidatos a muitos dos erros que cometemos historicamente com variáveis.

svidgen
fonte
3
A maneira de garantir (já que não podemos assumir) "que os dados que recebemos não foram alterados" em um banco de dados seria uma transação.
L0b0 25/05
Sim ... isso deveria estar implícito no "mesmo tipo de bloqueio".
Svidgen 25/05
Mas, pode ser difícil pensar cuidadosamente no final do dia.
Sim, os bancos de dados são de fato um estado global - e é por isso que é tão tentador compartilhar dados usando algo como git ou ipfs.
William Payne
21

Não concordo com a afirmação fundamental de que:

Quando seu programa está trabalhando com dados de um banco de dados, você não se importa se outro código do seu sistema está alterando-o ou mesmo se um programa completamente diferente está alterando-o.

Meu pensamento inicial foi "Uau. Apenas Uau". Despende muito tempo e esforço tentando evitar exatamente isso - e calculando quais compensações e compromissos funcionam para cada aplicativo. Ignorar é uma receita para o desastre.

Mas eu também tenho diplomas em nível arquitetural. Uma variável global não é apenas um estado global. É um estado global que pode ser acessado de qualquer lugar de forma transparente. Ao contrário de usar um banco de dados, você precisa ter um identificador para ele - (a menos que você armazene um identificador em uma variável global ...)

Por exemplo, o uso de uma variável global pode se parecer com isso

int looks_ok_but_isnt() {
  return global_int++;
}

int somewhere_else() {
  ...
  int v = looks_ok_but_isnt();
  ...
}

Mas fazer o mesmo com um banco de dados teria que ser mais explícito sobre o que está fazendo

int looks_like_its_using_a_database( MyDB * db ) {
   return db->get_and_increment("v");
}

int somewhere_else( MyBD * db ) { 
   ...
   v = looks_like_its_using_a_database(db);
   ...
}

O banco de dados obviamente está mexendo com um banco de dados. Se você não quiser usar um banco de dados, poderá usar um estado explícito e parecer quase o mesmo que o caso do banco de dados.

int looks_like_it_uses_explicit_state( MyState * state ) {
   return state->v++;
}


int somewhere_else( MyState * state ) { 
   ...
   v = looks_like_it_uses_explicit_state(state);
   ...
}

Então, eu diria que usar um banco de dados é muito mais como usar estado explícito do que usar variáveis ​​globais.

Michael Anderson
fonte
2
Sim, achei interessante quando o OP disse: " Você não se importa com o que são os dados; esse é o ponto inteiro " - se não nos importamos, por que armazená-los? Aqui está um pensamento: vamos parar de usar variáveis e dados em tudo . Isso deve tornar as coisas muito mais simples. "Pare o mundo, eu quero sair!"
1
+1 Diferentes threads ou aplicativos que escrevem e leem do mesmo banco de dados são uma fonte potencial de um grande número de problemas conhecidos, e é por isso que sempre deve haver uma estratégia para lidar com isso, no nível do banco de dados ou do aplicativo, ou ambos. Portanto, definitivamente NÃO é verdade que você (o desenvolvedor do aplicativo) não se importa com quem mais está lendo ou escrevendo no banco de dados.
Andrés F.
1
+1 Em uma nota lateral, esta resposta explica basicamente o que eu mais odeio na injeção de dependência. Ele oculta esses tipos de dependências.
Jpmc26 28/05
@ jpmc26 Posso estar marcando palavras, mas o exemplo acima não é um bom exemplo de como a injeção de dependência (em oposição à pesquisa global) ajuda a tornar as dependências explícitas? Parece-me que você prefere ter problemas com certas APIs, como talvez a mágica de anotação usada pelo JAX-RS e Spring.
Emil Lundberg
2
@EmilLundberg Não, o problema é quando você tem uma hierarquia. A injeção de dependência oculta as dependências das camadas inferiores do código nas camadas superiores, dificultando o controle de quais coisas interagem. Por exemplo, se MakeNewThingdepende MakeNewThingInDbe minha classe de controlador usa MakeNewThing, não está claro no código no meu controlador que estou modificando o banco de dados. Então, e se eu usar outra classe que realmente confirme minha transação atual no banco de dados? O DI torna muito difícil controlar o escopo de um objeto.
Jpmc26
18

O ponto em que a única razão pela qual as variáveis ​​globais não podem ser confiáveis, uma vez que o estado pode ser alterado em outro lugar, não é, por si só, razão suficiente para não usá-las, concordou (é uma boa razão!). É provável que a resposta tenha sido principalmente descrever o uso em que restringir o acesso de uma variável apenas às áreas de código com as quais faria sentido faria mais sentido.

Os bancos de dados são uma questão diferente, no entanto, porque são projetados com o objetivo de serem acessados ​​"globalmente", por assim dizer.

Por exemplo:

  • Os bancos de dados geralmente possuem validação de tipo e estrutura que vai além do idioma que os acessa
  • Os bancos de dados atualizam quase unanimemente as transações baseadas em off, o que evita estados inconsistentes, onde não há garantias de como será o estado final em um objeto global (a menos que esteja oculto atrás de um singleton)
  • A estrutura do banco de dados é pelo menos implicitamente documentada com base na tabela ou na estrutura do objeto, mais do que no aplicativo que a utiliza

Mais importante ainda, os bancos de dados têm um propósito diferente de uma variável global. Os bancos de dados são para armazenar e pesquisar grandes quantidades de dados organizados, onde variáveis ​​globais atendem a nichos específicos (quando justificáveis).

Jeffrey Sweeney
fonte
1
Hã. Você me venceu enquanto eu escrevia uma resposta quase idêntica. :)
Jules
@Jules, sua resposta fornece mais detalhes do lado do aplicativo; mantê-la.
Jeffrey Sweeney
Mas, a menos que você dependa inteiramente dos procedimentos armazenados para acesso a dados, toda essa estrutura ainda falhará em impor que as tabelas sejam usadas conforme o planejado. Ou que as operações são executadas na ordem apropriada. Ou que bloqueios (transações) são criados conforme necessário.
Svidgen
Olá, os pontos 1 e 3 ainda são aplicáveis ​​se você estiver usando uma linguagem de tipo estático como Java?
Jesvin Jose
@aitchnyu Não necessariamente. O argumento a ser levantado é que os bancos de dados são criados com o objetivo de compartilhar dados de maneira confiável, onde as variáveis ​​globais normalmente não o são. Um objeto que implementa uma interface de auto-documentação em uma linguagem estrita serve a um propósito diferente, mesmo que um banco de dados NoSQL de tipo fraco.
Jeffrey Sweeney
10

Mas quando olho para isso, não posso deixar de pensar que essa é uma explicação realmente fraca, porque é que isso é diferente de trabalhar com dados armazenados em um banco de dados?

Ou diferente de trabalhar com um dispositivo interativo, com um arquivo, com memória compartilhada etc. Um programa que faz exatamente a mesma coisa sempre que é executado é um programa muito chato e bastante inútil. Então, sim, é um argumento fraco.

Para mim, a diferença que faz a diferença em relação às variáveis ​​globais é que elas formam linhas de comunicação ocultas e desprotegidas. Ler de um teclado é muito óbvio e protegido. Preciso fazer uma determinada chamada de função e não consigo acessar o driver do teclado. O mesmo se aplica ao acesso a arquivos, memória compartilhada e seu exemplo, bancos de dados. É óbvio para o leitor do código que essa função lê no teclado, que acessa um arquivo, alguma outra função acessa a memória compartilhada (e é melhor haver proteções a esse respeito), e ainda outra função acessa um banco de dados.

Com variáveis ​​globais, por outro lado, não é nada óbvio. A API diz para ligar foo(this_argument, that_argument). Não há nada na sequência de chamada que diga que a variável global g_DangerWillRobinsondeve ser definida com algum valor, mas antes da chamada foo(ou examinada após a chamada foo).


O Google proibiu o uso de argumentos de referência não constantes no C ++, principalmente porque não é óbvio para o leitor o código que foo(x)será alterado, xporque isso fooexige uma referência não constante como argumento. (Compare com o C #, que determina que tanto a definição da função quanto o site de chamada devem qualificar um parâmetro de referência com a refpalavra - chave.) Embora eu não concorde com o padrão do Google sobre isso, entendo seu ponto de vista.

O código é escrito uma vez e modificado algumas vezes, mas se for bom, é lido muitas e muitas vezes. Linhas ocultas de comunicação são um karma muito ruim. A referência não-const do C ++ representa uma pequena linha oculta de comunicação. Uma boa API ou um bom IDE me mostrará que "Oh! Isso é chamado por referência". Variáveis ​​globais são uma enorme linha oculta de comunicação.

David Hammen
fonte
Sua resposta faz mais sentido.
Billal Begueradj
8

Penso que a explicação citada simplifica demais a questão até o ponto em que o raciocínio se torna ridículo. Obviamente, o estado de um banco de dados externo contribui para o estado global. A questão importante é comoseu programa depende do estado global (mutável). Se uma função de biblioteca para dividir seqüências de caracteres no espaço em branco dependeria dos resultados intermediários armazenados em um banco de dados, eu objetaria a esse design pelo menos tanto quanto objetaria uma matriz global de caracteres usada para o mesmo objetivo. Por outro lado, se você decidir que seu aplicativo não precisa de um DBMS completo para armazenar dados de negócios neste momento e uma estrutura global de valores-chave na memória funcionará, isso não é necessariamente um sinal de design inadequado. O que é importante é que - independentemente da solução escolhida para armazenar seus dados - essa opção é isolada em uma porção muito pequena do sistema, para que a maioria dos componentes possa ser independente da solução escolhida para implantação e testada por unidade isoladamente e implantada. solução pode ser alterada posteriormente com pouco esforço.

5gon12eder
fonte
8

Como engenheiro de software que trabalha predominantemente com firmware incorporado, quase sempre estou usando variáveis ​​globais para qualquer coisa entre os módulos. Na verdade, é uma prática recomendada para incorporado. Eles são atribuídos estaticamente, portanto não há risco de explodir a pilha / pilha e não há tempo extra para alocação / limpeza de pilha na entrada / saída da função.

A desvantagem desta situação é que nós não temos que considerar como essas variáveis são usadas, e um monte de que vem para baixo para o mesmo tipo de pensamento que vai para banco de dados disputas. Qualquer leitura / gravação assíncrona de variáveis DEVE ser atômica. Se mais de um local puder gravar uma variável, é necessário pensar em garantir que eles sempre gravem dados válidos, para que a gravação anterior não seja arbitrariamente substituída (ou que a substituição arbitrária seja uma coisa segura a ser feita). Se a mesma variável for lida mais de uma vez, deve-se considerar o que acontece se a variável alterar o valor entre as leituras ou uma cópia da variável deve ser tirada no início para que o processamento seja feito usando um valor consistente, mesmo se esse valor se torna obsoleto durante o processamento.

(No último dia, no meu primeiro dia de contrato trabalhando em um sistema de contramedidas de aeronaves, tão altamente relacionado à segurança, a equipe de software estava analisando um relatório de bug que tentava descobrir há uma semana. Eu tinha tido tempo suficiente para baixar as ferramentas de desenvolvimento e uma cópia do código. Perguntei "essa variável não pode ser atualizada entre leituras e causá-la?", Mas realmente não recebi uma resposta. Ei, o que o afinal, enquanto eles ainda estavam discutindo, eu adicionei um código de proteção para ler a variável atomicamente, fiz uma compilação local e basicamente disse "ei pessoal, tente isso". Maneira de provar que valho minha taxa de contratação . :)

Portanto, as variáveis ​​globais não são uma coisa inequivocamente ruim, mas deixam você aberto a uma ampla gama de questões, se você não pensar nelas com cuidado.

Graham
fonte
7

Dependendo do aspecto que você está julgando, as variáveis ​​globais e o acesso ao banco de dados podem ser diferentes, mas enquanto os julgamos como dependências, eles são os mesmos.

Vamos considerar a definição de uma função pura pela programação funcional que deve depender apenas dos parâmetros que ela recebe como entradas, produzindo uma saída determinística. Ou seja, dado o mesmo conjunto de argumentos duas vezes, ele deve produzir o mesmo resultado.

Quando uma função depende de uma variável global, ela não pode mais ser considerada pura, pois, para o mesmo conjunto ou argumentos, pode produzir resultados diferentes, pois o valor da variável global pode ter sido alterado entre as chamadas.

No entanto, a função ainda pode ser vista como determinística se considerarmos a variável global como parte da interface da função e seus outros argumentos, portanto não é o problema. O problema é apenas que isso fica oculto até o momento em que somos surpreendidos por um comportamento inesperado de funções aparentemente óbvias; em seguida, leia suas implementações para descobrir as dependências ocultas .

Nesta parte, o momento em que uma variável global se torna uma dependência oculta é o que é considerado mau por nós, programadores. Torna o código mais difícil de raciocinar, difícil de prever como ele se comportará, difícil de reutilizar, difícil de testar e, principalmente, aumenta o tempo de depuração e correção quando ocorre um problema.

O mesmo acontece quando ocultamos a dependência no banco de dados. Podemos ter funções ou objetos fazendo chamadas diretas para consultas e comandos do banco de dados, ocultando essas dependências e causando-nos exatamente o mesmo problema que as variáveis ​​globais causam; ou podemos explicitá-los, o que, como se vê, é considerado uma prática recomendada com muitos nomes, como padrão de repositório, armazenamento de dados, gateway etc.

PS: Existem outros aspectos importantes para essa comparação, como se a concorrência está envolvida, mas esse ponto é abordado por outras respostas aqui.

MichelHenrich
fonte
Eu gosto que você entendeu isso do ângulo das dependências.
Cbojar 27/05
6

Ok, vamos começar do ponto histórico.

Estamos em um aplicativo antigo, escrito em sua combinação típica de montagem e C. Não há funções, apenas procedimentos . Quando você deseja passar um argumento ou retornar valor de um procedimento, você usa uma variável global. Escusado será dizer que é muito difícil acompanhar e, em geral, todo procedimento pode fazer o que quiser com cada variável global. Sem surpresa, as pessoas passaram a passar argumentos e retornar valores de uma maneira diferente assim que possível (a menos que fosse crítico para o desempenho não fazê-lo - por exemplo, consulte o código-fonte do Build Engine (Duke 3D)). O ódio das variáveis ​​globais nasceu aqui - você tinha muito pouca ideia de qual parte do estado global cada procedimento leria e mudaria e não era possível aninhar chamadas de procedimentos com segurança.

Isso significa que o ódio à variável global é coisa do passado? Nem tanto.

Primeiro, devo mencionar que vi exatamente a mesma abordagem para transmitir argumentos no projeto em que estou trabalhando agora. Para passar duas instâncias do tipo de referência em C #, em um projeto com cerca de 10 anos de idade. Não há literalmente uma boa razão para fazer isso dessa maneira, e provavelmente nasceu do cultivo de carga ou de um completo mal-entendido de como o C # funciona.

O ponto mais importante é que, ao adicionar variáveis ​​globais, você está expandindo o escopo de cada parte do código que tem acesso a essa variável global. Lembre-se de todas as recomendações como "mantenha seus métodos curtos"? Se você possui 600 variáveis ​​globais (novamente, exemplo do mundo real: /), todos os seus escopos de método são implicitamente expandidos por essas 600 variáveis ​​globais e não há uma maneira simples de acompanhar quem tem acesso a quê.

Se feito errado (da maneira usual :)), variáveis ​​globais podem ter acoplamento entre si. Mas você não tem idéia de como eles são acoplados, e não há mecanismo para garantir que o estado global seja sempre consistente. Mesmo se você introduzir seções críticas para tentar manter as coisas consistentes, verá que ele se compara mal a um banco de dados ACID adequado:

  • Não há como reverter uma atualização parcial, a menos que você preserve os valores antigos antes da "transação". Escusado será dizer que, a essa altura, passar um valor como argumento já é uma vitória :)
  • Todos que acessam o mesmo estado devem aderir ao mesmo processo de sincronização. Mas não há como impor isso - se você esquecer de configurar a seção crítica, está ferrado.
  • Mesmo se você sincronizar corretamente todo o acesso, pode haver chamadas aninhadas que acessam o estado parcialmente modificado. Isso significa que você entra em conflito (se suas seções críticas não são permanentes) ou lida com dados inconsistentes (se eles são permanentes).

É possível resolver esses problemas? Na verdade não. Você precisa de um encapsulamento para lidar com isso, ou uma disciplina realmente estrita. É difícil fazer as coisas direito, e isso geralmente não é uma receita muito boa para o sucesso no desenvolvimento de software :)

Escopos menores tendem a facilitar o raciocínio do código. As variáveis ​​globais fazem com que até mesmo as partes mais simples do código incluam enormes faixas de escopo.

Obviamente, isso não significa que o escopo global seja ruim. Apenas não deve ser a primeira solução que você procura - é um exemplo típico de "simples de implementar, difícil de manter".

Luaan
fonte
Parece muito com o mundo físico: muito difícil reverter as coisas.
Essa é uma boa resposta, mas pode conter uma declaração de tese (seção TL; DR) desde o início.
Jpmc26
6

Uma variável global é uma ferramenta, pode ser usada para o bem e para o mal.

Um banco de dados é uma ferramenta, pode ser usado para o bem e para o mal.

Como observa o pôster original, a diferença não é tão grande assim.

Os estudantes inexperientes costumam pensar que erros são algo que acontece com outras pessoas. Os professores usam "As variáveis ​​globais são más" como uma razão simplificada para penalizar o mau design. Os alunos geralmente não entendem que, apenas porque seu programa de 100 linhas é livre de bugs, não significa que os mesmos métodos possam ser usados ​​para programas de 10000 linhas.

Quando você trabalha com bancos de dados, não pode simplesmente banir o estado global, pois é disso que trata o programa. Em vez disso, você obtém diretrizes mais detalhadas, como ACID e Formulários normais, etc.

Se as pessoas usassem a abordagem ACID para variáveis ​​globais, elas não seriam tão ruins.

Por outro lado, se você criar mal bancos de dados, eles podem ser pesadelos.

Stig Hemmer
fonte
3
Reivindicação típica de estudante no stackoverflow: Me ajude! Meu código é perfeito, mas não está funcionando direito!
David Hammen
"Abordagem de ACID para variáveis ​​globais" - veja refs em Clojure.
Charles Duffy
@DavidHammen e você acha que os profissionais têm um cérebro diferente dos estudantes?
Billal Begueradj
@BillalBEGUERADJ - Essa é a diferença entre profissionais e estudantes. Sabemos que, apesar de anos de experiência e apesar dos melhores esforços de revisão de código, teste etc., nosso código não é perfeito.
David Hammen
5

Para mim, o principal mal é que os Globals não têm proteção contra problemas de concorrência. Você pode adicionar mecanismos para lidar com esses problemas com o Globals, mas descobrirá que quanto mais problemas de simultaneidade resolver, mais o Globals começará a imitar um banco de dados. O mal secundário não é um contrato de uso.

G DeMasters
fonte
3
Por exemplo, errnoem C.
David Hammen
1
Isso explica exatamente por que globals e bancos de dados não são os mesmos. Pode haver outras diferenças, mas sua postagem específica destrói completamente o conceito. Se você desse um exemplo rápido de código, tenho certeza que receberia muitos votos. por exemplo, MyFunc () {x = globalVar * 5; // .... Algum outro processamento; y = globalVar * 34; // Ups, algum outro encadeamento poderia ter mudado globalVar durante algum outro processamento e x e y estão usando valores diferentes para globalVar em seus cálculos, o que quase certamente não daria resultados desejáveis.
Dunk
5

Algumas das outras respostas tentam explicar por que usar um banco de dados é bom. Eles estão errados! Um banco de dados é um estado global e, como tal, é tão ruim quanto um singleton ou uma variável global. É errado usar um banco de dados quando você pode facilmente usar um mapa ou uma matriz local!

Variáveis ​​globais permitem acesso global, o que acarreta risco de abuso. Variáveis ​​globais também têm vantagens. Dizem que as variáveis ​​globais são algo que você deve evitar, e não algo que você nunca deve usar. Se você pode evitá-los facilmente, deve evitá-los. Mas se os benefícios superam os inconvenientes, é claro que você deve usá-los! *

A mesma coisa ** se aplica aos bancos de dados, que são estados globais - assim como as variáveis ​​globais. Se você pode se contentar sem acessar um banco de dados, e a lógica resultante faz tudo o que você precisa e é igualmente complexo, o uso de um banco de dados aumenta o risco do seu projeto, sem nenhum benefício correspondente.

Na vida real, muitos aplicativos exigem estado global por design, às vezes até estado global persistente - é por isso que temos arquivos, bancos de dados etc.


* A exceção aqui são os alunos. Faz sentido proibir os alunos de usar variáveis ​​globais para que eles tenham que aprender quais são as alternativas.

** Algumas respostas afirmam incorretamente que os bancos de dados estão de alguma forma melhor protegidos do que outras formas de estado global (a pergunta é explicitamente sobre estado global , não apenas variáveis ​​globais). Isso é besteira. A proteção primária oferecida no cenário do banco de dados é por convenção, que é exatamente a mesma para qualquer outro estado global. A maioria dos idiomas também permite muita proteção adicional para o estado global, na forma de constclasses que simplesmente não permitem alterar seu estado após a configuração no construtor, ou getters e setters que podem levar em consideração as informações do encadeamento ou o estado do programa.

Peter
fonte
2

Em certo sentido, a distinção entre variáveis ​​globais e um banco de dados é semelhante à distinção entre membros públicos e privados de um objeto (supondo que alguém ainda use campos públicos). Se você pensa em todo o programa como um objeto, as globais são as variáveis ​​privadas e o banco de dados são os campos públicos.

A principal diferença aqui é uma das responsabilidades assumidas.

Quando você escreve um objeto, supõe-se que qualquer pessoa que mantenha os métodos de membro garantirá que os campos privados permaneçam bem comportados. Mas você já desiste de quaisquer suposições sobre o estado dos campos públicos e os trata com cuidado extra.

A mesma suposição se aplica em um nível mais amplo ao banco de dados global v / s. Além disso, a linguagem / ecossistema de programação garante restrições de acesso ao público v / s público da mesma forma que as aplica no banco de dados global (memória não compartilhada) v / s globais.

Quando o multithreading entra em cena, o conceito de banco de dados público / virtual global v / s privado v / s é meramente distinções ao longo de um espectro.

static int global; // within process memory space
static int dbvar; // mirrors/caches data outside process memory space

class Cls {
    public: static int class_public; // essentially the same as global
    private: static int class_private; // but public to all methods in class

    private: static void method() {
        static int method_private; // but public to all scopes in method
        // ...
        {
            static int scope1_private; // mutex guarded
            int the_only_truly_private_data;
        }
        // ...
        {
            static int scope2_private; // mutex guarded
        }
    }
}
Benito Ciaro
fonte
1

Um banco de dados pode ser um estado global, mas não precisa ser o tempo todo. Eu discordo da suposição de que você não tem controle. Uma maneira de gerenciar isso é o bloqueio e a segurança. Isso pode ser feito no registro, tabela ou banco de dados inteiro. Outra abordagem é ter algum tipo de campo de versão que impeça a alteração de um registro se os dados estiverem obsoletos.

Como uma variável global, os valores em um banco de dados podem ser alterados depois que eles são desbloqueados, mas existem várias maneiras de controlar o acesso (não dê a todos os desenvolvedores a senha da conta com permissão para alterar dados.). Se você tem uma variável que tem acesso limitado, não é muito global.

JeffO
fonte
0

Existem várias diferenças:

  • Um valor do banco de dados pode ser modificado em tempo real. O valor de um global definido no código, por outro lado, não pode ser alterado, a menos que você reimplemente seu aplicativo e modifique seu código. De fato, isso é intencional. Um banco de dados é para valores que podem mudar com o tempo, mas as variáveis ​​globais devem ser apenas para itens que nunca mudarão e quando não contiverem dados reais.

  • Um valor do banco de dados (linha, coluna) possui um contexto e um mapeamento relacional no banco de dados. Essa relação pode ser facilmente extraída e analisada usando ferramentas como o Jailer (por exemplo). Uma variável global, por outro lado, é um pouco diferente. Você pode encontrar todos os usos, mas seria impossível você me dizer todas as maneiras pelas quais a variável interage com o resto do seu mundo.

  • Variáveis ​​globais são mais rápidas . Obter algo de um banco de dados requer que uma conexão com o banco de dados seja feita, uma seleção para mim ser executada e a conexão com o banco de dados deve ser fechada. Quaisquer conversões de tipos que você possa precisar vêm além disso. Compare isso com um global que está sendo acessado no seu código.

Estes são os únicos que consigo pensar agora, mas tenho certeza de que existem mais. Simplificando, são duas coisas diferentes e devem ser usadas para objetivos diferentes .

Arnab Datta
fonte
0

É claro que os globais nem sempre são inadequados. Eles existem porque têm um uso legítimo. O principal problema com os globais, e a principal fonte de advertência para evitá-los, é que o código que usa um global é anexado a esse e apenas um global.

Por exemplo, considere um servidor HTTP armazenando o nome do servidor.

Se você armazenar o nome do servidor em um global, o processo não poderá executar simultaneamente a lógica para dois nomes diferentes. Talvez o design original nunca tenha pensado em executar mais de uma instância do servidor por vez, mas se você decidir mais tarde fazer isso, simplesmente não poderá se o nome do servidor for global.

Por outro lado, se o nome do servidor estiver em um banco de dados, não há problema. Você pode simplesmente criar uma instância desse banco de dados para cada instância do servidor HTTP. Como cada instância do servidor possui sua própria instância do banco de dados, ele pode ter seu próprio nome de servidor.

Portanto, a principal objeção às globais, pode haver apenas um valor para todo o código que acessa essa global, não se aplica às entradas do banco de dados. O mesmo código pode acessar facilmente instâncias de banco de dados distintas que possuem valores diferentes para uma entrada específica.

David Schwartz
fonte
0

Eu acho que essa é uma pergunta interessante, mas é um pouco difícil de responder, porque há duas questões principais que estão sendo conflitadas sob o termo 'estado global'. O primeiro é o conceito de 'acoplamento global'. A prova disso é que a alternativa dada ao estado global é a injeção de dependência. O fato é que o DI não elimina necessariamente o estado global. Ou seja, é absolutamente possível e comum injetar dependências no estado global. O que o DI faz é remover o acoplamento que acompanha as variáveis ​​globais e o padrão Singleton comumente usado. Além de um design um pouco menos óbvio, há muito pouca desvantagem em eliminar esse tipo de acoplamento e os benefícios de eliminar o acoplamento aumentam exponencialmente com o número de dependências desses globais.

O outro aspecto disso é o estado compartilhado. Não tenho certeza se existe uma distinção clara entre estado compartilhado globalmente e estado compartilhado em geral, mas os custos e benefícios são muito mais variados. Simplificando, existem inúmeros sistemas de software que exigem que o estado compartilhado seja útil. O Bitcoin, por exemplo, é uma maneira muito inteligente de compartilhar o estado globalmente (literalmente) de uma maneira descentralizada. Compartilhar o estado mutável corretamente sem criar grandes gargalos é difícil, mas útil. Portanto, se você realmente não precisar fazer isso, poderá simplificar seu aplicativo minimizando o estado mutável compartilhado.

Portanto, a questão de como os bancos de dados diferem dos globais também é bifurcada nesses dois aspectos. Eles introduzem acoplamento? Sim, eles podem, mas isso depende muito de como o aplicativo é projetado e como o banco de dados é projetado. Existem muitos fatores para ter uma resposta única para saber se os bancos de dados apresentam acoplamento global sem detalhes do design. Quanto à introdução de compartilhamento de estado, esse é o principal ponto de um banco de dados. A questão é se eles fazem isso bem. Mais uma vez, acho que é muito complicado responder sem muitas outras informações, como as alternativas e muitas outras compensações.

JimmyJames
fonte
0

Eu pensaria um pouco diferente: o "comportamento da variável global" é um preço pago pelos administradores de banco de dados (DBAs) porque é um mal necessário fazer o trabalho deles.

O problema com variáveis ​​globais, como várias outras apontaram, não é arbitrário. O problema é que o uso deles torna o comportamento do seu programa cada vez menos previsível, porque fica mais difícil determinar quem está usando a variável e de que maneira. Esse é um grande problema para o software moderno, porque normalmente é solicitado que o software moderno faça muitas coisas flexíveis. Pode fazer bilhões ou mesmo trilhões de manipulações complexas de estado durante o decorrer de uma corrida. A capacidade de provar declarações verdadeiras sobre o que esse software fará nesses bilhões ou trilhões de operações é extremamente valiosa.

No caso de software moderno, todos os nossos idiomas fornecem ferramentas para ajudá-lo, como o encapsulamento. A escolha de não usá-lo é desnecessária, o que leva à mentalidade "global é má". Em muitas regiões do campo de desenvolvimento de software, as únicas pessoas que os utilizam são pessoas que não sabem como codificar melhor. Isso significa que eles não apenas têm problemas diretamente, mas indiretamente sugerem que o desenvolvedor não sabia o que estava fazendo. Em outras regiões, você verá que os globais são totalmente normais (o software incorporado, em particular, adora os globais, em parte porque eles funcionam bem com ISRs). No entanto, entre os muitos desenvolvedores de software existentes, eles são a voz minoritária; portanto, a única voz que você ouve é "os globais são maus".

O desenvolvimento de banco de dados é uma dessas situações de voz minoritária. As ferramentas necessárias para o trabalho do DBA são muito poderosas e sua teoria não está enraizada no encapsulamento. Para obter cada instante de desempenho de seus bancos de dados, eles precisam de acesso total e irrestrito a tudo, semelhante aos globais. Use um dos seus monstruosos bancos de dados de 100 milhões de linhas (ou mais!) E você entenderá por que eles não deixam seu mecanismo de banco de dados dar nenhum soco.

Eles pagam um preço por isso, um preço caro. Os DBAs são forçados a ser quase patológicos com sua atenção aos detalhes, porque suas ferramentas não os protegem. O melhor que eles têm em termos de proteção é o ACID ou talvez as chaves estrangeiras. Aqueles que não são patológicos encontram-se com uma bagunça total de tabelas que é completamente inutilizável, ou mesmo corrompida.

Não é incomum ter pacotes de software de 100 mil linhas. Em teoria, qualquer linha do software pode afetar qualquer global a qualquer momento. Nos DBAs, você nunca encontra 100 mil consultas diferentes que podem modificar o banco de dados. Isso não seria razoável manter com a atenção aos detalhes necessários para protegê-lo de si mesmo. Se um DBA tiver algo grande assim, ele intencionalmente encapsulará seu banco de dados usando acessadores, evitando os problemas "globais" e, em seguida, fará o máximo de trabalho possível com esse mecanismo "mais seguro". Assim, quando o push chega ao ponto, mesmo as pessoas do banco de dados evitam globais. Eles simplesmente vêm com muito perigo, e existem alternativas igualmente fortes, mas não tão perigosas.

Você prefere andar em cacos de vidro ou em calçadas bem varridas, se todas as outras coisas forem iguais? Sim, você pode andar em vidro quebrado. Sim, algumas pessoas ainda ganham a vida fazendo isso. Mas, ainda assim, deixe-os varrer a calçada e seguir em frente!

Cort Ammon
fonte
0

Eu acho que a premissa é falsa. Não há razão para que um banco de dados precise ser "estado global" em vez de um objeto de contexto (muito grande). Se você está vinculando o banco de dados específico que seu código está usando por meio de variáveis ​​globais ou de parâmetros fixos de conexão com o banco de dados global, não é diferente nem menos mau do que qualquer outro estado global. Por outro lado, se você transmitir adequadamente um objeto de contexto para a conexão com o banco de dados, é apenas um estado contextual grande (e amplamente usado), não um estado global.

Medir a diferença é fácil: você poderia executar duas instâncias da lógica do seu programa, cada uma usando seu próprio banco de dados, em um único programa / processo sem fazer alterações invasivas no código? Nesse caso, seu banco de dados não é realmente "estado global".

R ..
fonte
-2

Globais não são maus; eles são simplesmente uma ferramenta. O mau uso de globais é problemático, assim como o mau uso de qualquer outro recurso de programação.

Minha recomendação geral é que os globais sejam usados ​​apenas em situações bem compreendidas e pensadas, nas quais outras soluções são menos ideais. Mais importante, você deseja garantir que esteja bem documentado onde esse valor global pode ser modificado e, se estiver executando multithread, que esteja garantindo que o global e quaisquer globais co-dependentes sejam acessados ​​de maneira transacional.

Byron Jones
fonte
Alguns dos que recusam se importam em explicar seus votos negativos? Parece rude fazer voto negativo sem uma explicação.
Byron Jones
-2

Somente leitura e assuma que seus dados não estão atualizados quando você os imprime. A fila grava ou manipula conflitos de outra maneira. Bem-vindo ao diabo do inferno, você está usando db global.

Vince
fonte