SOLID: O que significa e como aplicar

SOLID

Se você trabalha com desenvolvimento de software, já deve ter esbarrado no acrônimo SOLID. Ele representa cinco princípios fundamentais da programação orientada a objetos. Esses princípios transformam código caótico em código organizado, testável e fácil de manter. Mas o que cada letra significa na prática? E por que tantos times de engenharia tratam SOLID como regra de ouro?

Neste artigo, você vai entender cada princípio com exemplos reais. Vai ver como eles se conectam com o dia a dia de quem constrói APIs escaláveis. E vai sair com uma visão clara de como aplicar tudo nos seus projetos.

O que é SOLID?

A origem dos cinco princípios

Robert C. Martin, o famoso Uncle Bob, reuniu esses cinco princípios no início dos anos 2000. O objetivo era simples: criar diretrizes que ajudassem desenvolvedores a escrever código mais robusto. O acrônimo SOLID foi popularizado por Michael Feathers, que percebeu que as iniciais formavam uma palavra poderosa.

Cada letra representa um princípio:

  • S — Single Responsibility Principle (Princípio da Responsabilidade Única)
  • O — Open/Closed Principle (Princípio Aberto/Fechado)
  • L — Liskov Substitution Principle (Princípio da Substituição de Liskov)
  • I — Interface Segregation Principle (Princípio da Segregação de Interfaces)
  • D — Dependency Inversion Principle (Princípio da Inversão de Dependência)

Esses princípios não são regras rígidas gravadas em pedra. Eles funcionam como guias que ajudam a tomar decisões melhores durante o design de software. Quando você aplica SOLID, seu código fica mais modular, mais fácil de testar e muito mais simples de evoluir.

Por que SOLID importa em projetos reais

Imagine que você mantém uma API de consulta de CPF. No começo, o código é pequeno e funciona bem. Porém, conforme novos endpoints surgem, consulta de CNPJ, validação de CEP, integração com outros bancos, o código vira uma bola de neve.

Sem princípios claros de organização, cada nova feature quebra algo que já funcionava. O tempo de deploy aumenta. Testes ficam frágeis. Novos desenvolvedores no time demoram semanas para entender o que acontece.

SOLID resolve exatamente esses problemas. Ele cria uma estrutura onde:

  • Cada classe faz apenas uma coisa
  • Novas funcionalidades não exigem reescrever o que já existe
  • Componentes podem ser substituídos sem efeitos colaterais
  • Interfaces são enxutas e específicas
  • Módulos de alto nível não dependem de detalhes de implementação

Você não precisa aplicar todos os princípios SOLID de uma vez. Comece pelo Princípio da Responsabilidade Única, ele sozinho já transforma a qualidade do seu código de forma visível.

A diferença deste guia é que todos os exemplos usam cenários reais de APIs e sistemas do dia a dia, nada de “Animal” ou “Forma Geométrica”. Cada princípio é demonstrado com código que você encontraria em projetos de produção.

S — Single Responsibility Principle na prática

Single Responsibility Principle
Single Responsibility Principle

O Princípio da Responsabilidade Única diz que uma classe deve ter apenas um motivo para ser alterada. Traduzindo: cada classe deve cuidar de uma única responsabilidade.

Parece simples, mas é o princípio que desenvolvedores mais violam.

Essa classe faz seis coisas diferentes. Qualquer mudança no formato do log, na conexão com o banco, na validação, obriga você a mexer nela. O risco de introduzir bugs cresce a cada alteração.

Como refatorar seguindo o princípio

A solução é separar cada responsabilidade em sua própria classe:

  • ValidadorCPF — cuida apenas da validação de formato
  • RepositorioCPF — gerencia a comunicação com o banco de dados
  • FormatadorResposta — transforma dados em JSON
  • LoggerConsulta — registra logs
  • NotificadorEmail — envia notificações

Agora, se o formato do log mudar, você altera apenas LoggerConsulta. Se a conexão com o banco precisar migrar para outro provedor, só RepositorioCPF é afetado.

Benefícios práticos do SRP

Quando cada classe tem uma única responsabilidade, você ganha:

  • Testes unitários precisos — cada teste cobre uma funcionalidade isolada
  • Reutilização real — o Validador CPF pode ser usado em qualquer parte do sistema
  • Deploys mais seguros — mudanças têm escopo limitado e previsível
  • Onboarding rápido — novos devs entendem o que cada classe faz em segundos

Esse princípio do SOLID é a base sobre a qual todos os outros se apoiam. Se você dominar apenas este, já estará muito à frente da maioria dos desenvolvedores.

O e L — Open/Closed e Liskov Substitution

Open/Closed
Open/Closed

Com a responsabilidade única dominada, o próximo passo é garantir que seu código evolua sem risco. Os princípios Open/Closed e Liskov trabalham juntos nesse objetivo. O primeiro define como adicionar funcionalidades novas. O segundo garante que essas adições respeitem o comportamento esperado.

Open/Closed: estender sem modificar

O segundo princípio SOLID diz que as classes devem estar abertas para extensão, mas fechadas para modificação. Na prática: você deve conseguir adicionar comportamentos novos sem alterar o código que já funciona.

Pense numa API que precisa suportar múltiplos formatos de resposta.

Cada novo formato exige adicionar um elif. O arquivo cresce sem parar, e qualquer erro num formato novo pode quebrar os que já funcionam.

Agora, para suportar CSV, você cria FormatadorCSV sem tocar nos formatadores existentes. O código antigo permanece estável.

Liskov Substitution: substituição sem surpresas

O terceiro princípio SOLID foi definido por Barbara Liskov. Ele estabelece que objetos de uma classe derivada devem substituir objetos da classe base sem quebrar o sistema.

Em termos simples: se seu código espera um Formatador, qualquer subclasse de Formatador deve funcionar perfeitamente no lugar dele.

Uma violação clássica acontece quando uma subclasse lança exceções inesperadas ou retornam tipos diferentes.

Quem usa Formatador espera uma string. FormatadorProtobuf retorna bytes. Isso quebra o contrato e causa bugs difíceis de rastrear.

A correção é garantir que toda subclasse respeite o mesmo contrato — mesmos tipos de entrada e saída, mesmas exceções documentadas, sem efeitos colaterais inesperados.

Princípio Pergunta-chave Sinal de violação
Single Responsibility “Esta classe tem mais de um motivo para mudar?” Classe com muitos métodos não relacionados
Open/Closed “Preciso modificar código existente para adicionar algo novo?” Cadeia de if/elif crescente
Liskov Substitution “Posso trocar a classe base pela derivada sem medo?” Subclasses que lançam exceções inesperadas
Interface Segregation “Minha classe implementa métodos que não usa?” Métodos vazios ou com pass
Dependency Inversion “Meu módulo depende de implementações concretas?” import direto de classes específicas

Combinando O e L no design de APIs

Design de APIs
Design de APIs

Quando você constrói uma API de consulta de documentos, o princípio Open/Closed permite adicionar novos tipos (RG, CNH, passaporte) sem alterar o fluxo principal. Já o princípio de Liskov garante que qualquer tipo funcione de maneira uniforme no pipeline de validação e resposta.

Essa combinação produz sistemas verdadeiramente extensíveis. Novos clientes pedem suporte a um novo documento? Basta criar uma nova classe que respeite o contrato existente. Zero alterações no código de produção.

Violações do princípio de Liskov costumam aparecer tarde — geralmente em produção, quando um caso raro dispara o comportamento inesperado da subclasse. Escreva testes específicos para verificar que cada subclasse respeita o contrato da classe base.

I e D: Interface Segregation e Dependency Inversion

Se Open/Closed e Liskov cuidam de como suas classes evoluem, os dois últimos princípios cuidam de como elas se comunicam. Interface Segregation define o que cada componente deve expor. Dependency Inversion define como os componentes se conectam entre si.

Interface Segregation: interfaces enxutas e específicas

O quarto princípio SOLID defende que nenhuma classe deve ser forçada a implementar métodos que não utiliza. Interfaces gordas são inimigas da manutenibilidade.

Se você precisa de uma classe que só consulta CPF, ela seria forçada a implementar enviar_email, gerar_relatorio_pdf e conectar_webhook — métodos completamente irrelevantes para sua função.

A solução é dividir essa interface em partes menores e coesas.

Agora cada classe implementa apenas o que realmente precisa. O código fica mais leve, mais testável e mais fácil de entender.

Dependency Inversion: dependa de abstrações

Dependency Inversion
Dependency Inversion

Para encerrar, o quinto e último princípio SOLID determina que módulos de alto nível não devem depender de módulos de baixo nível, mas sim que ambos devem depender de abstrações. Traduzindo isso para o mundo das APIs, significa que a sua lógica de negócio não deve conhecer os detalhes do banco de dados, tampouco saber qual provedor de cacheou serviço de fila está sendo utilizado.

No exemplo anterior, além do forte acoplamento, o código concatenava o CNPJ direto na query SQL, gerando uma grave vulnerabilidade de SQL injection. Por outro lado, com a inversão de dependência, o repositório cuida da query de forma totalmente segura, de modo que o ServicoConsulta sequer precisa saber que o SQL existe.

Consequentemente, se amanhã você precisar trocar o PostgreSQL pelo MongoDB, ou até mesmo o Redis pelo Memcached, não terá que reescrever o ServicoConsulta inteiro. Afinal, agora ele depende exclusivamente de abstrações (Repositorio e Cache). Sendo assim, você pode injetar qualquer implementação sem alterar uma única linha da lógica de negócio.

Como Dependency Inversion transforma seus testes

Por um lado, sem a adoção da inversão de dependência, testar o ServicoConsulta exige, obrigatoriamente, que haja um banco de dados real rodando. Por outro lado, com a inversão devidamente aplicada, você passa a injetar mocks, ou seja, objetos que simulam perfeitamente o comportamento dos componentes reais, mas sem se conectar a absolutamente nada externo.

Como resultado direto dessa prática, você obtém testes muito mais rápidos, totalmente livres de dependências externas e que são executáveis em qualquer ambiente. Consequentemente, essa abordagem não apenas acelera o ciclo de desenvolvimento da equipe, como também reduz de forma drástica a incidência de bugs no ambiente de produção.

Cenário Sem SOLID Com SOLID
Adicionar novo formato de resposta Modificar classe existente com novo elif Criar nova classe sem tocar no código atual
Trocar banco de dados Reescrever toda a camada de dados Criar novo adapter e injetar via construtor
Testar lógica de negócio Precisa de banco, cache e fila rodando Usa mocks e roda em milissegundos
Novo dev entendendo o código Classe com 500 linhas e 12 responsabilidades Classes pequenas com nomes autoexplicativos
Escalar o time Devs pisam no código um do outro Cada dev trabalha em módulos independentes
Adicionar suporte a novo documento Alterar fluxo principal e torcer para não quebrar Criar nova classe que implementa a interface

Como aplicar SOLID no dia a dia do desenvolvimento

Como aplicar SOLID
Como aplicar SOLID

Começando pelo código que já existe

Em primeiro lugar, aplicar o SOLID não significa, de forma alguma, reescrever todo o seu projeto do zero. Na verdade, o caminho mais inteligente é sempre adotar uma refatoração progressiva. Para começar, identifique as partes do código que mais causam problemas — ou seja, aquelas que quebram com frequência e nas quais a equipe evita mexer. Feito isso, aplique os princípios diretamente nelas primeiro.

Nesse sentido, uma técnica extremamente eficaz é o “teste do motivo de mudança”. Basicamente, para cada classe do seu sistema, pergunte-se: “Quantos motivos diferentes podem me levar a alterar essa estrutura?”. Se, por acaso, a resposta for mais de um, fica claro que a classe viola o Princípio da Responsabilidade Única (SRP) e, portanto, merece ser dividida imediatamente.

Além disso, outra abordagem muito poderosa consiste em revisar detalhadamente as suas importações. Afinal de contas, se uma única classe importa bibliotecas de banco de dados, envio de e-mail, geração de PDF e parsing de XML simultaneamente, é altamente provável que ela esteja acumulando funções demais e precise de uma intervenção estrutural.

Ferramentas que ajudam a manter SOLID

Felizmente, linters e analisadores estáticos podem identificar violações de SOLID de forma totalmente automática. Para começar, o SonarQube detecta classes com complexidade ciclomática alta, ou seja, aponta o excesso de condicionais (if, for e switch) que indicam responsabilidades acumuladas.

Em paralelo, ferramentas como Pylint e ESLint, quando equipadas com plugins adequados, revelam o acoplamento excessivo. Já o Code Climate mede a manutenibilidade e destaca code smells. Por sua vez, opções como Archunit (Java) e pytest-archon (Python) permitem criar regras de arquitetura diretamente como testes automatizados.

No entanto, muito além das ferramentas, as práticas de equipe são absolutamente essenciais para reforçar a aplicação do SOLID. Por exemplo, durante os code reviews focados em design, os revisores devem sempre questionar se a classe possui uma responsabilidade única.

Adicionalmente, o pair programming garante que dois pares de olhos capturem violações muito mais cedo. Por fim, o uso de Architecture Decision Records (ADRs) documenta as razões por trás de cada design escolhido, enquanto os testes de contrato garantem, de forma definitiva, que as subclasses respeitem fielmente o princípio de Liskov.

Padrões de projeto que complementam SOLID

Padrões de projeto que complementam SOLID
Padrões de projeto que complementam SOLID

Adicionalmente, os princípios SOLID ganham uma força extra quando combinados com padrões de projeto consagrados. Por exemplo, o Strategy Pattern encapsula algoritmos em classes separadas, sendo uma implementação natural do Open/Closed. Em paralelo, o Factory Pattern centraliza a criação de objetos e, consequentemente, facilita a inversão de dependência.

Já o Repository Pattern separa a lógica de dados da de negócio, representando o SRP puro. Além disso, o Adapter Pattern permite a integração de interfaces incompatíveis, respeitando o ISP, enquanto o Observer Pattern desacopla quem emite eventos de quem reage a eles.

De fato, quase todos esses padrões se fundamentam em, pelo menos, um princípio SOLID. Portanto, dominar ambos eleva drasticamente a sua capacidade de projetar sistemas escaláveis.

Como resultado prático, times que adotam essas práticas de forma consistente observam reduções significativas de bugs em produção. Mais ainda, aceleram o onboarding de novos desenvolvedores, visto que um código modular com responsabilidades claras é muito mais fácil de entender, testar e manter. No fim das contas, esse impacto positivo se acumula sprint após sprint.

Quando SOLID pode ser exagero

Em primeiro lugar, é fundamental entender que nem todo projeto precisa de cinco camadas de abstração. Por exemplo, se estamos falando de um script que roda apenas uma vez por mês, aplicar todas essas regras resultará em puro over engineering. Da mesma forma, caso se trate de um protótipo com prazo de uma semana, a melhor decisão éfocar exclusivamente na velocidade de entrega.

Por outro lado, o SOLID brilha verdadeiramente em projetos que envolvem múltiplos desenvolvedores, manutenção de longo prazo e mudanças frequentes de requisitos. Sendo assim, para um código descartável, o pragmatismo vence facilmente o purismo técnico. No entanto, quando o software precisa durar e escalar ao longo do tempo, a adoção do SOLID deixa de ser uma simples escolha e vira uma absoluta necessidade.

SOLID em diferentes linguagens

Historicamente, os princípios SOLID nasceram no contexto de linguagens puramente orientadas a objetos, como Java e C++. Apesar disso, eles se adaptam perfeitamente a qualquer linguagem moderna. Por exemplo, o Python utiliza classes abstratas (ABC) e duck typing. Em contrapartida, o TypeScript traz interfaces explícitas e generics, os quais facilitam imensamente a aplicação do princípio Open/Closed. Enquanto isso, Go trabalha de forma fluida com interfaces implícitas e, por sua vez, Rust usa traits para definir comportamentos compartilhados.

Surpreendentemente, até mesmo as linguagens funcionais aplicam os conceitos que fundamentam o SOLID. Nesse paradigma, por exemplo, funções puras e composição representam a versão funcional do SRP. Da mesma forma, as higher-order functions implementam de maneira totalmente natural o Open/Closed. Em suma, independentemente da tecnologia escolhida, o ponto central permanece rigorosamente o mesmo: separar responsabilidades, depender sempre de abstrações e, acima de tudo, projetar contratos claros.

Erros comuns e checklist de implementação SOLID

Os 5 erros mais frequentes

Geralmente, desenvolvedores que estão começando com os princípios SOLID costumam cair nas mesmas armadilhas. Contudo, conhecê-las antecipadamente poupa horas de refatoração. Primeiramente, o erro mais comum é a fragmentação excessiva. Afinal, SRP não significa ter uma classe por método, mas sim uma por motivo de mudança. Portanto, se dois métodos mudam pelo mesmo motivo, devem permanecer juntos.

Em seguida, destacam-se as abstrações prematuras, ou seja, criar interfaces para componentes de implementação única. Por isso, abstraia apenas quando a necessidade real surgir. Além disso, é um equívoco ignorar o contexto do projeto. Por exemplo, aplicar SOLID com rigor máximo em um MVP de duas semanas apenas atrasa a entrega; logo, calibre a intensidade conforme a longevidade do sistema.

Paralelamente, muitos confundem ISP com SRP. Enquanto o ISP foca no que você expõe externamente, o SRP foca estritamente nas responsabilidades internas. Por fim, o DIP sem injeção real é uma falha clássica. Sendo assim, instanciar objetos concretos diretamente no construtor não é uma inversão de verdade, visto que a dependência precisa, obrigatoriamente, ser injetada de fora.

Roadmap de aprendizado SOLID

Roadmap de aprendizado SOLID
Roadmap de aprendizado SOLID

Para dominar o SOLID em 12 semanas, siga esta sequência prática e contínua. Primeiramente, nas semanas 1 e 2, pratique o SRP refatorando uma classe real. Em seguida, nas semanas 3 e 4, substitua blocos if/elif crescentes por polimorfismo, aplicando o princípio Open/Closed. Posteriormente, entre as semanas 5 e 6, garanta o princípio de Liskov escrevendo testes de contrato para subclasses. Logo após, nas semanas 7 e 8, quebre interfaces genéricas em específicas (ISP).

Na sequência, nas semanas 9 e 10, refatore dependências utilizando a injeção via construtor (DIP). Por fim, nas duas últimas semanas, consolide o aprendizado projetando um módulo inteiramente novo aplicando todos os conceitos juntos.

Em resumo, esses cinco princípios formam a base absoluta de qualquer arquitetura de software duradoura. Portanto, aplique-os progressivamente para obter resultados robustos.

Sendo assim, você está pronto para construir sistemas com a melhor engenharia? Nesse sentido, o Hub do Desenvolvedor é o seu ponto de partida ideal. Afinal, nossas APIs de CPF, CNPJ e CEP possuem arquitetura limpa e endpoints intuitivos. Então, comece agora a integrar dados confiáveis e comprove na prática a diferença que essas regras fazem.

Compartilhe nas mídias:

Obtenha Acesso Imediato a todos WebServices!

Tenha acessos a todos os dados e API de WS.

Destaques: