Identificando Code Smells em JavaScript

Roberto Umbelino - Jul 6 - - Dev Community

Fala devs, tudo bem? Nesse post vou falar sobre um tópico importante que às vezes passa despercebido, que são os Code Smells. 🐽

📑 Sobre

Code Smells são sinais de que algo pode estar errado no seu código. Eles não são bugs propriamente ditos, mas indicam problemas de design que podem levar a bugs no futuro. Basicamente, são aqueles "toques" que fazem você pensar: "Hmm, isso pode causar problemas mais tarde." Vamos dar uma olhada em alguns exemplos em JavaScript! 😎


1. Duplicated Code (Código Duplicado)

Código duplicado é tipo quando você cola o mesmo trecho de código em várias partes do seu projeto, aí depois dá aquele nó e vira uma bagunça pra manter tudo certinho.

function calcularPrecoTotalComImposto(preco, taxaImposto) {
    return preco + (preco * taxaImposto);
}

function calcularPrecoTotalComDescontoEImposto(preco, desconto, taxaImposto) {
    const precoComDesconto = preco - desconto;
    // Código duplicado
    return precoComDesconto + (precoComDesconto * taxaImposto);
}
Enter fullscreen mode Exit fullscreen mode

Refatorado:

// Código refatorado
function calcularPrecoTotalComImposto(preco, taxaImposto) {
    return preco + (preco * taxaImposto);
}

function calcularPrecoTotalComDescontoEImposto(preco, desconto, taxaImposto) {
    const precoComDesconto = preco - desconto;
    return calcularPrecoTotalComImposto(precoComDesconto, taxaImposto);
}
Enter fullscreen mode Exit fullscreen mode

2. Long Method (Método Longo)

Um método longo é aquele que faz muitas operações em sequência, fazendo com que fique muito difícil de entender e modificar.

// Método longo
function processarPedido(pedido) {
    // Validar pedido
    if (!pedido.id || !pedido.itens || pedido.itens.length === 0) {
        throw new Error('Pedido inválido');
    }

    // Calcular preço total
    const precoTotalInicial = itens.reduce((total, item) => total + item.preco * item.quantidade, 0);

    // Aplicar desconto
    const precoComDesconto = pedido.desconto ? precoTotalInicial - pedido.desconto : precoTotalInicial;

    // Aplicar imposto    
    const taxaImposto = 0.1;
    const precoFinal = precoComDesconto + precoComDesconto * taxaImposto;

    // Finalizar pedido
    console.log(`Pedido ${pedido.id} processado com preço total ${precoFinal.toFixed(2)}`);
}
Enter fullscreen mode Exit fullscreen mode

Refatorado:

// Método refatorado
function validarPedido(pedido) {
    if (!pedido.id || !pedido.itens || pedido.itens.length === 0) {
        throw new Error('Pedido inválido');
    }
}

function calcularPrecoTotal(itens, desconto = 0) {
    const precoTotal = itens.reduce((total, item) => total + item.preco * item.quantidade, 0);
    return precoTotal - desconto;
}

function aplicarImposto(precoTotal) {
    return precoTotal * 1.1;
}

function processarPedido(pedido) {
    validarPedido(pedido);
    const precoTotal = calcularPrecoTotal(pedido.itens, pedido.desconto);
    const precoTotalComImposto = aplicarImposto(precoTotal);
    console.log(`Pedido ${pedido.id} processado com preço total ${precoTotalComImposto.toFixed(2)}`);
}
Enter fullscreen mode Exit fullscreen mode

3. Large Class (Classe Grande)

Uma classe grande tem muitas responsabilidades ou métodos, o que pode indicar que ela está fazendo mais do que deveria e pode ser difícil de manter.

// Classe grande
class Pedido {
    constructor(id, itens, desconto) {
        this.id = id;
        this.itens = itens;
        this.desconto = desconto;
    }

    validar() {
        if (!this.id || !this.itens || this.itens.length === 0) {
            throw new Error('Pedido inválido');
        }
    }

    calcularPrecoTotal() {
        const precoTotal = itens.reduce((total, item) => total + item.preco * item.quantidade, 0);
        return precoTotal - desconto;
    }

    aplicarImposto(precoTotal) {
        return precoTotal * 1.1;
    }

    processar() {
        this.validar();
        let precoTotal = this.calcularPrecoTotal();
        precoTotal = this.aplicarImposto(precoTotal);
        console.log(`Pedido ${this.id} processado com preço total ${precoTotal}`);
    }
}
Enter fullscreen mode Exit fullscreen mode

Refatorado:

// Classes refatoradas
class ValidadorDePedido {
    static validar(pedido) {
        if (!pedido.id || !pedido.itens || pedido.itens.length === 0) {
            throw new Error('Pedido inválido');
        }
    }
}

class CalculadoraDePreco {
    static calcularPrecoTotal(itens, desconto = 0) {
        const precoTotal = itens.reduce((total, item) => total + item.preco * item.quantidade, 0);
        return precoTotal - desconto;
    }
}

class CalculadoraDeImposto {
    static aplicarImposto(precoTotal) {
        return precoTotal * 1.1;
    }
}

class ProcessadorDePedido {
    constructor(pedido) {
        this.pedido = pedido;
    }

    processar() {
        ValidadorDePedido.validar(this.pedido);
        const precoTotal = CalculadoraDePreco.calcularPrecoTotal(this.pedido.itens, this.pedido.desconto);
        const precoTotalComImposto = CalculadoraDeImposto.aplicarImposto(precoTotal);
        console.log(`Pedido ${this.pedido.id} processado com preço total ${precoTotalComImposto.toFixed(2)}`);
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Feature Envy (Inveja de Funcionalidade)

Feature Envy ocorre quando uma classe utiliza excessivamente métodos ou dados de outra classe, o que pode indicar uma distribuição inadequada de responsabilidades.

// Feature Envy
class Cliente {
    constructor(nome, endereco) {
        this.nome = nome;
        this.endereco = endereco;
    }
}

class Pedido {
    constructor(cliente, itens) {
        this.cliente = cliente;
        this.itens = itens;
    }

    imprimirEtiquetaDeEnvio() {
        console.log(`Envio para: ${this.cliente.nome}, ${this.cliente.endereco}`);
    }
}
Enter fullscreen mode Exit fullscreen mode

Refatorado:

// Refatorado para mover a responsabilidade
class Cliente {
    constructor(nome, endereco) {
        this.nome = nome;
        this.endereco = endereco;
    }

    obterEtiquetaDeEnvio() {
        return `Envio para: ${this.nome}, ${this.endereco}`;
    }
}

class Pedido {
    constructor(cliente, itens) {
        this.cliente = cliente;
        this.itens = itens;
    }

    imprimirEtiquetaDeEnvio() {
        console.log(this.cliente.obterEtiquetaDeEnvio());
    }
}
Enter fullscreen mode Exit fullscreen mode

5. Primitive Obsession (Obsessão por Primitivos)

Primitive Obsession é quando se utiliza primitivos (como strings e números) para representar conceitos que deveriam ser encapsulados em classes específicas, resultando em código menos legível e mais difícil de manter.

// Primitive Obsession
class Pedido {
    constructor(id, itens, codigoDesconto) {
        this.id = id;
        this.itens = itens;
        this.codigoDesconto = codigoDesconto;
    }

    aplicarDesconto() {
        if (this.codigoDesconto === 'SAVE10') {
            // Aplicar desconto de 10%
        } else if (this.codigoDesconto === 'SAVE20') {
            // Aplicar desconto de 20%
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Refatorado:

// Refatorado para usar classes específicas
class Desconto {
    constructor(codigo, porcentagem) {
        this.codigo = codigo;
        this.porcentagem = porcentagem;
    }

    aplicar(precoTotal) {
        return precoTotal - (precoTotal * this.porcentagem / 100);
    }
}

class Pedido {
    constructor(id, itens, desconto) {
        this.id = id;
        this.itens = itens;
        this.desconto = desconto;
    }

    aplicarDesconto(precoTotal) {
        if (this.desconto) {
            return this.desconto.aplicar(precoTotal);
        }
        return precoTotal;
    }
}
Enter fullscreen mode Exit fullscreen mode

Curtiu o post? Ainda há outros exemplos que poderiam ser citados, mas isso deixaria o post muito longo 😋, mas espero que esses exemplos ajudem de alguma forma a você conseguir identificar e corrigir os code smells no seu projeto. 🚀

. . . . .