SOLID em xeque

TL;DR

SOLID não é o objetivo — é um conjunto de heurísticas a serviço de um objetivo maior: baixo acoplamento e alta coesão para gerenciar complexidade. Aplicado no piloto automático, ele se vira contra você: interfaces com uma só implementação “pro futuro” (over-engineering), dezenas de classes minúsculas e rasas (a classitis de Ousterhout), indireção que ninguém pediu. O senior demonstra julgamento, não recitação: aplica a regra quando ela paga o custo, e a ignora quando ela piora o código.

Aprendemos os cinco princípios um a um — SRP, OCP, LSP, ISP, DIP — e cada um, sozinho, faz sentido. Esta nota é o contraponto. Porque existe um modo de errar que nenhum dos cinco te avisa: acertar todos eles e ainda assim produzir um código pior.

Como assim? A pergunta retórica que abre o capstone é simples: e se obedecer SOLID à risca te levar para o lugar errado? Spoiler: leva, e com frequência. Vamos ver onde, por quê, e o que um senior responde quando o entrevistador cutuca essa ferida.

Quando SOLID atrapalha

Over-engineering: a interface fantasma

O pecado número um é abstrair cedo demais. Você cria uma interface PagamentoGateway com uma única implementação StripeGateway, “porque um dia pode ter outro provedor”. Esse dia quase nunca chega — e enquanto não chega, todo mundo paga o imposto: mais um arquivo, mais uma camada para atravessar ao depurar, um nome a mais para inventar.

// YAGNI: interface com UMA implementação, "pro futuro que não veio"
public interface PagamentoGateway { void cobrar(Pedido p); }
public class StripeGateway implements PagamentoGateway { /* única impl */ }

A regra de ouro vem do YAGNI (You Aren’t Gonna Need It): abstraia quando o segundo caso real aparece, não antes. Repare que isso é o reverso da leitura preguiçosa do OCP. OCP diz “deixe extensível para o que varia” — não “deixe extensível para tudo”. O ponto de extensão custa caro; só vale quando há variação real, não imaginada. Antecipar variação que nunca vem é speculative generality — um code smell clássico, não uma virtude.

Classitis: quando SRP e ISP fragmentam demais

Aqui mora a crítica mais afiada, e ela vem de fora da igreja do SOLID. John Ousterhout, em A Philosophy of Software Design, batiza de classitis a crença de que “se classes são boas, mais classes são melhores”. O resultado de levar SRP e ISP ao pé da letra: você estilhaça o sistema em dezenas de classes minúsculas, cada uma fazendo quase nada.

E qual o problema? Cada classe é simples — mas o sistema fica mais complexo. A lógica que antes morava num lugar agora está espalhada por sete arquivos, e para entender uma única operação você salta de um para o outro. A carga cognitiva sobe.

Ousterhout opõe a isso o conceito de módulo profundo (deep module): muita funcionalidade escondida atrás de uma interface simples. O oposto é o módulo raso (shallow module) — interface grande em relação ao que ele de fato faz. O exemplo dele: classes decorator tendem a ser rasas, cheias de métodos pass-through que só repassam a chamada e agregam pouco.

flowchart LR
    subgraph raso["CLASSITIS (modulos rasos)"]
        direction TB
        C1["ClasseA<br/>(faz quase nada)"]
        C2["ClasseB<br/>(faz quase nada)"]
        C3["ClasseC<br/>(faz quase nada)"]
        C4["ClasseD<br/>(faz quase nada)"]
        C1 --> C2 --> C3 --> C4
    end
    subgraph fundo["MODULO PROFUNDO"]
        direction TB
        D1["Modulo<br/>interface simples"]
        D2["implementacao rica<br/>(escondida)"]
        D1 --- D2
    end

Leitura do diagrama: à esquerda, a classitis — uma corrente de caixas pequenas onde a lógica pinga de uma para a outra; a complexidade não sumiu, ela vazou para as costuras entre as classes. À direita, o módulo profundo: uma única caixa com interface fina por cima e uma implementação gorda por baixo, escondida. O critério de Ousterhout não é “quantas classes”, é profundidade — quanto a interface esconde. Mais classes pequenas pode ser pior, não melhor.

Cuidado com a leitura

Ousterhout não está dizendo “faça classes gigantes”. Ele ataca a regra cega “classes pequenas são sempre boas”. O alvo certo é interface simples / implementação rica — esconder complexidade, não picotá-la. SRP continua valendo; o que não vale é confundir “uma responsabilidade” com “cinco linhas por classe”.

DIP em excesso: indireção que não paga

O terceiro exagero é parente do primeiro. DIP manda inverter a seta entre alto e baixo nível — ótimo na fronteira do domínio com a infra. Mas quando você embrulha tudo numa interface — incluindo coisas que nunca serão trocadas, nunca serão mockadas, nunca variarão — você só adicionou indireção.

Indireção tem um custo concreto de navegação: o leitor clica em “ir para definição” e cai numa interface vazia, sem saber qual das implementações roda em produção. DIP paga quando você realmente vai trocar a implementação (ou injetar um fake em teste). Para o resto, é cerimônia. A pergunta que filtra: eu tenho um motivo concreto para inverter esta seta, ou só estou inverting por reflexo?

SOLID vs. simplicidade

Agora o ponto central, do qual todos os exageros acima são sintomas. O objetivo final nunca foi “obedecer SOLID”. O objetivo é gerenciar complexidade — e a métrica disso é baixo acoplamento e alta coesão. SOLID é só um caminho bastante bom (mas não o único) para chegar lá.

A consequência prática é libertadora: quando uma regra “correta” piora o código, você aplicou a regra errada — ou aplicou a regra certa no lugar errado. Não é o código que está em débito com SOLID; é SOLID que está a serviço do código. Se inverter a seta tornou o sistema mais difícil de entender, a inversão estava errada naquele ponto, por mais que o acrônimo dissesse “faça”.

Essa inversão de prioridades não é heresia solitária. Dan North — o mesmo da BDD — propõe o CUPID explicitamente como alternativa a SOLID, argumentando que princípios não são universais, são contextuais, e que importa mais como o código é (suas propriedades) do que regras de como produzi-lo. CUPID descreve cinco propriedades de código “que dá alegria de manter”:

flowchart TB
    cupid["CUPID (Dan North)<br/>propriedades, nao regras"]
    cupid --> c["C - Composable<br/>'plays well with others'"]
    cupid --> u["U - Unix philosophy<br/>'does one thing well'"]
    cupid --> p["P - Predictable<br/>'does what you expect'"]
    cupid --> i["I - Idiomatic<br/>'feels natural'"]
    cupid --> d["D - Domain-based<br/>'fala a lingua do dominio'"]

Leitura do diagrama: cada letra de CUPID é uma propriedade do código pronto, não um mandamento de processo. É a virada de North: SOLID diz o que fazer enquanto escreve; CUPID descreve como o resultado deve ser. Você não precisa adotar CUPID para tirar a lição — a lição é que a meta (composição, coesão, previsibilidade) está acima de qualquer acrônimo. SOLID é uma ferramenta para alcançá-la; quando não alcança, troca-se a ferramenta.

Lastro

Os cinco princípios SOLID são canônicos (Robert C. Martin / Uncle Bob). Já as críticas desta nota são posições nomeadas e verificáveis, não consenso universal: (1) a classitis e a oposição módulo profundo vs. raso são de John Ousterhout, em A Philosophy of Software Design (2018) — ele critica explicitamente o conselho de “classes pequenas”; (2) CUPID é de Dan North (2021), proposto como alternativa a SOLID (“princípios são contextuais, não universais”); (3) over-engineering por abstração prematura conecta a YAGNI e ao smell de speculative generality (Fowler/Beck). Nenhuma dessas críticas invalida SOLID — elas calibram quando aplicá-lo. Trate-as como o contraditório saudável de um princípio, não como refutação. Fontes: Ousterhout — A Philosophy of Software Design (resumo); Dan North — CUPID: for joyful coding.

SOLID na arquitetura

Há um lado positivo que fecha o capstone: os mesmos princípios escalam. O que vale para classes vale para módulos e serviços inteiros, e nessa escala a crítica de “classitis” tem um eco direto — micro-serviços rasos demais são o equivalente sistêmico de classes minúsculas demais.

  • SRP sobe para bounded context — um serviço, uma razão para mudar.
  • DIP sobe para ports & adapters (arquitetura hexagonal) — o domínio define portas, a infra fornece adaptadores.
  • OCP sobe para arquiteturas de plugin — estender sem recompilar o núcleo.

Isso não é assunto desta nota. O tratamento de SOLID aplicado à arquitetura vive em Arquitetura de Software — siga o link para o aprofundamento; aqui fica só o aceno de que a escada existe e os degraus têm o mesmo formato.

Em entrevista

O que o entrevistador senior está testando não é se você decorou SOLID — é se você sabe quando não aplicá-lo. A pergunta-armadilha clássica: “como você aplicou DIP recentemente?” A resposta fraca recita a definição; a resposta forte traz um caso concreto e o trade-off. (Cole o caso de 07 - DIP na prática - DI e IoC e diga o que você ganhou e o que pagou.)

Frases para ter na ponta da língua:

  • “I treat SOLID as heuristics, not rules — I apply DIP when I actually need to swap implementations or inject a fake in tests, not preemptively.”
  • “Over-applying ISP and SRP can fragment the code into shallow classes that are harder to follow. Ousterhout calls this classitis — more classes can mean more complexity, not less.”
  • “The goal isn’t to satisfy SOLID; it’s low coupling and high cohesion. When a ‘correct’ rule makes the code worse, I either applied the wrong rule or applied it in the wrong place.”
  • “I’d rather have one deep module — a simple interface hiding a rich implementation — than five shallow ones that leak complexity into the seams between them.”
  • “YAGNI keeps me honest: I introduce an abstraction when the second real case shows up, not for a future that may never arrive.”
  • “There are alternatives worth knowing — Dan North’s CUPID, for instance — which frame this as properties of good code rather than rules to obey. The principles are contextual.”

Vocabulário PT → EN:

  • heurística → heuristic
  • regra (mandamento) → rule
  • over-engineering → over-engineering
  • abstração prematura → premature abstraction
  • generalização especulativa → speculative generality
  • carga cognitiva → cognitive load
  • módulo profundo → deep module
  • módulo raso → shallow module
  • indireção → indirection
  • trade-off → trade-off
  • julgamento (de engenharia) → engineering judgment
  • fragmentar o código → to fragment the code
  • cerimônia (excesso de ritual) → ceremony / boilerplate
  • esconder complexidade → to hide complexity

Cheat-sheet: os 5 e seus exageros

A leitura completa de cada princípio está na sua nota; esta tabela é o resumo de bolso — incluindo, de propósito, a armadilha de exagero que esta nota inteira destrincha.

LetraFrase de 1 linhaCheiro de violaçãoArmadilha de exagero
SSRPuma classe, uma razão para mudarclasse que muda por motivos não relacionadosclassitis: picotar em classes rasas demais, espalhando a lógica
OOCPaberto p/ extensão, fechado p/ modificaçãoeditar o mesmo switch/if a cada caso novopontos de extensão para variação imaginada (speculative generality)
LLSPsubtipo substitui o tipo base sem surpresasubclasse que lança exceção ou enfraquece o contratohierarquia forçada onde composição serviria melhor
IISPinterface enxuta, focada no consumidorimplementar métodos que você não usaesfarelar em micro-interfaces de um método só
DDIPdependa de abstrações, não de detalhesnegócio importando o banco concretointerface para tudo: indireção que ninguém vai trocar

A leitura honesta da última coluna: cada princípio tem um gradiente — pouco demais (violação) de um lado, demais (cerimônia) do outro. A senioridade é achar o meio, e isso é julgamento, não regra.

flowchart TD
    start["Vou introduzir esta abstracao?"]
    q1{"Tenho 2+ implementacoes<br/>REAIS hoje?"}
    q2{"Vou trocar / mockar<br/>em teste de verdade?"}
    apply["APLIQUE<br/>(a abstracao paga o custo)"]
    yagni["YAGNI<br/>(adie ate o 2o caso real)"]
    start --> q1
    q1 -->|sim| apply
    q1 -->|nao| q2
    q2 -->|sim| apply
    q2 -->|nao| yagni

Leitura do diagrama: o decisor para abstrair tem duas portas de entrada. Se já existem duas implementações reais, ou se você de fato vai injetar um fake em teste, a abstração se paga — aplique. Se a resposta para as duas é “não”, a única coisa que a interface adiciona é indireção: YAGNI, adie até o segundo caso real bater à porta. Não é “nunca abstraia”; é “abstraia por um motivo presente, não por um futuro hipotético”.

E é exatamente o mesmo recado do capstone de OO: assim como OO é ferramenta, não religião, SOLID é heurística, não dogma. Os dois servem à mesma deusa — código que se entende e se muda sem medo. Quando o ritual brigar com esse fim, o fim ganha.

Veja também