Desmistificando o SOLID: Os 5 Princípios para um Código Limpo
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.