Evite condicionais em JavaScript com o uso de Duck Typing
Escreva código de qualidade e orientado a objetos em JavaScript
Desenvolver aplicações Web é uma prática que requer do desenvolvedor conhecimento em diversas linguagens, como HTML, CSS, JavaScript, Java, SQL, entre outras. Com a exigência dos sistemas Web, é importante que um desenvolvedor Web sinta-se confortável ao trabalhar com JavaScript, já que ele usará esta linguagem a maior parte do tempo para criar interfaces ricas e com uma boa usabilidade para o usuário final. Por esse motivo, abordamos JavaScript e jQuery no nosso curso Java para Web.
Mas assim como qualquer outra linguagem orientada e objetos, se não soubermos aproveitar as características deste paradigma nós acabamos por escrever código procedural e muitas vezes difíceis de manter e entender. Um dos problemas mais conhecidos é o excesso de ifs
no código, que a priori parece normal e até necessário, mas na verdade é uma armadilha que pode causar problemas de repetição de código, falta de legibilidade e interferir numa manutenção sustentável do software.
Para entender este problema de maneira prática, imagine que depois de uma requisição AJAX você precise construir uma div
com vários botões ou links dentro e que cada um deles aparecerá de acordo com algumas regras baseado no objeto JSON retornado do servidor. Nada que um desenvolvedor Web não esteja acostumado a fazer no dia a dia. Portanto, não seria uma surpresa encontrar um código dentro da função de callback de sucesso como a seguir:
function(entrega) {
var div = $("#todos-botoes");
// adiciona botão "Cancelar" na div
if (entrega.pendente) {
var botaoCancelar = $("<a>")
.attr("href", "#")
.attr("title", "Cancelar entrega")
.html("Cancelar");
div.append(botaoCancelar);
}
// adiciona botão "Editar" na div
if (entrega.pendente && entrega.dadosIncompletos) {
var botaoEditar = $("<a>")
.attr("href", "/pizzas/entregas/" + entrega.id)
.attr("title", "Editar dados da entrega")
.html("Editar");
div.append(botaoEditar);
}
// adiciona botão "Enviar recibo" na div
if (entrega.status === "CONFIRMADO") {
var botaoEnviar = $("<a>")
.attr("href", "#")
.attr("title", "Enviar recibo por email")
.html("Enviar recibo");
div.append(botaoEnviar);
}
div.show(); // exibe os botões
}
O problema com essa abordagem é que se precisarmos adicionar mais um botão nós teremos que colocar mais uma instrução if
com um bloco cheio de código dentro dele. Isso pode ser ainda pior se tivermos condicionais aninhadas ou mesmo regras mais complexas.
Nós podemos melhorar o código sem muito esforço, bastaria separar cada lógica responsável por criar um botão em diferentes funções. Algo semelhante ao código abaixo:
function(entrega) {
var div = $("#todos-buttons");
// adiciona botão "Cancelar" na div
if (entrega.pendente) {
adicionaBotaoCancelarNaDiv(div, entrega);
}
// adiciona botão "Editar" na div
if (entrega.pendente && entrega.dadosIncompletos) {
adicionaBotaoEditarNaDiv(div, entrega);
}
// adiciona botão "Enviar recibo" na div
if (entrega.status === "CONFIRMADO") {
adicionaBotaoEnviarReciboNaDiv(div, entrega);
}
div.show(); // exibe os botões
}
O código está um pouco melhor, mas ainda temos o problema com a sequência de ifs
. Se no futuro nós precisarmos de mais um botão nós ainda teremos que escrever mais um if
dentro da função de callback, sem falar da função responsável por criar este novo botão. O problema com ifs
é levado tão a sério que gerou uma campanha contra ifs
na internet.
Uma solução muito comum e orientada a objetos para evitar a proliferação de ifs
no código é usar polimorfismo, e, para nossa felicidade, a linguagem JavaScript tem suporte a ele. Dessa forma, em vez de separar cada lógica em funções nós podemos separá-las em diferentes classes. Por exemplo, uma classe BotaoCancelar
para representar o botão cancelar, uma outra classe BotaoEditar
para o botão editar e assim por diante.
Embora JavaScript suporte polimorfismo e herança, achamos que usar Duck Typing é uma solução mais simples e melhor entendida pela maioria dos desenvolvedores. Com duck typing podemos criar classes que seguem contratos "informais", sem a necessidade de tipos explícitos. Estes contratos seriam válidos apenas dentro de um contexto, semelhante a um acordo de cavalheiros. Por esse motivo, não é à toa que o termo "duck typing" vem do seguinte ditado:
Se parece com um pato, nada como um pato e grasna como um pato, então provavelmente é um pato.
Para usarmos a técnica, primeiramente precisamos definir um contrato para o uso das classes. Dessa forma, todas as classes devem ter o mesmo comportamento, as mesmas funções e consequentemente o mesmo contrato. Pensando na forma como os botões e suas lógicas são utilizadas no código, o contrato pode ser dividido em duas funções:
- uma função para verificar se o botão deve ser renderizado (a condicional);
- uma outra função para criar o botão em si;
Para criar as classes em JavaScript, podemos usar funções construtoras como no código a seguir:
var BotaoCancelar = function() {
this.deveRenderizar = function(entrega) {
return entrega.pendente;
}
this.getHtml = function(entrega) {
var botaoCancelar = $("<a>")
.attr("href", "#")
.attr("title", "Cancelar")
.html("Cancelar");
return botaoCancelar;
}
}
var BotaoEditar = function() {
this.deveRenderizar = function(entrega) {
return entrega.pendente && entrega.dadosIncompletos;
}
this.getHtml = function(entrega) {
var botaoEditar = $("<a>")
.attr("href", "/pizzas/entregas/" + entrega.id)
.attr("title", "Editar dados da entrega")
.html("Editar");
return botaoEditar;
}
}
// outros botões
Agora nós podemos mudar o código principal da função de callback para usar as novas classes em vez do aglomerado de ifs
. Como estamos usando duck typing e todas as classes seguem o mesmo contrato, nós podemos escrever um código que simplesmente trabalhará em cima deste contrato:
function(entrega) {
var div = $("#todos-buttons");
var botoes = [new BotaoCancelar(), new BotaoEditar(), new BotaoEnviarRecibo()];
$.each(botoes, function(index, botao) {
if (botao.deveRenderizar(entrega)) {
div.append(botao.getHtml(entrega));
}
});
div.show(); // exibe os botões
}
Repare que além de termos evitado múltiplos ifs
no código, nós ainda podemos adicionar novos tipos (classes) sem ter que mudar a lógica principal da função de callback. Não seria difícil para um desenvolvedor olhar para o código acima e entender rapidamente como ele funciona e o que é preciso implementar para adicionar um novo botão à div
. Neste caso, bastaria criar um novo botão e adicionar uma instância dele no array botoes
, depois disso toda a lógica funcionaria como esperado.
Com princípios básicos de orientação a objetos e sabendo aproveitar algumas características de linguagens dinâmicas podemos evitar ifs
e condicionais aninhadas no nosso código.
O problema com os ifs
não surge apenas com JavaScript, mas também em outras linguagens e paradigmas. Devido a este problema ser muito comum entres os desenvolvedores Java, o nosso curso de Java e Orientação a Objetos aborda polimorfismo e herança através de exemplos práticos com bastante profundidade.
E você, o que tem feito ou usado para evitar o excesso de ifs
no seu código JavaScript?
Desenvolvedor e instrutor na TriadWorks