Boas Práticas Nathan Geeksman

Clean Code: 5 princípios para escrever código que humanos conseguem ler.

Clean Code: 5 princípios para escrever código que humanos conseguem ler.

Clean Code: 5 princípios para escrever código que humanos conseguem ler.

Introdução

O código limpo é um conceito fundamental no desenvolvimento de software, pois reflete direta e ininterruptamente a qualidade dos projetos que ele sustenta.

A cada dia que passa, os requisitos dos sistemas vão aumentando e tornar-se mais complexos. O número de linhas de código cresce, tornando difícil para os desenvolvedores entenderem suas próprias aplicações. A manutenção do código se torna onerosa e dispendiosa.

No mercado atual, a tecnologia está cada vez mais evoluindo e os requisitos dos clientes estão aumentando em termos de complexidade.

Nesse cenário é necessário pensar no futuro da aplicação e entender que quanto menos difícil de entender o código for menor será o tempo e recursos necessários para fazer as alterações futuras.

Dessa forma, a escrita do código limpo se tornou uma habilidade essencial. Neste artigo você aprenderá os 5 princípios que permitem criar códigos que não apenas são eficientes, mas também fáceis de entender e manter por outros desenvolvedores.

O que é e por que importa

O conceito de código limpo (Clean Code) foi proposto pelo programador estadunidense Robert C. Martin, mais conhecido como "Uncle Bob", em seu livro homônimo publicado em 2008. Código limpo se refere a um conjunto de práticas e padrões que permitem escrever código que seja fácil de entender, manter e modificar por outros desenvolvedores.

O principal objetivo do código limpo é eliminar a complexidade, tornando o código mais conciso e expressivo, sem sacrificar sua legibilidade ou eficiência. Isso é alcançado através da aplicação de princípios como simplicidade, clareza e consistência, que permitem identificar facilmente os problemas e melhorias no código.

Ao adotar a escrita do código limpo, os desenvolvedores podem resolver vários problemas comuns associados à manutenção de sistemas complexos. Alguns desses problemas incluem:

  • Dificuldade em entender o funcionamento do sistema
  • Tempo e recursos necessários para fazer alterações no código
  • Propensão a erros e defeitos ao longo do tempo
  • Desenvolvimento lento devido à complexidade do código

Ao implementar os princípios do código limpo, os desenvolvedores podem:

  • Reduzir o tempo de desenvolvimento e manutenção do sistema
  • Melhorar a qualidade do código, tornando-o mais confiável e escalável
  • Facilitar a colaboração entre os membros da equipe de desenvolvimento

Como funciona na prática

O código limpo é alcançado através da aplicação de cinco princípios fundamentais, que são:

1. Fazer em vez de perguntar

Em vez de criar variáveis para armazenar valores temporários e questioná-los em todo lugar do código, é mais eficiente realizar as operações necessárias imediatamente.

  • Exemplo: Em vez de $valor = calcularPreco($produto); if ($valor > $limite) { ... }, fazer if (calcularPreco($produto) > $limite) { ... }
  • Motivo: Reduz a quantidade de variáveis e declarações desnecessárias

2. Favor subclasses sobre atributos

Subclasses são mais flexíveis do que simplesmente adicionar atributos, pois permitem reutilização de código e melhor manutenção.

  • Exemplo: Em vez de criar um método para adicionar novo campo a uma classe existente, criar uma subclasse com o novo campo necessário
  • Motivo: Facilita a extensão do comportamento da classe sem comprometer a consistência

3. Favor objetos sobre booleanos

Em vez de armazenar resultados como booleanos (true/false), é mais eficiente criar classes para representar esses estados e associados comportamentos.

  • Exemplo: Em vez de if ($estado) { ... }, criar uma classe Estado com métodos relacionados a cada estado
  • Motivo: Melhora a clareza do código ao descrever o significado dos dados

4. Evite variáveis globais

Variáveis globais podem causar problemas de concorrência e tornam difícil manter o código, pois muitas partes dele dependem da mesma variável.

  • Exemplo: Em vez de global $var, criar objetos que contenham esses valores ou usar parâmetros nos métodos
  • Motivo: Reduz a complexidade do código ao eliminar as interações indiretas entre suas partes

5. Evite 'Geral'

Métodos e classes não devem ter responsabilidades muito amplas, pois acabam acumulando muita lógica e tornando difícil entender o que está acontecendo.

  • Exemplo: Em vez de classe Geral { ... }, criar subclasses para cada grupo de comportamentos
  • Motivo: Melhora a reutilização do código ao isolá-lo em classes mais específicas

Exemplo real

Em uma aplicação para gestão de frotas de veículos, temos a necessidade de armazenar informações sobre cada veículo. Alguns atributos que precisamos incluir são:

  • Placa
  • Marca
  • Modelo
  • Ano de fabricação

Se adicionar esses campos diretamente à classe existente, podemos acabar com um monolito difícil de manter.

// Não é uma boa prática criar atributos adicionais em classes existentes
public class Veiculo {
    private String placa;
    private String marca;
    private String modelo;
    private int anoFabricacao;

    public void adicionarCampoPlaca(Veiculo veiculo, String placa) {
        this.placa = placa;
    }
}

Nesse exemplo, cada vez que precisamos incluir um novo campo, vamos precisar adicionar mais métodos ao nosso monolito. Isso torna o código muito difícil de manter e reutilizar.

Em vez disso, podemos criar uma subclasse para cada grupo de comportamentos relacionados a atributos do veículo.

// Crie subclasses para representar grupos de comportamentos relacionados aos atributos do veículo
public class VeiculoBasico {
    private String placa;
    private String marca;
}

public class VeiculoCompleto extends VeiculoBasico {
    private String modelo;
    private int anoFabricacao;

    public void adicionarCampoModelo(VeiculoCompleto veiculo, String modelo) {
        this.modelo = modelo;
    }

    // Adicione mais métodos relacionados ao novo campo
}

Dessa forma, podemos criar classes com responsabilidades específicas e isoladas. Isso facilita a reutilização do código e melhora a manutenção.

// Exemplo de uso da subclasse VeiculoCompleto
public class Frotas {
    public void adicionarVeiculo(VeiculoBasico veiculo) { }
    public void adicionarVeiculo(VeiculoCompleto veiculo) { }

    // Cada vez que precisamos incluir um novo campo, podemos criar uma nova subclasse e não ter que alterar as classes existentes
}

Boas práticas

Utilize a técnica de Substituição de Liskov para evitar subclasse desnecessária

  • Isso pode ocorrer quando você precisa adicionar um novo campo a uma classe, mas não consegue encontrar uma maneira de fazer isso sem violar as regras da hierarquia de subclasses.
  • A Substituição de Liskov é uma técnica que permite substituir uma subclasse por sua superclasse em qualquer contexto no qual a superclasse seja esperada.
  • Se você precisar adicionar um novo campo, tente fazer isso na classe mais específica possível, para evitar a criação de subclasses desnecessárias.

Separe responsabilidades e utilize encapsulamento

  • Evite classes com muitos métodos que manipulam atributos diferentes.
  • Cada método deve ter uma única responsabilidade e não manipular mais de um atributo por vez.
  • Isso facilita a manutenção e o entendimento do código.

Utilize herança para compartilhar comportamento, não para compartilhar estado

  • Se você precisa criar subclasses que compartilham apenas o estado (atributos), considere utilizar composição em vez de herança.
  • A herança deve ser usada para compartilhar comportamentos (métodos) entre classes, não para compartilhar atributos.

Armadilhas comuns

Subclasse "gerador" de novos campos

  • Não crie subclasses apenas para adicionar novos campos a uma superclasse.
  • Isso pode levar a um acúmulo de subclasses que são apenas variantes da mesma classe, mas com alguns campos adicionais.
  • Em vez disso, utilize a técnica de Substituição de Liskov e adicione os novos campos diretamente na superclasse.

Over-engineering

  • Não crie hierarquias de classes muito complexas para resolver problemas simples.
  • A sobrecarga de abstração pode levar a código difícil de manter e entender.
  • Priorize a simplicidade e o desempenho no desenvolvimento do seu código.

Conclusão

Os cinco princípios apresentados visam proporcionar código que seja facilmente legível e manejável por humanos. Ao priorizar a substituição de subclasses por suas superclasses, evitar adicionar novos campos em classes incorretas e separar responsabilidades com encapsulamento, é possível reduzir complexidade e aumentar a compreensão do código.

A aplicação destes princípios ajudará a criar soluções mais sustentáveis e escaláveis. Além disso, ao evitar armadilhas como subclasse "gerador" de novos campos e over-engineering, é possível garantir que o código não se torne um problema a ser resolvido em vez de uma solução para os requisitos do negócio.

Para aprofundar seu conhecimento, explore conceitos relacionados, como Design Patterns (Padrões de Projeto) e Clean Architecture, que fornecem ferramentas adicionais para desenvolver código robusto e manejável.

Referências