Há grandes homens que fazem com que todos se sintam pequenos. Mas o verdadeiro grande homem é aquele que faz com que todos se sintam grandes. - G.K. Chesterton
A utilização de variáveis globais é considerada má prática de maneira geral, possuindo um impacto ainda mais negativo quando se trabalha em equipe.
As razões para evitar o uso de variáveis globais são basicamente três:
- Colisões de Nomes
- Fragilidade do Código
- Dificulta Testes
A melhor maneira de se evitar o uso de variáveis globais é através da criação de um único objeto global como fazem diversas bibliotecas: YUI, jQuery, Dojo, Closure. Normalmente, a criação do global único acompanha o uso de Namespaces e Módulos para melhor organizar a aplicação e as equipes que podem estar trabalhando nela. Facilitando a manutenção e ampliação do objeto.
Colisões de Nomes
Observe o código abaixo:
function sayLocation() {
alert(location); // Ruim: De onde location vem?
}
O caso acima utiliza uma variável global location que terá colisão com a variável window.location. Qualquer desenvolvedor que se deparasse com o código acima sem encontrar location no mesmo arquivo em que é definida a função sayLocation ficaria perdido. Não se saberia se location é de fato a variável global window.location ou se é uma variável declarada em outro script que a substitui.
Mesmo que se usasse uma variável global não existente no objeto window, por exemplo validation, ainda se correria dois riscos:
- Do browser posteriormente implementá-la.
- Algum outro desenvolvedor ter a mesma idéia e criar uma variável global diferente com mesmo nome em outro script.
Fragilidade do Código
Uma função que depende de globais é extremamente dependente do ambiente. Utilizar uma função dependente de variáveis globais é inviabilizar sua reutilização e correr o risco de invocá-la e não ter o comportamento esperado,posto que o ambiente constantemente muda e ,por conseguinte, pode impactar sua variável global.
Dificulta Testes
Na contexto de testes unitários, qualquer função que dependa variáveis globais para funcionar requer que você recrie o ambiente global inteiramente para que se possa avaliá-la. Isso quer dizer que você tem que gerenciar mudanças no ambiente global tanto em produção quanto no ambiente de testes. Isso adiciona um duplo custo de manutenção e a necessidade constante de garantir que ambos ambientes estejam sincronizados. Esse é o começo de um penoso trabalho de manutenção.
Globais Não-Intencionais
Existem algumas vezes, entretanto, que mesmo que você não queira declarar variáveis globais isso acaba acontecendo. Veja o exemplo a seguir:
function accidentalGlobal() {
var nome= "Programador";
sobrenome= "Objetivo"; // global não-intencional
}
O código acima provavelmente foi escrito com a intenção que sobrenome fosse local, porém acabou digitando errado o ponto-e-vírgula no lugar da vírgula.
É possível, entretanto, evitar esse tipo de acontecimento infeliz, utilizando ferramentas como JSLint, JSHint ou ainda usando o modo estrito do JavaScript, conforme abaixo:
function accidentalGlobal() {
"use strict";
var nome= "Programador";
sobrenome= "Objetivo"; // ReferenceError: sobrenome is not defined
}
Evitando Variáveis Globais
Como dissemos, uma das maneiras mais clássicas de evitar variáveis globais é através da centralização. Dojo, YUI, jQuery e Closure são todas bibliotecas que fazem essa centralização nos respectivos objetos globais: dojo, YUI, jQuery e goog.
Exemplo de criação:
var MyShop = {};
MyShop.Car= function(model, year, price) {
this.model= model;
this.year= year;
this.price = year;
};
O exemplo acima demonstra como seria criado um objeto global de maneira bem simples.
Utilizar um objeto global genérico permite maior reutilização do código e ajuda na divisão de tarefas e até de equipes.
O Yahoo, por exemplo, possuía uma divisão clara entre equipes, através dos namespaces DOM e Mail. Assim, o YUI.DOM continha implementações exclusivas para se manipular o Document Object Model, enquanto o YUI.Mail se tratava das funcionalidades da aplicação de email.
Namespaces
A criação de Namespaces em si, entretanto, pode ser uma tarefa maçante. Se fôssemos criar um novo namespace para MyShop chamado Motorcycle, teríamos que primeiro criar a propriedade no objeto global para só então atribuir propriedades a Motorcycle:
if(!MyShop.Motorcycle){
MyShop.Motorcycle = {};
}
MyShop.Motorcycle.Motor= {};
MyShop.Motorcycle.Motor.cylinders = 4;
Uma maneira de agilizar esse processo de criação de namespaces sem se preocupar com a existência prévia de propriedades do objeto global é demonstrada na implementação abaixo retirada do livro Maintainable JavaScript.
Fazendo uso do método auxiliar namespace, o código anterior que especifica a quantidade de cilindros no motor de uma motocicleta se reduziria a uma linha:
MyShop.namespace("Motorcycle.Motor").cylinders = 4;
Módulos
Outra maneira de reduzir a interferência de variáveis globais é usando módulos. Quem já trabalhou com NodeJs já está bastante familiarizado com essa prática. O que algumas pessoas não sabem é que também é possível modularizar o código de Front-End. Um dos mais utilizados carregadores de módulo no lado do cliente é o RequireJS.
Muito em breve os próprios navegadores irão possuir seus carregadores de módulos, conforme padrão especificado na ECMAScript 6, facilitando ainda mais a nossa vida.
Conclusão
Insistimos em dizer que variáveis globais devem ser evitadas e mostramos como fazer isso. Isso não quer dizer que elas somente possam ser utilizadas, através da centralização em um único objeto ou ainda que elas nunca possam ser utilizadas.
Abordar o escopo global como algo maligno a nunca ser utilizado a seu favor também pode prejudicar a simplicidade da aplicação e sua reutilização. A abordagem de nunca utilizar variáveis globais em um script, por exemplo, só pode ser utilizada quando se está ciente de que ele não poderá ser reaproveitado por outros scripts, limitando sua funcionalidade a uma única situação.
É o caso do exemplo a seguir que se vale de closures para proteger o escopo global, mas cuja única serventia é preparar o botão para o evento clique.
(function(){
var btn = document.querySelector("#button");
btn.addEventListener('click', function(e){
alert('evento aconteceu!');
});
console.log("Evento preparado");
})()
Próximos Passos
Continue vendo boas práticas de JavaScript em: