Condições globais. Essa frase causa medo e dor no coração de todo desenvolvedor que teve o infortúnio de enfrentar esse fenômeno. Você já encontrou um comportamento inesperado do aplicativo, sem entender seus motivos, como um cavaleiro infeliz tentando matar Hydra com muitas cabeças? Você acaba em um ciclo interminável de tentativa e erro, 90% do tempo se perguntando o que está acontecendo?
Tudo isso pode ser conseqüências irritantes de globais: variáveis ocultas que mudam de estado em lugares desconhecidos, por razões que você ainda não descobriu.
Você gosta de passear no escuro enquanto tenta alterar o aplicativo? Claro que não gosto disso. Felizmente, tenho velas para você:
- Primeiro, descreverei o que chamamos mais frequentemente de estados globais. Esse termo nem sempre é usado com precisão, portanto, requer esclarecimentos.
- A seguir, descobriremos por que os globais são prejudiciais à nossa base de códigos.
- Depois, explicarei como aparar o escopo dos globais para transformá-los em variáveis locais.
- Por fim, falarei sobre encapsulamento e por que a cruzada contra variáveis globais é apenas parte do grande problema.
Espero que este artigo explique tudo o que você precisa saber sobre estados globais. Se você acha que senti muita falta e me odeia por isso e não quer mais me ver, escreva sobre isso nos comentários. Será agradável para mim, meus leitores e todos que apareceram de repente nesta página.
Você está pronto, querido leitor, para pular a cavalo e conhecer seu inimigo? Vá encontrar esses globais e fazê-los provar o aço de nossas espadas!
O que é uma condição?

Vamos começar com o básico, para que os desenvolvedores se entendam.
Um estado é uma definição de um sistema ou entidade. Os estados são encontrados na vida real:
- Quando o computador está desligado, seu estado está desligado.
- Quando uma xícara de chá está quente, sua condição está quente.
No desenvolvimento de software, algumas construções (como variáveis) podem ter estados. Digamos que a string "olá" ou o número 11 não sejam considerados estados, são valores. Tornam-se estado quando são anexados a uma variável e colocados na memória.
<?php echo "hello";
Dois tipos de estados podem ser distinguidos:
Estados variáveis: após a inicialização, eles podem mudar durante a execução do seu aplicativo a qualquer momento.
<?php $lala = "hello";
Estados imutáveis: não podem ser alterados durante a execução. Você atribui o primeiro estado à sua variável e seu valor não é alterado posteriormente. "Constantes" na vida cotidiana são chamadas de exemplos de estados imutáveis:
<?php define("GREETING", "hello");
Agora vamos ouvir uma conversa hipotética entre Denis e Vasily, seus colegas desenvolvedores:
Dan! Você criou variáveis globais em todos os lugares! Eles não podem ser alterados sem tudo quebrar! Eu vou te matar!
- Nifiga, Vasek! Minhas fortunas globais são incríveis! Coloquei minha alma neles, são obras-primas! Eu adoro meus globals!
Na maioria das vezes, os desenvolvedores chamam estados globais, variáveis globais ou globais do que devem chamar estados globais mutáveis. Ou seja, estados que podem ser modificados no maior dos
escopos disponíveis para você: em todo o aplicativo.
Quando uma variável não tem todo o aplicativo como escopo, estamos falando de variáveis locais ou localidades. Eles existem em algumas áreas de visibilidade, menos que a área de todo o aplicativo.
<?php namespace App\Ecommerce; $global = "I'm a mutable global variable!";
Você pode pensar: como é conveniente ter variáveis que você possa acessar de qualquer lugar e alterá-las! Eu posso transferir estados de uma parte do aplicativo para outra! Não há necessidade de passar por funções e escrever tanto código! Salve, estado mutável global!
Se você realmente pensou assim, eu recomendo continuar lendo.
Estados globais piores que peste e cólera?
O maior diagrama de links
Fato: será mais fácil criar um diagrama de conexão de aplicativo preciso se ele contiver apenas localidades com escopos pequenos e definidos, sem globais.
Porque
Digamos que você tenha um aplicativo grande com variáveis globais. Toda vez que você precisar alterar algo, você deve:
- Lembre-se de que esses estados globais mutáveis existem.
- Estime se eles afetarão o escopo que você vai alterar.
Normalmente, você não precisa pensar em variáveis locais que estão em outros escopos, mas faça o que fizer, sempre mantenha em seu cérebro cansado um lugar para estados mutáveis globais, pois eles podem afetar todos os escopos.
Além disso, seus estados mutáveis globais podem mudar em qualquer lugar do aplicativo. Geralmente, é preciso se perguntar qual é o estado atual deles. Isso significa que você é forçado a pesquisar em todo o aplicativo, tentando calcular os valores dos globais no escopo modificável.
Isso não é tudo. Se você precisar alterar o estado dos globais, não imaginará qual o escopo afetado. Isso levará a um comportamento inesperado de outra classe, método ou função? Sucesso na busca.
Em resumo, você combina todas as classes, métodos e funções que usam o mesmo estado global. Não se esqueça: as
dependências aumentam bastante a complexidade . Isso te assusta? Deveria ser. Pequenas áreas específicas de visibilidade são muito úteis: você não precisa se lembrar de toda a aplicação; basta lembrar apenas das áreas com as quais trabalha.
As pessoas não rastreiam imediatamente uma grande quantidade de informações. Quando tentamos fazer isso, esgotamos rapidamente o suprimento de capacidades cognitivas, torna-se difícil nos concentrarmos e começamos a criar bugs e coisas estúpidas. É por isso que é tão desagradável atuar no escopo global do seu aplicativo.
Colisões de nomes globais
Existem dificuldades ao usar bibliotecas de terceiros. Imagine que você deseja usar a biblioteca super bacana que colore aleatoriamente cada personagem com um efeito de cintilação. O sonho de todo desenvolvedor! Se essa biblioteca também usar globais que tenham os mesmos nomes que os seus, você poderá desfrutar de colisões de nomes. Seu aplicativo falhará e você descobrirá os motivos, provavelmente por um longo tempo:
- Primeiro, você precisará descobrir que sua biblioteca usa variáveis globais.
- Em segundo lugar, você precisa calcular qual variável foi usada durante a execução - sua ou bibliotecas? Não é tão simples, os nomes são os mesmos!
- Em terceiro lugar, como você não pode alterar a biblioteca, renomeie suas variáveis mutáveis globais. Se usado em todo o aplicativo, você chorará.
Em cada estágio, você arranca o cabelo da raiva e do desespero. Em breve você não precisará mais de um pente. É improvável que esse cenário o seduza. Talvez alguém se lembre de que as bibliotecas JavaScript Mootools, Underscore e jQuery sempre se chocavam entre si se não fossem colocadas em escopos menores. Ah, e o famoso objeto global
$
no jQuery!
Os testes se transformarão em um pesadelo
Se ainda não o convenci, vejamos a situação do ponto de vista do teste de unidade: como você escreve testes na presença de variáveis globais? Como os testes podem mudar globais, você não sabe em que teste estava o estado. Você precisa isolar os testes um do outro, e estados globais os unem.
Você já teve isso para que, isoladamente, os testes funcionem bem e, quando você executa o pacote inteiro, eles falham? Não? E eu tive. Toda vez que me lembro disso, sofro.
Problemas de simultaneidade
Estados globais variáveis podem causar muitos problemas se você precisar de simultaneidade. Quando você altera o estado dos globais em vários segmentos de execução, fique de cabeça para baixo em um
estado poderoso
da corrida .
Se você é um desenvolvedor PHP, isso não o incomoda, a menos que você use bibliotecas que permitem criar paralelismo. No entanto, quando você
aprende um novo idioma no qual o paralelismo é fácil de implementar, espero que você se lembre da minha prosa.
Evitando estados globais mutáveis

Embora os estados mutáveis globais possam causar muitos problemas, às vezes são difíceis de evitar.
Adote a API REST: os terminais recebem algum tipo de solicitação HTTP com parâmetros e enviam respostas. Esses parâmetros HTTP enviados ao servidor podem ser exigidos em vários níveis do seu aplicativo. É muito tentador tornar esses parâmetros globais ao receber uma solicitação HTTP, modificando-os antes de enviar uma resposta. Adicione paralelismo em cima de cada solicitação e a receita do desastre está pronta.
Estados mutáveis globais também podem ser diretamente suportados em implementações de idiomas. Por exemplo, no PHP existem
superglobais .
Se os globais vêm de algum lugar, como lidar com eles? Como refatorar a aplicação de Denis, seu colega desenvolvedor que criou globals sempre que possível, porque ele não leu nada sobre desenvolvimento nos últimos 20 anos?
Argumentos de Função
A maneira mais fácil de evitar globais é passar variáveis usando argumentos de função. Veja um exemplo simples:
<?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 você pode ver, a
$productData
do controlador, através de uma solicitação HTTP, passa por diferentes níveis:
- O controlador recebeu uma solicitação HTTP.
- Parâmetros passados para o modelo.
- Parâmetros passados para o DAO .
- Os parâmetros são salvos no banco de dados do aplicativo.
Poderíamos tornar esse conjunto de parâmetros global quando o recuperarmos da solicitação HTTP. Parece tão mais simples: não há necessidade de transferir dados para 4 funções diferentes. No entanto, passando parâmetros como argumentos para funções:
- Obviamente, isso mostrará que essas funções usam a
$productData
.
- Obviamente, mostrará quais funções usam quais parâmetros. Pode-se observar que, para
ProductDao::find
da $productData
, apenas $id
necessário e não tudo.
Os Globals tornam o código menos compreensível e associam métodos entre si, o que é um preço muito alto pela quase completa ausência de vantagens.
Você já ouviu Denis protestar: “E se uma função tiver três ou mais argumentos? Se você precisar adicionar ainda mais, a complexidade da função aumentará! E as variáveis, objetos e outras construções necessárias em todos os lugares? Você os passará para cada função no aplicativo? ”
As perguntas são justas, caro leitor. Como um
bom desenvolvedor , você deve explicar a Denis, usando suas habilidades de comunicação, isto é o que:
“Denis, se suas funções tiverem muitos argumentos, elas poderão ser um problema. Eles provavelmente fazem demais, são responsáveis por muitas coisas. Você não pensou em dividi-los em funções menores? " .
Sentindo-se como um orador na Acrópole de Atenas, você continua:
“Se você precisar de variáveis em muitas áreas de visibilidade, isso é um problema, e em breve falaremos sobre isso. Mas se você realmente precisa deles, o que há de errado em passar por argumentos de função? Sim, você terá que digitá-las no teclado, mas somos desenvolvedores, é nosso trabalho escrever código. ”Pode parecer mais complicado quando você tem mais argumentos (talvez seja assim), mas repito, as vantagens superam as desvantagens: é melhor que o código seja o mais claro possível e não use estados mutáveis globais ocultos.
Objetos de contexto
Objetos contextuais são aqueles que contêm dados definidos por algum contexto. Normalmente, esses dados são armazenados como uma construção de par de chaves, como uma matriz associativa em PHP. Esse objeto não possui comportamento, apenas dados, semelhantes
a um objeto de valor .
Um objeto de contexto pode substituir qualquer estado mutável global. Retorne ao exemplo de código anterior. Em vez de passar dados da solicitação pelos níveis, podemos usar um objeto que encapsula esses dados.
O contexto será a própria consulta: outra consulta - outro contexto - outro conjunto de dados. Em seguida, o objeto de contexto será passado para qualquer método que precise desses dados.
Você diz: "É incrível e tudo isso, mas o que isso dá?"
- Os dados são encapsulados em um objeto. Na maioria das vezes, sua tarefa será tornar os dados imutáveis, ou seja, para que você não possa alterar o estado - o valor dos dados no objeto após a inicialização.
- Obviamente, o contexto precisa dos dados do objeto de contexto, pois é transferido para todas as funções (ou métodos) que precisam desses dados.
- Isso resolve o problema de simultaneidade: se cada solicitação tiver seu próprio objeto de contexto, você poderá gravá-los ou lê-los com segurança em seus próprios encadeamentos de execução.
Mas tudo no desenvolvimento tem um preço. Objetos de contexto podem ser prejudiciais:
- Observando os argumentos da função, você não saberá quais dados estão no objeto de contexto.
- Você pode colocar qualquer coisa em um objeto de contexto. Cuidado para não colocar muito, por exemplo, toda a sessão do usuário ou até a maioria dos dados do aplicativo. E então isso pode acontecer:
$context->getSession()->getUser()->getProfil()->getUsername()
. Quebre a lei de Deméter e sua maldição será uma complexidade insana.
- Quanto maior o objeto de contexto, mais difícil é descobrir quais dados e em qual escopo ele usa.
Em geral, eu evitaria usar objetos contextuais o máximo possível. Eles podem causar muitas dúvidas. A imutabilidade dos dados é uma grande vantagem, mas não devemos esquecer as deficiências. Se você estiver usando um objeto de contexto, verifique se ele é pequeno o suficiente e passe-o para um escopo pequeno e cuidadosamente definido.
Se antes de executar um programa você não tem idéia de quantos estados serão passados para suas funções (por exemplo, parâmetros de uma solicitação HTTP), os objetos de contexto podem ser úteis. Portanto, alguns deles os utilizam, lembre-se, por exemplo, do objeto
Request
no Symfony.
Injeção de Dependência
Outra boa alternativa para os estados mutáveis globais seria incorporar diretamente os dados necessários no objeto quando você os criar. Esta é a definição de injeção de dependência: um conjunto de técnicas para incorporar objetos em seus componentes (classes).
Por que exatamente injeção de dependência?
O objetivo é limitar o uso de suas variáveis, objetos ou outras construções e colocá-las em um escopo limitado. Se você possui dependências incorporadas e, portanto, só pode atuar no escopo de um objeto, será mais fácil descobrir em que contexto elas são usadas e por quê. Sem angústia e tormento!
A injeção de dependência divide o ciclo de vida do aplicativo em duas fases importantes:
- Criando objetos de aplicativo e implementando suas dependências.
- Use objetos para atingir seus objetivos.
Essa abordagem torna o código mais claro: você não precisa instanciar tudo em locais aleatórios ou, pior ainda, usar objetos globais em qualquer lugar.
Muitas estruturas usam injeção de dependência, às vezes em esquemas bastante complexos, com arquivos de configuração e um DIC (Dependency Injection Container). Mas não é de todo necessário para complicar as coisas. Você pode simplesmente criar dependências em um nível e implementá-las em um nível inferior. Por exemplo, no mundo Go, não conheço ninguém que usaria o DIC. Você simplesmente cria as dependências no arquivo principal com o código (main.go) e as transfere para o próximo nível. Você também pode instanciar tudo em pacotes diferentes para indicar claramente que a "fase de injeção de dependência" deve ser executada apenas nesse nível específico. No Go, o escopo dos pacotes pode facilitar as coisas do que no PHP, no qual as DICs são amplamente usadas em todas as estruturas que conheço, incluindo Symfony e Laravel.
Implementação via construtor ou setter
Existem duas maneiras de injetar dependências: através do construtor ou dos setters. Eu aconselho, se possível, a seguir o primeiro método:
- Se você precisa saber o que são dependências de classe, tudo o que você precisa fazer é encontrar um construtor. Não há necessidade de procurar métodos espalhados por toda a classe.
- Definir dependências durante a instalação dará a você confiança na segurança do uso do objeto.
Vamos falar um pouco sobre o último ponto: isso é chamado de "imposição de invariante". Ao criar uma instância de um objeto e implementar suas dependências, você sabe que não importa o que seu objeto precise, ele está configurado corretamente. E se você usa setters, como você sabe que suas dependências já estão definidas no momento do uso do objeto? Você pode ir para a pilha e tentar descobrir se os setters foram chamados, mas tenho certeza que você não quer fazer isso.
Violação de encapsulamento
Afinal, a única diferença entre estados locais e globais é seu escopo. Eles são limitados para estados locais e, para global, todo o aplicativo está disponível. No entanto, você pode encontrar problemas específicos para estados globais se você usar estados locais. Porque
Você disse encapsulamento?
O uso de estados globais acabará com o encapsulamento, assim como você pode quebrá-lo com os estados locais.
Vamos começar do começo. O que a
Wikipedia nos diz sobre a definição de encapsulamento? O mecanismo de linguagem para restringir o acesso direto a alguns componentes de um objeto. Restrição de acesso? Porque
Bem, como vimos acima, é muito mais fácil argumentar no âmbito local do que no global. Estados mutáveis globais estão, por definição, disponíveis em todos os lugares, e isso é contra o encapsulamento! Sem restrições de acesso para você.
Escopo crescente e vazamento de estado

Vamos imaginar um estado em seu próprio escopo pequeno. Infelizmente, com o tempo, o aplicativo cresce, esse estado local é passado como argumento para a função em todo o aplicativo. Agora seu código de idioma é usado em muitos escopos e em todos eles o acesso direto ao código de idioma é autorizado. Agora é difícil calcular o estado exato do código do idioma sem examinar todas as áreas de visibilidade em que ele existe e onde pode ser alterado. Tudo isso já vimos com estados globais mutáveis.
Tomemos um exemplo: O
Modelo de Domínio Anêmico pode aumentar o escopo de seus modelos mutáveis. De fato, o Modelo de Domínio Anêmico divide os dados e o comportamento de seus objetos de domínio em dois grupos: modelos (objetos apenas com dados) e serviços (objetos apenas com comportamento). Na maioria das vezes, esses modelos serão usados em todos os serviços. Portanto, é provável que algum tipo de modelo aumente constantemente o escopo. Você não entenderá qual modelo é usado em que contexto, o estado deles mudará e os mesmos problemas cairão sobre você.
Quero transmitir uma idéia importante: se você evitar estados globais mutáveis, isso não significa que você pode relaxar, segurar um coquetel em uma mão e pressionar os botões com a outra, curtindo a vida e seu código lendário. -,
, -, , .
, . , .
? - . , , -.
, - , , . , , — . , : , . .
.
.
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 .
, , . : , , . , , , . , , .
?
, (, ), , , . , , , , .
. :
. , — . , .
, , .
, , . , . , , . , !