Desmistificando o SOLID: Os 5 Princípios para um Código Limpo

andresn
: ~/blog/
$

Se você já teve que mexer em um código onde alterar uma simples linha fazia outras cinco partes do sistema quebrarem misteriosamente, você sentiu na pele a falta do SOLID.

SOLID é um acrônimo para cinco princípios de design de software orientado a objetos, popularizados por Robert C. Martin (o Uncle Bob) no início dos anos 2000. O objetivo deles é simples: ajudar desenvolvedores a criarem códigos que sejam fáceis de manter, fáceis de estender e fáceis de entender à medida que o projeto cresce.

Vamos entender o que significa cada uma dessas letras com exemplos práticos.


S — Single Responsibility Principle (Princípio da Responsabilidade Única)

“Uma classe deve ter um, e apenas um, motivo para mudar.”

Uma classe deve fazer apenas uma coisa e fazê-la bem. Se a sua classe lida com regras de negócio, envia e-mails e salva dados no banco, ela tem responsabilidades demais.

❌ Errado:

public class Pedido {
    public void Processar() { /* ... */ }
    public void SalvarNoBanco() { /* ... */ } // Responsabilidade de persistência
    public void EnviarEmailConfirmacao() { /* ... */ } // Responsabilidade de notificação
}

Certo:

O correto é separar essas tarefas em classes distintas (ex: Pedido, PedidoRepository e EmailService). Se o formato do e-mail mudar, a classe Pedido não precisa ser tocada.

O — Open/Closed Principle (Princípio Aberto/Fechado)

“Objetos ou entidades devem estar abertos para extensão, mas fechados para modificação.”

Você deve ser capaz de adicionar novos recursos ao sistema sem precisar alterar o código que já está escrito e funcionando.

❌ Errado:

Se você precisa adicionar um novo método de pagamento (como Pix) e precisa abrir a sua classe ProcessadorDePagamento para colocar um novo if ou switch, você violou este princípio.

Certo:

Crie uma interface IPagamento e faça com que Cartao, Boleto e Pix a implementem. O processador principal apenas recebe o contrato IPagamento, sem se importar com o tipo específico. Para adicionar um método novo, basta criar uma nova classe.

L — Liskov Substitution Principle (Princípio da Substituição de Liskov)

“Uma classe filha deve poder ser substituída por sua classe pai sem quebrar a aplicação.”

Se a classe B herda da classe A, nós devemos conseguir usar B no lugar de A em qualquer parte do código sem que o sistema mude de comportamento ou lance exceções inesperadas.

❌ Errado:

O exemplo clássico: criar uma classe Pinguim que herda de Ave. Se a classe Ave tiver o método Voar(), o Pinguim será forçado a lançar um NotImplementedException, quebrando o princípio.

Certo:

Nem toda ave voa. A herança deve ser repensada. Poderíamos ter uma classe Ave e uma subclasse AveQueVoa que adiciona o comportamento de voo.

I — Interface Segregation Principle (Princípio da Segregação de Interface)

“Uma classe não deve ser forçada a depender de interfaces que não utiliza.”

É melhor ter várias interfaces específicas e pequenas do que uma interface gigantesca e genérica (“interface gorda”).

❌ Errado:

Criar uma interface IDocumento com os métodos Ler(), Gravar(), Imprimir() e Grampear(). Se você criar uma classe PdfReadonly, será obrigado a implementar o método Gravar() e Grampear(), mesmo que eles não façam sentido ali.

Certo:

Divida a interface gigante em interfaces menores: ILeitor, IGravador, IImprimivel. Sua classe implementa apenas o que ela realmente precisa.

D — Dependency Inversion Principle (Princípio da Inversão de Dependência)

“Dependa de abstrações e não de implementações concretas.”

Módulos de alto nível (a lógica de negócio) não devem depender de módulos de baixo nível (infraestrutura, banco de dados). Ambos devem depender de abstrações (interfaces).

❌ Errado:

public class Carro {
    private MotorV8 _motor = new MotorV8(); // Acoplamento total com uma implementação concreta
}

Certo:

public class Carro {
    private IMotor _motor;
    
    public Carro(IMotor motor) { // O carro aceita qualquer motor que siga o contrato
        _motor = motor;
    }
}

Conclusão

Seguir o SOLID pode parecer dar “voltas desnecessárias” no código no começo, gerando mais arquivos e interfaces. No entanto, o ganho de longo prazo em projetos comerciais e de larga escala é imensurável. O código se torna modular, as alterações de requisitos ficam menos traumáticas e aplicar testes unitários vira uma tarefa simples.