Code review é o processo de análise de código por meio de um processo (teoricamente) rigoroso de leitura e revisão crítica de seu conteúdo feita por, no mínimo, duas pessoas. Antes de enviar o código para revisão, os programadores geralmente o "limpam" com uma série de ferramentas automatizadas, dependendo da linguagem e plataforma a serem usados.
No mundo JavaScript, simplesmente por causa da natureza da linguagem, muitos desenvolvedores, especialmente os iniciantes, não conseguem ver as armadilhas e erros que cometem ao programar. Pode ser qualquer coisa, desde o uso de variáveis não declaradas a exceções de ponteiro nulo devido a verificações nulas inconsistentes, até mesmo o uso indevido — ou nenhum uso — do que uma função retorna.
O Google Closure Compiler faz exatamente isso: ele compila código JavaScript para um JavaScript melhorado, analisando, minimizando e reescrevendo-o. E, é claro, também nos alerta sobre as mesmas armadilhas que mencionamos acima. Ele remove o que é desnecessário, verifica a sintaxe, etc.
Neste artigo, apresentaremos alguns problemas comuns que os desenvolvedores de front-end enfrentam, bem como entender melhor como o Closure Compiler pode nos ajudar a verificar rapidamente o código que estamos escrevendo para garantir o melhor código possível.
Começando simples
Você pode executar o Closure Compiler na linha de comando da sua aplicação (por exemplo, em Node.js) ou por meio de um serviço web disponível gratuitamente.
Basicamente, ele expõe uma página web onde você pode compilar seu código por meio de um arquivo JavaScript linkado ou simplesmente colando o código. A ferramenta exibe os resultados no lado direito da tela.
Esses resultados, por sua vez, mostram a diferença de tamanho entre o código-fonte original e a versão compilada (compactada e descompactada) e um link gerado automaticamente para você fazer o download do arquivo JavaScript final.
Mais importante, você verá uma tabela com quatro guias, exibindo:
- O código compilado final;
- Uma lista de avisos e erros, indicando quando eles aconteceram, a linha, o tipo de erro/aviso e uma descrição do que estava errado;
- Os dados do POST que foram usados para fazer o request ao web service REST do Closure Compiler.
Em relação às otimizações, você pode selecionar entre as opções Simple e Advanced (não consideraremos o Whitespace only, pois não faz muita coisa).
O Simple transpilará e minificará seu código JS, além de alertar sobre a sintaxe e os erros mais perigosos (ainda que óbvios) que normalmente cometemos.
O avançado, por outro lado, é muito mais agressivo quando se trata de remover código, reorganizando toda a estrutura da sua implementação original.
Veja a imagem anterior do exemplo padrão “hello, world” na página do web service do Closure: ele reduziu o código e o simplificou, mas perdeu a função hello(), o que significa que referências externas a ele seriam interrompidas. Mas não se preocupe, exploraremos como corrigir isso.
Vamos dar outro exemplo um pouco mais complexo extraído dos tutoriais oficiais do Google:
// Copyright 2009 Google Inc. All Rights Reserved.
/**
* Creates the DOM structure for the note and adds it to the document.
*/
function makeNoteDom(noteTitle, noteContent, noteContainer) {
// Create DOM structure to represent the note.
var headerElement = document.createElement('div');
var headerText = document.createTextNode(noteTitle);
headerElement.appendChild(headerText);
var contentElement = document.createElement('div');
var contentText = document.createTextNode(noteContent);
contentElement.appendChild(contentText);
var newNote = document.createElement('div');
newNote.appendChild(headerElement);
newNote.appendChild(contentElement);
// Add the note's DOM structure to the document.
noteContainer.appendChild(newNote);
}
/**
* Iterates over a list of note data objects and creates a DOM
*/
function makeNotes(data, noteContainer) {
for (var i = 0; i < data.length; i++) {
makeNoteDom(data[i].title, data[i].content, noteContainer);
}
}
function main() {
var noteData = [
{title: 'Note 1', content: 'Content of Note 1'},
{title: 'Note 2', content: 'Content of Note 2'}];
var noteListElement = document.getElementById('notes');
makeNotes(noteData, noteListElement);
}
main();
Aqui, basicamente criamos uma estrutura de dados de notas, cada uma com atributos string de título e conteúdo. O restante é composto por funções utilitárias para iterar a lista de notas e colocá-las todas no documento por meio de cada função de criação. O mesmo código ficará assim depois de compilado pelo Closure Compiler:
for (var a = [{title:"Note 1", content:"Content of Note 1"}, {title:"Note 2", content:"Content of Note 2"}], b = document.getElementById("notes"), c = 0; c < a.length; c++) { var d = a[c].content, e = b, f = document.createElement("div"); f.appendChild(document.createTextNode(a[c].title)); var g = document.createElement("div"); g.appendChild(document.createTextNode(d)); var h = document.createElement("div"); h.appendChild(f); h.appendChild(g); e.appendChild(h); } ;
Observe que toda a lista de variáveis noteData foi alterada para uma declaração de objeto inline, que vem dentro do loop. As variáveis foram renomeadas para caracteres aleatórios do alfabeto. E você não pode reutilizar as funções anteriores em outros lugares; O Closure Compiler provavelmente teria colado a lista duas vezes se estivesse sendo chamado de outro lugar.
No entanto, a legibilidade e a compreensão do código não são boas (o que, é claro, não poderia ser usado em um ambiente de desenvolvimento).
Variáveis não utilizadas
Existem muitos cenários nos quais o Closure Compiler pode atuar — ou seja, problemas comuns ao nosso dia a dia como desenvolvedores JavaScript. Vejamos esse exemplo:'use strict';
const helperModule = require('./helper.js');
var notUsed;
O que aconteceria com o código de saída gerado quando usamos o modo 'use strict'? Ou uma variável não utilizada, mesmo que você defina um valor mais tarde?
É comum criarmos muitas estruturas (não apenas variáveis, mas constantes, funções, classes, etc.) a serem removidas posteriormente que são facilmente esquecíveis — ainda mais se você estiver lidando com uma enorme quantidade de arquivos de código-fonte. Dependendo da complexidade de seus modelos ou de como você expõe seus objetos ao mundo externo, isso pode levar a situações indesejadas.
Bem, esse é o resultado:
var a = require(“./helper.js”);
As estruturas que não foram utilizadas foram identificadas e removidas automaticamente pelo Closure Compiler. Além disso, variáveis locais (let) e constantes (const) são substituídas por declarações var.
Fluxos condicionais
E quanto a um cenário em que um fluxo depende de outro fluxo condicional? Digamos que você tenha uma função, check(), que depende de outra, getRandomInt(), para gerar um número aleatório entre 0 e 1, e retornará true se for 1.Com base nesse fluxo, não sabemos o que vai acontecer porque a função é aleatória — ou seja, somente em tempo de execução veremos se o código entra no if ou não:
let abc = 1;
if (check()) {
abc = "abc";
}
console.info(`abc length: ` + abc.length);
function check() { return getRandomInt(2) == 1; }
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
Eis o código compilado:
var b = 1;
1 == Math.floor(2 * Math.random()) && (b = "abc");
console.info("abc length: " + b.length);
O fluxo condicional foi analisado e reprogramado para uma única linha. Observe como o Closure Compiler verifica a primeira condição precedida por um operador &&. Este operador diz que somente se a primeira condição for verdadeira a segunda será executada. Caso contrário, se nosso número aleatório não for igual a 1, b nunca receberá "abc" como valor.
Que tal um if multi-condicional?
if(document == null || document == undefined || document == ‘’)
console.info('Is not valid');
Eis o resultado:
null != document && void 0 != document && “” != document || console.info(“Is not valid”);
Os ifs condicionais foram aninhados. Esse é o comportamento padrão do Closure Compiler: ele sempre tenta diminuir o máximo possível, mantendo o código pequeno e ainda assim executável.
Referências externas e anotações
Pode ser potencialmente perigoso e improdutivo sempre revisar o código que foi compilado. Além disso, o que acontece se você desejar manter a função check() disponível globalmente para outros arquivos JavaScript? Existem alguns truques aqui, como a sugestão do Google de anexar a função ao objeto global window:window.check = c;
Teríamos, portanto, o seguinte output:
var a = require("./helper.js"), b = 1;
c() && (b = "abc");
console.info("abc length: " + b.length);
null != document && void 0 != document && "" != document || console.info("Is not valid");
function c() {
return 1 == Math.floor(2 * Math.random());
}
window.check = c;
O sistema de tipos que o Closure Compiler checa é o coração de tudo. Você pode anotar seu código para ser mais "tipificado", o que significa que o Compiler verificará usos incorretos com base em suas anotações.
Vamos dar o exemplo da função getRandomInt(). Você precisa que seu parâmetro seja um número em todos os casos; logo, pode solicitar ao Closure Compiler para verificar se um caller está passando algo diferente:
function getRandomInt(/** number */ max) {
return Math.floor(Math.random() * Math.floor(max));
}
window['getRandomInt'] = getRandomInt;
getRandomInt('a');
Isso retornaria:
Mesmo que o arquivo seja sempre compilado para "warnings", você pode ter uma ideia do que está acontecendo com seu código, principalmente para códigos atualizados por muitas pessoas.
Outro recurso interessante são as definições de exportação. Se você decidir que não deseja que algo seja renomeado por meio da opção Advanced, também pode anotar seu código com:
/** @export */
Dessa forma, o código de saída não sofrerá mudanças.
Conclusão
Existem muitos cenários diferentes que você pode usar para testar o poder dessa ferramenta. Vá em frente, pegue seus próprios códigos JavaScript e experimente-os com o Closure Compiler. Faça anotações sobre o que ele gera e as diferenças entre cada opção de otimização. Você também pode pegar qualquer arquivo JavaScript externo e importá-lo no web service para fins de teste.Lembre-se, também é possível executá-lo a partir da linha de comando ou mesmo do seu sistema de build, como Gulp ou Webpack. O Google também fornece uma pasta com exemplos no repositório oficial do GitHub, onde você pode testar mais recursos. Ótimos estudos!
Esse post foi traduzido do meu post no blog da LogRocket: Using Google Closure Compiler to deliver better JavaScript.
Comentários
Postar um comentário