Por que evitar as variáveis globais e como

Em um artigo publicado em 1973 por Wulf e Shaw [1] foi descrito que o uso indiscriminado de variáveis globais é prejudicial. O problema independe da linguagem de programação: as variáveis globais são danosas desde a linguagem de baixo nível até as mais altas. E os mais jovens acabam aprendendo sobre isso quando entram em contato com comunidades de programação, livro do Jack Ganslee [2] ou errando nos projetos da empresa. Em 2011 houveram problemas com a Toyota Camry, e foi apontado que uma das causas era o uso de 11.000 variáveis globais [3] [4].

Mas qual é exatamente o problema das variáveis globais? Para quem já trabalhou em algum projeto sabe que é fácil chegar a mil ou duas mil linhas de código, ficando pouco a pouco mais difícil de cumprir o fluxograma inicial ou diagrama de estados planejado. Uma das razões é devido ao fato de várias funções e interrupções terem livre acesso às variáveis globais, gerando estados não existentes ao inicialmente planejado.

Para quem viveu numa república durante a faculdade vai entender com o seguinte exemplo: todos têm acesso à geladeira, certo? Você chega de noite, coloca o pudim que comprou dentro da geladeira, e espera o dia seguinte para comê-lo depois do almoço. E o que acontece? O seu “irmão” come o pudim antes porque teve livre acesso a ele. E ainda é provável de você ser chamado de “o vacilão”.

Uma das formas de evitar as variáveis globais é logicamente utilizar as variáveis locais e implementar funções com passagem de valor. Veja o exemplo abaixo, adaptado para o caso do pudim na república:

A variável pudding é vista somente dentro de main() e a função paulaEatsPudding() tem acesso à variável. Você pode prever o que vai acontecer com a variável de acordo com a ordem de execução ao estilo polling no main(), porém ao implementar as interrupções no código, o programador começa a coçar a cabeça, uma vez que não é possível fazer passagem de valor às interrupções.

Isso seria a mesma coisa que, para proteger o pudim, a Paula tivesse que esperar em frente a geladeira até a hora que ficar com fome, vigiando se alguém não vá pegar antes dela. Somando a isso, e se um colega de quarto dela pedisse um pedaço também?

Na próxima parte irei mostrar uma solução utilizando-se de modificadores de acesso e flags na linguagem C.

“Bilhete” para limitar o acesso e “sinalizações” para avisar quem foi que comeu

variáveis-globais-01
Figura 1 – Além do static, não esqueça de colocar o seu nome!

Agora vamos imaginar a seguinte situação: a Paula comprou o pudim (pudding), guardou dentro da geladeira (main.c) e foi para o seu quarto (room.c). Mas a Diana, a sua colega de quarto e melhor amiga, viu o pudim e perguntou para a Paula se poderia comer também, quando estivesse com fome. A Paula, muito boazinha, deixou. Vamos interpretar a fome das duas como se fossem dois bits de uma interrupção de fome (interrupt_Hunger() de room.c). O código ficaria da seguinte maneira:

Arquivo main.c:

Arquivo room.h:

Arquivo room.c:

O modificador static torna a variável global pudding visível somente no arquivo em que é declarado (main.c), e na república ficou estabelecido que somente as duas teriam acesso ao pudim (linha 15 do main.c). Elas podem ficar no quarto delas (room.c) até a hora em que a fome bater (interrupt_Hunger()).

As interrupções somente avisam que algo aconteceu, mas não fazem o processamento complexo de alguma variável. No exemplo, utilizamos as interrupções para manipular somente as flags/sinalizações, para que o processamento dos dados principais (pudding = eatTheFood(pudding);) seja feito no loop do main().  Isso de certa forma evita a descaracterização do fluxo lógico ou a criação de um estado inexistente na máquina de estados, como também segue o princípio de responsabilidade única.

Note também que no interrupt_Hunger() do arquivo room.c não foi usado a comparação “==”, mas sim o operador binário “&”, pois se as duas interrupções ocorressem simultaneamente, na comparação “==” os dois casos if iriam ser falsas. Porém, utilizando o operador binário “&” será verdadeiro nos dois casos. Veja o artigo do Fábio Souza a respeito de manipulação de bits. No loop do main() coloquei o caso da Paula primeiro, pois caso as duas interrupções ocorram, ela irá comer primeiro (porque foi ela quem comprou o pudim), e a Diana comerá se sobrar algum pedaço (o que não vai acontecer no exemplo pois só há um pedaço).

Caso as duas interrupções viessem de registradores diferentes, seria necessário configurar a prioridade das interrupções para não haver conflito de simultaneidade: Caso duas ou mais interrupções ocorram, a de maior prioridade é executada antes e a de menor depois. Se as duas estivessem com fome simultaneamente, a Paula teria a prioridade e comeria o pudim. No caso de firmware multithreads deve-se usar a exclusão mútua (Mutex) para evitar que duas ou mais threads modifiquem uma variável ao mesmo tempo. O Felipe Neves já escreveu um artigo muito legal tratando sobre isso.

Tecnicamente falando, a variável pudding poderia estar dentro do main() sendo uma variável local sem o static. A diferença entre a variável local e a static é que a primeira é apagada do stack da memória RAM quando a função que a usa finaliza o seu serviço. Mas como a variável local estará dentro de main(), permanecerá existindo. No artigo do Fernando é explicado mais detalhadamente sobre o modificador static e mostra um uso alternativo nas funções.

“Uso medicinal” das variáveis globais

Praticamente em duas situações você pode usar as variáveis globais:

  • Quando é uma constante: Bits de configuração de registrador, constantes matemáticas como π, valores de referências fixas que nunca mudam. É aconselhável utilizar modificador const ou macros #define por segurança. Fernando Deluno Garcia fez alguns artigos relacionados a isso em linguagem C [9] [10]. Você pode deixar essas constantes listadas em um arquivo header para não poluir visualmente o arquivo principal.
  • Valores de referência para todo o programa: Por exemplo, velocidade de um motor que deve servir de referência para muitas funções e módulos. Essas variáveis não podem ser duplicadas e deve ser utilizado o modificador volatile para que o valor se propague pelo fluxo lógico.
Variáveis globais - Pudim Static, revenda com os melhores profissionais de firmware!
Figura 2 -  Pudim Static, revenda com os melhores profissionais de firmware!

Conclusão

Gostaria de deixar claro que esses exemplos não são métodos infalíveis de se evitar bugs no código. Um profissional da área pode perceber falhas nos exemplos acima, mas como é um exemplo generalista, não quis entrar em detalhes para não deixar mais enfadonho para o estudante que está vendo isso pela primeira vez. O ponto principal do artigo é: reduzindo o escopo das variáveis torna o código mais seguro a bugs. A solução básica é utilizando variáveis locais e funções com passagem de valor. Eu quis também mostrar uma solução alternativa para quando conceitos como interrupção e modularização entram em jogo. Por se tratar de uma solução que exige mais da memória, o programador deve tomar cuidado e alocar corretamente o tamanho stack e heap da RAM.

Espero que tenham gostado do artigo, e se você conhece alguma técnica alternativa para evitar as variáveis globais, escreva nos comentários! Deem uma olhada nas referências abaixo, principalmente nos artigos do Embarcados, pois temos bastante material de excelente qualidade, ensinando a escrever códigos profissionais.

Referências

[1] WULF, W.; SHAW, M.; GLOBAL VARIABLE CONSIDERED HARMFUL, Carnegie-Mellon University, Pittsburg, Fev. 1973.

[2] Ganssle, J.; The Art of Designing Embedded Systems, Newnes, 2000.

[3] ”Toyota Unintended Acceleration and the Big Bowl of “Spaghetti” Code” por Safety Research & Strategies INC.

[4] “Editorial: Custo do firmware” por Equipe Embarcados

[5] “Princípio da Responsabilidade Única em Firmwares” por Felipe Lavratti

[6] “Bits em Linguagem C - Conceito e Aplicação” por Fábio Souza

[7] “Como utilizar os semáforos para compartilhar recursos no mbed OS” por Felipe Neves

[8] “Como utilizar os semáforos para compartilhar recursos no mbed OS” por Fernando Deluno Garcia

[9] “Modificadores de Acesso na Linguagem C” por Fernando Deluno Garcia

[10] “Pré-processador C – Parte 1” por Fernando Deluno Garcia

[11] "Better Embedded System SW - Minimize Use of Global Variables" por Phil Koopman

[12] “Ponteiro em C: Definição” por Fernando Deluno Garcia

[13] “Programação Modular em C” por Fernando Deluno Garcia

[14] “Arquitetura de desenvolvimento de software - parte I” por Rodrigo Almeida

[15] “Máquina de estado” por Pedro Bertoleti

[16] “Regras do Contexto de Interrupção em Kernel Space” por Felipe Lavratti

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Software » Por que evitar as variáveis globais e como
Talvez você goste:
Comentários:

Séries

Menu