
S.O.L.I.D.: O Guia Definitivo para Escrever Código que Não Apodrece
[!NOTE] Princípios, Não Dogmas: S.O.L.I.D. são diretrizes, não regras invioláveis. Use-os como ferramentas de design, não como checklist burocrático.
"Código legado" não é apenas código velho. É código que você tem medo de tocar. É aquela função de 500 linhas que, se você mudar uma vírgula, o sistema de faturamento para de funcionar.
A "podridão de software" (software rot) acontece quando a arquitetura se degrada ao longo do tempo. Dependências se cruzam, responsabilidades se misturam e o código se torna rígido (difícil de mudar) e frágil (fácil de quebrar).
Para combater isso, Robert C. Martin (o famoso "Uncle Bob") compilou 5 princípios fundamentais de design orientado a objetos no início dos anos 2000. O acrônimo S.O.L.I.D. se tornou a base da Engenharia de Software moderna.
Se você quer sair do nível "Programador" e chegar ao nível "Engenheiro", você precisa dominar isso. Não é teoria acadêmica; é ferramenta de sobrevivência.
S - Single Responsibility Principle (SRP)
"Uma classe deve ter apenas um, e somente um, motivo para mudar."
Muitas pessoas acham que SRP significa "fazer apenas uma coisa". Não. Significa que a classe deve atender a apenas um ator do negócio.
O Jeito Errado
class Usuario {
calcularSalario() {
// Regra do RH
}
gerarRelatorioHoras() {
// Regra do Gerente de Projetos
}
salvarNoBanco() {
// Regra do DBA
}
}Aqui, a classe Usuario muda se o RH mudar a regra de salário, OU se o DBA mudar o banco de dados. Isso é acoplamento.
O Jeito Certo (SRP)
class CalculadoraDeSalario {
calcular(usuario: Usuario) { ... }
}
class RepositorioDeUsuario {
salvar(usuario: Usuario) { ... }
}
class Usuario {
// Apenas dados e comportamentos essenciais do domínio
}O - Open/Closed Principle (OCP)
"Entidades de software devem estar abertas para extensão, mas fechadas para modificação."
Você deve ser capaz de adicionar novas funcionalidades sem tocar no código existente (e arriscar introduzir novos bugs).
O Jeito Errado
class ProcessadorPagamento {
processar(pagamento: any) {
if (pagamento.tipo === 'BOLETO') {
// processa boleto
} else if (pagamento.tipo === 'CARTAO') {
// processa cartao
}
}
}Se quisermos adicionar "PIX", temos que abrir e modificar essa classe. Violação do OCP.
O Jeito Certo
Use Polimorfismo!
interface MetodoPagamento {
pagar(): void;
}
class Boleto implements MetodoPagamento {
pagar() { ... }
}
class Pix implements MetodoPagamento {
pagar() { ... }
}
class ProcessadorPagamento {
processar(metodo: MetodoPagamento) {
metodo.pagar(); // Não sabe se é Pix ou Boleto, e não importa!
}
}L - Liskov Substitution Principle (LSP)
"Subclasses devem ser substituíveis por suas superclasses."
Se parece um pato e faz "quack" como um pato, mas precisa de pilhas para funcionar, você tem uma abstração errada.
O Exemplo Clássico (O Quadrado que não é Retângulo)
Matematicamente, um quadrado é um retângulo. Em programação, não.
class Retangulo {
setAltura(h) { this.altura = h; }
setLargura(w) { this.largura = w; }
}
class Quadrado extends Retangulo {
setAltura(h) {
this.altura = h;
this.largura = h; // Força ser quadrado
}
}Se você tem uma função que espera um Retângulo e recebe um Quadrado, ao mudar a altura, a largura muda "magicamente". Isso quebra a expectativa do código cliente e viola o LSP.
I - Interface Segregation Principle (ISP)
"Muitas interfaces específicas são melhores do que uma interface única geral."
Não force uma classe a implementar métodos que ela não usa.
O Jeito Errado
interface Trabalhador {
trabalhar(): void;
comer(): void;
}
class Robo implements Trabalhador {
trabalhar() { ... }
comer() {
throw new Error("Robôs não comem!"); // Violação!
}
}O Jeito Certo
Quebre as interfaces.
interface Trabalhador { trabalhar(): void; }
interface SerVivo { comer(): void; }
class Humano implements Trabalhador, SerVivo { ... }
class Robo implements Trabalhador { ... }D - Dependency Inversion Principle (DIP)
"Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações."
Este é o princípio mais importante para desacoplar sua arquitetura. Seu código de negócio (O "Core") não pode depender do MySQL ou da API do Stripe.
O Jeito Errado (Alto acoplamento)
class ServicoDePedidos {
constructor() {
this.banco = new MySQLDatabase(); // Dependência direta ("new") na implementação concreta
}
}Agora é impossível testar ServicoDePedidos sem ter um banco MySQL rodando.
O Jeito Certo (DIP com Injeção de Dependência)
interface BancoDeDados {
salvar(pedido: Pedido): void;
}
class MySQLDatabase implements BancoDeDados { ... }
class PostgreSQLDatabase implements BancoDeDados { ... }
class ServicoDePedidos {
constructor(private banco: BancoDeDados) { // Depende da ABSTRAÇÃO (Interface)
}
}Agora você pode injetar o MySQL em produção e um MockDatabase em memória nos testes unitários.
Conclusão
Aplicar S.O.L.I.D. exige mais digitação no início? Sim. Você cria mais arquivos e interfaces. Mas o software não é escrito apenas uma vez; ele é lido e modificado milhares de vezes. O tempo "extra" gasto criando uma arquitetura desacoplada paga dividendos enormes na manutenção futura.
Glossário Técnico
- SRP (Single Responsibility Principle): Uma classe deve ter apenas um motivo para mudar.
- OCP (Open/Closed Principle): Código aberto para extensão, fechado para modificação.
- LSP (Liskov Substitution Principle): Subtipos devem ser substituíveis pelos seus tipos base.
- ISP (Interface Segregation Principle): Prefira interfaces específicas a interfaces genéricas.
- DIP (Dependency Inversion Principle): Dependa de abstrações (interfaces), não de implementações concretas.
Referências
- Robert C. Martin. Clean Architecture: A Craftsman's Guide. O livro definitivo.
- Uncle Bob. The Principles of OOD. Website original.
- Martin Fowler. Refactoring. Técnicas complementares.
