DEV Community

Thiago Bertuzzi 👨🏻‍💻
Thiago Bertuzzi 👨🏻‍💻

Posted on

10 Dicas Basicas de Arquitetura de Código : 2 - Code Smells

Bertuzzi no Pc

Fala galera,

tudo beleza?

Dando continuidade a serie de artigos sobre arquitetura (se não viu a parte 1 clique aqui), hoje vamos falar sobre quando ta fedendo a ... Bem, Código mal cheirosos no portugues bem claro heheheh

Bora?

Bertuzzi voando com um cheiro

Em um bom resumo Code Smells são indicações de possiveis problemas ou deficiências no seu código que podem afetar negativamente a qualidade, perfomance e principalmente manutenção de uma aplicação.

Esses "Smells" de código devem ser levados em consideração pois são sinais de que algo pode estar errado com o projeto.

Code Smells podem ser identificados através de análises e/ou revisões de código. Geralmente são analises feitas por desenvolvedores mais experientes, alem de existirem diversos problemas que podem ocorrer.

Para te ajudar a verificar o seu código (Considerando exemplos comuns) vou demonstrar alguns casos de Code Smells que podem te ajudar a identificar problemas.

Bertuzzi em frente a lixeira pegando fogo

Código em duplicidade:

A famosa duplicidade de código ocorre quando há trechos semelhantes ou idênticos em diferentes partes do código. Isso pode levar a problemas de manutenção, pois qualquer alteração deve ser feita em vários lugares.
Um exemplo de duplicação seria quando a mesma lógica é repetida em diferentes métodos ou classes. A solução para esse problema é extrair a lógica duplicada para um método separado e chamá-lo onde for necessário.

// Código com duplicação
public void CalcularTotalPedido(Pedido pedido)
{
    // ...
    decimal total = 0;
    foreach (var item in pedido.Itens)
    {
        total += item.Preco * item.Quantidade;
    }
    // ...
}

public void CalcularTotalVenda(Venda venda)
{
    // ...
    decimal total = 0;
    foreach (var item in venda.Itens)
    {
        total += item.Preco * item.Quantidade;
    }
    // ...
}

// Código refatorado
public decimal CalcularTotal(IEnumerable<Item> itens)
{
    decimal total = 0;
    foreach (var item in itens)
    {
        total += item.Preco * item.Quantidade;
    }
    return total;
}

public void CalcularTotalPedido(Pedido pedido)
{
    // ...
    decimal total = CalcularTotal(pedido.Itens);
    // ...
}

public void CalcularTotalVenda(Venda venda)
{
    // ...
    decimal total = CalcularTotal(venda.Itens);
    // ...
}

Enter fullscreen mode Exit fullscreen mode

Classe com mais responsabilidades do que devia:

Você ja ouviu a palavra da Mono-Classe? Se não nem escute porque ta errado hahaha. Uma classe com muitas responsabilidades tende a ser mais difícil de entender, testar e manter.
É importante seguir o princípio da responsabilidade única (quer entender mais escute nosso episódio DEVSHOW #21 – SOLID (SRP), onde cada classe deve ter apenas uma razão para mudar.
Isso pode ser identificado quando uma classe executa várias operações diferentes ou possui muitos métodos e propriedades. A solução para isso é dividir a classe em classes menores e mais especializadas, seguindo o princípio da segregação de interfaces.

// Classe com muitas responsabilidades
public class ProcessamentoPedido
{
    public void ProcessarPedido(Pedido pedido)
    {
        // Lógica para processar o pedido

        // Enviar email de confirmação
        EnviarEmailConfirmacao(pedido);

        // Atualizar estoque
        AtualizarEstoque(pedido);

        // Gerar relatório
        GerarRelatorio(pedido);

        // ...
    }

    private void EnviarEmailConfirmacao(Pedido pedido)
    {
        // Lógica para enviar o email
    }

    private void AtualizarEstoque(Pedido pedido)
    {
        // Lógica para atualizar o estoque
    }

    private void GerarRelatorio(Pedido pedido)
    {
        // Lógica para gerar o relatório
    }
}

// Classes refatoradas
public class EmailService
{
    public void EnviarEmailConfirmacao(Pedido pedido)
    {
        // Lógica para enviar o email
    }
}

public class EstoqueService
{
    public void AtualizarEstoque(Pedido pedido)
    {
        // Lógica para atualizar o estoque
    }
}

public class RelatorioService
{
    public void GerarRelatorio(Pedido pedido)
    {
        // Lógica para gerar o relatório
    }
}

public class ProcessamentoPedido
{
    private readonly EmailService emailService;
    private readonly EstoqueService estoqueService;
    private readonly RelatorioService relatorioService;

    public ProcessamentoPedido(EmailService emailService, EstoqueService estoqueService, RelatorioService relatorioService)
    {
        this.emailService = emailService;
        this.estoqueService = estoqueService;
        this.relatorioService = relatorioService;
    }

    public void ProcessarPedido(Pedido pedido)
    {
        // Lógica para processar o pedido

        emailService.EnviarEmailConfirmacao(pedido);
        estoqueService.AtualizarEstoque(pedido);
        relatorioService.GerarRelatorio(pedido);

        // ...
    }
}

Enter fullscreen mode Exit fullscreen mode

Métodos muito longos:

Se você não quer escutar a palavra da Mono-Classe cuidado com o Mono-Método! Métodos longos podem dificultar a compreensão e manutenção do código.
É recomendado que as funções sejam curtas e executem apenas uma tarefa específica. Isso pode ser identificado quando um método possui muitas linhas de código ou realiza várias ações diferentes. A solução é extrair partes do método em funções menores e mais especializadas.

// Método muito longo
public void ProcessarPedido(Pedido pedido)
{
    // Lógica 1
    // ...

    // Lógica 2
    // ...

    // Lógica 3
    // ...

    // ...

    // Lógica 10
    // ...
}

// Método refatorado
public void ProcessarPedido(Pedido pedido)
{
    ProcessarLogica1(pedido);
    ProcessarLogica2(pedido);
    ProcessarLogica3(pedido);
    // ...
    ProcessarLogica10(pedido);
}

private void ProcessarLogica1(Pedido pedido)
{
    // Lógica 1
    // ...
}

private void ProcessarLogica2(Pedido pedido)
{
    // Lógica 2
    // ...
}

// ...

Enter fullscreen mode Exit fullscreen mode

Classe ou método com muitos parâmetros:

Não adianta nada refatorar um método gigante e encher o mesmo de 500 parametros. Quando uma classe ou método possui muitos parâmetros, isso pode indicar uma falta de coesão ou uma responsabilidade excessiva.
Isso pode dificultar a compreensão e o uso da classe ou método. A solução é agrupar parâmetros relacionados em objetos ou estruturas de dados e passá-los como um único argumento.

// Classe com muitos parâmetros
public class Pedido
{
    public void AdicionarItem(string descricao, decimal preco, int quantidade, DateTime dataEntrega)
    {
        // Lógica para adicionar item
    }
}

// Classe refatorada
public class ItemPedido
{
    public string Descricao { get; set; }
    public decimal Preco { get; set; }
    public int Quantidade { get; set; }
    public DateTime DataEntrega { get; set; }
}

public class Pedido
{
    public void AdicionarItem(ItemPedido item)
    {
        // Lógica para adicionar item
    }
}

Enter fullscreen mode Exit fullscreen mode

Métodos ou classes com baixa coesão:

Ainda falando de baixa coesão, a mesma ocorre quando há muitas responsabilidades ou tarefas diferentes agrupadas em um único método ou classe. Isso pode tornar o código mais difícil de entender e manter. A solução é dividir o método ou classe em partes menores e mais coesas.

// Classe com baixa coesão
public class Relatorio
{
    public void GerarRelatorioVendas()
    {
        // Lógica para buscar dados de vendas

        // Lógica para calcular total de vendas

        // Lógica para formatar e exibir relatório
    }
}

// Classe refatorada
public class Relatorio
{
    public IEnumerable<Venda> BuscarVendas()
    {
        // Lógica para buscar dados de vendas
    }

    public decimal CalcularTotalVendas(IEnumerable<Venda> vendas)
    {
        // Lógica para calcular total de vendas
    }

    public void ExibirRelatorio(IEnumerable<Venda> vendas)
    {
        // Lógica para formatar e exibir relatório
    }

    public void GerarRelatorioVendas()
    {
        var vendas = BuscarVendas();
        var total = CalcularTotalVendas(vendas);
        ExibirRelatorio(vendas);
    }
}

Enter fullscreen mode Exit fullscreen mode

Dependências desnecessárias:

Muitas vezes declaramos classes, ou refatoramos um código e esquecemos varias dependências que não são mais utilizadas. Isso pode aumentar o acoplamento e tornar o código mais frágil. A solução é remover as dependências não utilizadas ou separá-las em classes ou módulos independentes.

// Classe com dependências desnecessárias
public class PedidoService
{
    private readonly EmailService emailService;
    private readonly EstoqueService estoqueService;

    public PedidoService(EmailService emailService, EstoqueService estoqueService)
    {
        this.emailService = emailService;
        this.estoqueService = estoqueService;
    }

    public void ProcessarPedido(Pedido pedido)
    {
        // Lógica para processar o pedido

        emailService.EnviarEmailConfirmacao(pedido);
        estoqueService.AtualizarEstoque(pedido);
    }
}

// Classe refatorada
public class PedidoService
{
    private readonly EmailService emailService;

    public PedidoService(EmailService emailService)
    {
        this.emailService = emailService;
    }

    public void ProcessarPedido(Pedido pedido)
    {
        // Lógica para processar o pedido

        emailService.EnviarEmailConfirmacao(pedido);
    }
}

Enter fullscreen mode Exit fullscreen mode

Muitas responsabilidades em uma classe base:

A famosa classe "Zelador"! Quando uma classe base contém muitas funcionalidades e responsabilidades, ela pode se tornar complexa e difícil de entender. Isso pode levar a problemas de manutenção e dificultar a extensibilidade do código. A solução é extrair as responsabilidades em classes derivadas ou interfaces mais especializadas.

// Classe base com muitas responsabilidades
public class Animal
{
    public void Mover()
    {
        // Lógica para mover o animal
    }

    public void Comer()
    {
        // Lógica para alimentar o animal
    }

    public void Dormir()
    {
        // Lógica para fazer o animal dormir
    }

    // ...
}

// Classes derivadas ou interfaces especializadas
public interface IMovel
{
    void Mover();
}

public interface IComivel
{
    void Comer();
}

public interface IDormivel
{
    void Dormir();
}

public class Cachorro : IMovel, IComivel, IDormivel
{
    public void Mover()
    {
        // Lógica para mover o cachorro
    }

    public void Comer()
    {
        // Lógica para alimentar o cachorro
    }

    public void Dormir()
    {
        // Lógica para fazer o cachorro dormir
    }

    // ...
}

// ...

Enter fullscreen mode Exit fullscreen mode

Uso excessivo de comentários:

Eu sempre digo que a arquitetura/código bem feito(a) é auto explicativo. Por mais que os comentários sejam úteis para explicar o propósito de um código, o uso excessivo deles pode ser indicativo de código mal escrito ou falta de clareza. Um código limpo e bem estruturado deve ser autoexplicativo na maioria dos casos. A solução é simplificar o código e usar nomes descritivos para classes, métodos e variáveis.

// Classe para calcular o total do pedido
public class CalculadoraPedido
{
    // Método para calcular o total do pedido
    public decimal CalcularTotalPedido(Pedido pedido)
    {
        decimal total = 0;

        // Loop pelos itens do pedido
        foreach (var item in pedido.Itens)
        {
            // Cálculo do subtotal do item
            decimal subtotal = item.Preco * item.Quantidade;

            // Adicionar o subtotal ao total
            total += subtotal;
        }

        // Retornar o total do pedido
        return total;
    }
}

Enter fullscreen mode Exit fullscreen mode

Objetos com muitas propriedades:

Quando você cria um objeto com um grande número de propriedades, isso pode indicar uma falta de coesão e um design pouco eficiente. Uma classe com muitas propriedades pode ser difícil de entender e pode indicar que ela está tentando fazer muitas coisas ao mesmo tempo (muitas vezes mais do que deveria). A solução é revisar a classe e identificar se é possível dividir suas responsabilidades em classes mais especializadas ou agrupar propriedades relacionadas em objetos compostos.

// Classe com muitas propriedades
public class Pessoa
{
    public string Nome { get; set; }
    public int Idade { get; set; }
    public string Endereco { get; set; }
    public string Telefone { get; set; }
    public string Email { get; set; }
    // ...
}

// Classe refatorada
public class Pessoa
{
    public string Nome { get; set; }
    public int Idade { get; set; }
    public Endereco Endereco { get; set; }
    public Contato Contato { get; set; }
    // ...
}

public class Endereco
{
    public string Logradouro { get; set; }
    public string Cidade { get; set; }
    public string Estado { get; set; }
    public string CEP { get; set; }
    // ...
}

public class Contato
{
    public string Telefone { get; set; }
    public string Email { get; set; }
    // ...
}

Enter fullscreen mode Exit fullscreen mode

Falta de tratamento de erros adequado:

Eu sei em, você ja fez ou viu um Catch vazio... Um código que não possui tratamento adequado de erros, como exceções não capturadas ou blocos catch vazios, pode resultar em falhas no sistema e comportamentos inesperados. É importante implementar tratamento de erros apropriado para lidar com exceções e situações inesperadas de forma segura e controlada.

// Falta de tratamento de erros adequado
public decimal Dividir(decimal a, decimal b)
{
    return a / b;
}

// Tratamento de erros adequado
public decimal Dividir(decimal a, decimal b)
{
    try
    {
        return a / b;
    }
    catch (DivideByZeroException ex)
    {
        // Lógica para lidar com a divisão por zero
        throw;
    }
    catch (Exception ex)
    {
        // Lógica para lidar com outras exceções
        throw;
    }
}

Enter fullscreen mode Exit fullscreen mode

Falta de testes automatizados:

É eu sei , se funciona na produção com certeza funciona na homologação. A falta de testes automatizados é um code smell comum em projetos.
A ausência de testes unitários adequados torna mais difícil verificar a corretude do código e introduz riscos de regressão. É recomendado implementar testes automatizados para garantir que o código funcione corretamente e evitar problemas futuros.

// Falta de testes automatizados

// Código a ser testado
public decimal CalcularTotal(List<decimal> valores)
{
    decimal total = 0;
    foreach (var valor in valores)
    {
        total += valor;
    }
    return total;
}

// Implementação de testes automatizados
[TestFixture]
public class CalculadoraTests
{
    [Test]
    public void CalcularTotal_DeveRetornarSomaDosValores()
    {
        // Arrange
        var calculadora = new Calculadora();
        var valores = new List<decimal> { 1.5m, 2.5m, 3.5m };

        // Act
        var resultado = calculadora.CalcularTotal(valores);

        // Assert
        Assert.AreEqual(7.5m, resultado);
    }
}

Enter fullscreen mode Exit fullscreen mode

Ausência de documentação adequada:

Principalmente para quem faz componentes, dlls, frameworks entre outros códigos que serão consumidos por terceiros, um código não possuir documentação adequada, como comentários explicativos ou documentação XML, pode ser difícil para outros desenvolvedores entenderem o propósito e o funcionamento do código. É recomendado fornecer documentação clara e concisa para ajudar na compreensão e no uso correto do código.

// Ausência de documentação adequada
public class Cliente
{
    public string Nome { get; set; }
    public string Email { get; set; }
}

// Documentação adequada
/// <summary>
/// Representa um cliente no sistema.
/// </summary>
public class Cliente
{
    /// <summary>
    /// Obtém ou define o nome do cliente.
    /// </summary>
    public string Nome { get; set; }

    /// <summary>
    /// Obtém ou define o e-mail do cliente.
    /// </summary>
    public string Email { get; set; }
}

Enter fullscreen mode Exit fullscreen mode

Ufa! Acho que por hoje ta bom né? hahaha. É claro que existem muitos outros casos, mas creio que com esses exemplos você ja entendeu os mais comuns e tambem a ideia dos Code Smells conseguindo identifica-los.

Caso queira entender mais sobre SOLID e alguns dos principios citados eu recomendo escutar nossos episódios do DEVSHOW Relacionados ao tema.

Espero ter ajudado!

Aquele abraço!

Top comments (0)