DEV Community

Cover image for SOLID: Os Princípios que Você Acha que Domina (e os Erros que Comete)
Bruno S Freschi
Bruno S Freschi

Posted on

SOLID: Os Princípios que Você Acha que Domina (e os Erros que Comete)

Se você é desenvolvedor de software, é quase certo que já ouviu falar dos princípios SOLID. As cinco letras que representam os pilares do bom design orientado a objetos são um mantra em entrevistas técnicas e discussões de arquitetura. Mas há uma grande diferença entre saber recitar o que cada letra significa e aplicar esses conceitos de forma eficaz no calor de um projeto real.

Saber a definição é o primeiro passo, mas a maestria vem de entender as nuances e as armadilhas. O propósito deste artigo é ir além do "o que é" para focar no "como não errar". Vamos explorar os erros mais comuns e as interpretações equivocadas na aplicação de cada um dos cinco princípios SOLID, usando como base os desafios práticos que encontramos no dia a dia do desenvolvimento.

  1. O SRP e o Perigo de Fragmentar Demais seu Sistema

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

O Princípio da Responsabilidade Única foi criado para combater as infames "Classes 'God Object'", aquelas classes monolíticas que fazem de tudo: validam regras de negócio, acessam o banco de dados, enviam e-mails e registram logs. Esse tipo de estrutura gera um acoplamento altíssimo, tornando a manutenção e os testes unitários um verdadeiro pesadelo.

O erro comum, no entanto, está no extremo oposto: aplicar o SRP de forma excessivamente granular. A tentativa de dar a cada método sua própria classe leva a um sistema que, embora tecnicamente desacoplado, torna-se fragmentado. O desenvolvedor se vê perdido em um labirinto de arquivos minúsculos, caçando a lógica que deveria estar unificada, e a coesão se perde para entender um único fluxo de negócio.

  1. O OCP e a Tentativa Inútil de Prever o Futuro

Entidades de software devem estar abertas para extensão, mas fechadas para modificação.

O OCP resolve um problema clássico: a necessidade de alterar o código-fonte de uma classe já existente e estável toda vez que um novo requisito surge. Adicionar blocos if/else ou switch para cada nova regra (como um novo tipo de desconto) introduz o risco de quebrar funcionalidades que já estavam funcionando perfeitamente.

O erro comum aqui é o Overengineering. Na ânsia de seguir o princípio, muitos desenvolvedores tentam prever todas as extensões futuras possíveis, criando abstrações complexas e camadas de indireção antes que elas sejam realmente necessárias. Essa busca por um design "perfeito" e à prova de futuro pode levar a uma complexidade desnecessária que atrasa o desenvolvimento e dificulta a compreensão do código, tornando o sistema mais frágil, e não mais robusto. Isso acontece porque essas abstrações prematuras são baseadas em suposições sobre o futuro que quase sempre se provam erradas, resultando em um design complexo de usar e ainda mais difícil de alterar quando os requisitos reais finalmente chegam.

  1. O LSP e o Equívoco de que Herança é Apenas sobre 'É um'

Subclasses devem ser substituíveis por suas classes base sem alterar o comportamento esperado do programa.

O LSP aborda as heranças "forçadas", onde uma classe filha herda de uma classe pai, mas não consegue cumprir todo o contrato de comportamento esperado. O exemplo clássico é um RubberDuck (Pato de Borracha) que herda de Duck (Pato) e é forçado a lançar uma exceção no método Fly(), quebrando a expectativa de que qualquer objeto do tipo Duck pode voar.

O erro comum e crucial é pensar na herança apenas como uma relação taxonômica ("é um") e ignorar o contrato de comportamento. Um pato de borracha "é um" tipo de pato, mas ele não se "comporta" como um pato real. Violar o LSP quebra a confiança no polimorfismo, forçando verificações de tipo e levando a erros inesperados em tempo de execução, justamente o que a abstração deveria evitar.

  1. O ISP e o Falso Dilema entre Interfaces 'Gordas' e Interfaces Excessivas

Clientes não devem ser forçados a depender de interfaces que não utilizam.

O ISP combate as "interfaces gordas" — interfaces que possuem tantos métodos que nenhuma classe consegue implementar todos de forma significativa. Isso força os implementadores a lançar NotImplementedException ou a criar implementações vazias para métodos que não fazem sentido em seu contexto, como uma EconomicPrinter sendo obrigada a ter um método Fax().

A armadilha comum é ir para o outro extremo: criar uma interface para cada classe. Essa abordagem, conhecida como "Interface per Class", polui o código com uma infinidade de abstrações que não trazem nenhum benefício real de desacoplamento, pois cada interface tem apenas um único implementador. O equilíbrio que o ISP busca é a criação de interfaces coesas. A mudança de mentalidade fundamental aqui é projetar interfaces da perspectiva do cliente que as consome, e não da classe que as implementa. Isso garante que os contratos sejam enxutos, relevantes e verdadeiramente úteis.

  1. O DIP e a Confusão Comum entre Princípio, Padrão e Contêiner

Dependa de abstrações, não de implementações. Módulos de alto nível não devem depender de módulos de baixo nível.

O DIP resolve o problema do acoplamento rígido a detalhes de infraestrutura. Quando uma classe de regra de negócio instancia diretamente uma implementação concreta, como um SqlServerRepository, o código fica "preso" àquela tecnologia. Isso dificulta a troca de fornecedores (por exemplo, mover para um banco de dados diferente) e complica a criação de testes unitários com mocks.

O erro mais frequente é confundir os conceitos relacionados. É fundamental entender a distinção:

O DIP é a estratégia; o DI é a técnica.

O Princípio da Inversão de Dependência (DIP) é a estratégia de alto nível que diz que seu código de negócio não deve depender de detalhes. A Injeção de Dependência (DI) é uma das técnicas (ou padrões) para implementar essa estratégia, geralmente passando as dependências através de um construtor. Contêineres de Inversão de Controle (IoC) são ferramentas que automatizam o processo de DI. Confundir esses três conceitos pode levar a uma aplicação incorreta do princípio.

Conclusão: O Código que Sobrevive ao Amanhã

Dominar os princípios SOLID não é sobre seguir um checklist de regras de forma cega. É sobre entender a intenção por trás de cada princípio e buscar o equilíbrio. Trata-se de saber quando uma abstração é necessária e quando ela é um excesso, quando uma classe deve ser dividida e quando ela está coesa o suficiente.

Como a sabedoria do desenvolvimento de software nos lembra: "Dominar o SOLID é a diferença entre escrever código que funciona hoje e código que sobrevive ao amanhã."

Qual desses erros você já viu (ou cometeu) em um projeto? Compartilhe sua experiência nos comentários!

Top comments (0)