DEV Community

Eduardo Teixeira
Eduardo Teixeira

Posted on

Clojure - Símbolos globais e locais

Até agora, sempre que foi necessário manter um valor em memória nós usamos o conceito de "variável", realizando a "definição de um símbolo" com o comando "def". Porém essa abordagem corresponde ao que conhecemos nas linguagens mais tradicionais como "definição de variável global".

Como é de conhecimento, as definições de "variáveis globais" podem trazer uma série de problemas as aplicações, uma vez que é quase impossível ter conhecimento de todas, com isso podemos acabar gerando problemas com a substituição de valores durante seu uso, podendo causar erros em diversos pontos da aplicação e tornando a aplicação instável e de difícil manutenção.

Dito isso, veremos como trabalhar com símbolos dentro de um escopo limitado, seguiremos os próximos exemplos dentro de nossas funções.

Escopo global

Como vimos até aqui, os símbolos de escopo global podem ser acessados de qualquer parte da aplicação, vamos ao exemplo, considerando uma aplicação hipotética de uma escola, temos duas rotinas distintas que mostram a média dos alunos em duas condições distintas:

Uma que mostra e média "padrão" acrescida de um ponto extra;
Outra que mostra a média do aluno em uma condição de "recuperação", que acresce a média dois pontos extras;
Enquanto a rotina que mostra a média "padrão" olha para um símbolo global para fazer seu cálculo sem fazer qualquer alteração a rotina que mostra a média dos alunos em condição de "recuperação" também utiliza o mesmo símbolo global, porém ela sempre altera seu valor para "dois".

O problema que teremos é o seguinte, enquanto a rotina que mostra as médias de alunos em "recuperação" não for executada a rotina de média "padrão", quando executada, apresentará um resultado correto, porém após primeira execução da rotina de "recuperação" a "padrão" começará a mostrar um resultado errado:

(def ponto-extra 1)

(defn verifica-situacao-aluno-padrao
  "Retorna a media do aluno somada a seu ponto extra"
  [nota1 nota2]
  (+ (/ (+ nota1 nota2) 2) ponto-extra)
)
(defn verifica-situacao-aluno-recuperacao
  "Retorna a media do aluno somada a seu ponto extra (em caso de recuperação)"
  [nota1 nota2]
  (def ponto-extra 2)
  (+ (/ (+ nota1 nota2) 2) ponto-extra)
)

ponto-extra
(verifica-situacao-aluno-padrao 5 5)
(verifica-situacao-aluno-recuperacao 5 5)
(verifica-situacao-aluno-padrao 5 5)
Enter fullscreen mode Exit fullscreen mode

Exemplificando um problema com símbolo global

Sobre a imagem:

  • Na linha três definimos nosso símbolo global "ponto-extra";
  • Da linha cinco até a linha dezesseis definimos nossas duas funções, "padrão" e "recuperação" respectivamente;
  • Na linha dezenove checamos o valor do nosso símbolo global "ponto-extra", que é "1";
  • Na linha vinte e um consultamos a situação de um aluno com a rotina "padrão" e vemos que sua média é "6";
  • Na linha vinte e três consultamos a situação de um aluno com a rotina de "recuperação" e vemos que sua média é "7";
  • Na linha vinte e cinco consultamos a situação de um aluno com a rotina "padrão" novamente e agora vemos um resultado diferente, sua média passou a ser "7", mesmo valor que a rotina de "recuperação";
  • Na linha vinte e sete checamos o valor do nosso símbolo global "ponto-extra" novamente, e conforme esperado ele agora é "2";

Conforme já explicado, a função de "recuperação" altera o valor do símbolo global que é usado por outras funções, essa mudança não era esperada em outras partes da aplicação e isso gerou um problema de divergência de valores, e pior, a momentos onde o problema não ocorre (já que ele só ocorre a partir da primeira execução da rotina de "recuperação") e a rotinas que continuam a funcionar (a rotina "recuperação" sempre funciona). Acabamos de gerar um bug em nossa aplicação que não será nada fácil de simular/encontrar/corrigir.

Escopo local

Como visto anteriormente, o uso inadequado de um símbolo global gerou um bug em nossa aplicação, para resolver esse problema o desenvolvedor que criou a função de "recuperação" deveria ter usado um símbolo de escopo local na rotina, uma vez que o valor de "ponto-extra" usado ali é uma regra especifica dessa rotina.

Para trabalharmos com o escopo local utilizaremos a função "let", com ele criamos um escopo local com símbolos e funções:

  • O "let" deve ser usado entre parênteses;
  • Ele recebe um vetor de símbolos/valores;
  • Ele recebe uma ou mais funções;
  • Ele retorna o resultado da execução de sua última função;
(let [idade 40] (+ idade 1))
Enter fullscreen mode Exit fullscreen mode

Usando o let pela primeira vez

Na imagem acima:

  • Na linha um criamos nosso escopo local com o "let", nesse escopo temos o símbolo "idade" com o valor "40" dentro do vetor e temos uma função que soma a valor "1" ao nosso símbolo "idade";
  • Na linha dois temos o resultado da função de soma;
  • Na linha três tentamos usar o símbolo "idade" e recebemos um erro na sequencia dizendo que não foi possível resolver o símbolo;

Bom, agora que já sabemos como resolver o problema de escopo vamos aplicar uma correção em nossa função "recuperação", para que ela deixe de usar o símbolo global, primeiro vamos reiniciar o símbolo global para seu valor correto, em seguida vamos corrigir a função e ne sequencia executar os testes:

(def ponto-extra 1)

(defn verifica-situacao-aluno-recuperacao
  "Retorna a media do aluno somada a seu ponto extra (em caso de recuperação)"
  [nota1 nota2]
  (let [ponto-extra-recuperacao 2]
    (+ (/ (+ nota1 nota2) 2) ponto-extra-recuperacao))  
)

(verifica-situacao-aluno-padrao 5 5)
(verifica-situacao-aluno-recuperacao 5 5)
(verifica-situacao-aluno-padrao 5 5)
ponto-extra
Enter fullscreen mode Exit fullscreen mode

Refatorando a função para uso do escopo local

Na imagem acima:

  • Na linha 1 redefinimos nosso símbolo global para seu valor correto;
  • Da linha três até a oito temos a refatoração de nossa função com o uso do "let";
  • Nas linhas dez, doze e catorze repetimos os testes, e agora os seus respectivos resultados estão corretos;

Conclusão

Nesse post entendemos e exemplificamos os problemas causados pelo uso do escopo global, vimos como trabalhar com escopo local com o "let" e refatoramos nossa função problemática para que nossa aplicação voltasse a funcionar da forma esperada.

Saiba mais

Top comments (0)