Abstração vs. Complexidade
Visão Geral
Abstração existe para reduzir o que precisa ser entendido de cada vez. Quando funciona, é a ferramenta mais poderosa da engenharia de software. Quando não funciona, ela acrescenta complexidade em vez de escondê-la — e o resultado é pior que a ausência dela.
A diferença é medível:
eixo real quanto esta abstração esconde, dividido pelo quanto ela
exige que se entenda para usá-la
Ousterhout chama isso de profundidade: uma abstração profunda tem interface pequena sobre implementação substancial. Uma rasa tem interface tão complexa quanto o que esconde — e nesse caso a camada é custo puro.
Problema
O reflexo de organizar em camadas produz estruturas que parecem certas e não pagam por si:
Controlador → Serviço → ServiçoImpl → Repositório → RepositórioImpl → Mapeador → DAO
Sete camadas em que quatro apenas repassam. Acrescentar um campo exige tocar sete arquivos, e nenhum dos sete esconde nada de ninguém — quem lê precisa atravessar todos para entender o que acontece.
O erro simétrico é ausência de abstração: lógica de negócio misturada a acesso a dados, a transporte e a formatação, em funções longas onde nada tem nome.
Os dois produzem o mesmo sintoma — dificuldade de entender —, e por isso são frequentemente confundidos. A resposta ao segundo é abstrair; ao primeiro, remover camada.
Conceitos Centrais
Profundidade: o que esconde sobre o que exige
profunda interface pequena, implementação substancial
ex.: uma função de leitura de arquivo que trata buffer,
codificação, erro parcial e fim de arquivo
rasa interface do tamanho do que esconde
ex.: uma classe cujo único método chama outro método
com os mesmos parâmetros
O teste prático: quantos parâmetros e conceitos preciso entender para usar isto, comparado ao que eu precisaria entender sem isto? Se a razão não for claramente favorável, a camada não se paga.
Ver abstração.
Abstração vazada cobra duas vezes
abstração fechada uso sem conhecer o que está por baixo
abstração vazada é preciso conhecer os dois níveis
Quando o comportamento subjacente atravessa a interface — desempenho, erro específico, limite, semântica de transação —, o usuário precisa entender a abstração e o que ela esconde. O custo dobra em vez de reduzir.
Exemplos comuns:
mapeador objeto-relacional que gera consultas ruins → é preciso saber SQL
cliente HTTP que esconde tempo excedido → é preciso saber a rede
fila abstraída sobre semânticas diferentes → é preciso saber o mecanismo
Isso não condena a abstração — significa que a comparação deve ser feita contando o custo real, que inclui o nível de baixo.
Interface pequena não é interface simplista
pequena poucos conceitos, poucos parâmetros, comportamento previsível
simplista esconde o que o usuário precisa decidir, e ele acaba
contornando a abstração
Uma abstração que esconde algo essencial força o contorno — e código que contorna a abstração é o pior estado: paga-se a camada e usa-se o nível de baixo mesmo assim.
Camada sem valor é reconhecível
métodos que apenas repassam, com a mesma assinatura
uma implementação por interface, há anos
classes cujo nome é o do tipo, não da responsabilidade
mudança simples exigindo tocar quatro arquivos em sequência
quem lê precisa atravessar a camada para entender qualquer coisa
Três ou mais desses sinais indicam camada removível. Remover é uma das refatorações de maior retorno e das menos praticadas, porque camadas parecem virtude.
O custo é de quem lê, e é recorrente
escrever a abstração uma vez
lê-la toda vez, por todos
Uma abstração mal escolhida cobra de cada pessoa que entra no código, para sempre. É por isso que "eu entendo, é simples" não é argumento — a pergunta é se alguém que chega em dois anos entenderá sem explicação.
Ver complexidade.
Nomear é a forma mais barata de abstrair
extrair uma função com nome que descreva a intenção
custo: quase zero
benefício: quem lê não precisa entender o corpo
Isso frequentemente basta, e é o que se deveria tentar antes de qualquer interface, camada ou hierarquia. Grande parte da complexidade atribuída à falta de abstração é, na verdade, falta de nomes.
Sinais de escolha errada
abstraiu demais
camadas de repasse
hierarquias profundas com um caminho real
interfaces com um implementador
navegação exigindo atravessar 4+ arquivos para achar a lógica
pessoas novas demorando a encontrar onde as coisas acontecem
abstraiu de menos
funções longas misturando níveis — negócio, dados, transporte
o mesmo bloco de tratamento repetido em vinte lugares
detalhe de infraestrutura aparecendo na lógica de negócio
impossível testar a regra sem subir banco e rede
Custo de mudar de ideia
concreto → abstrato barato: os casos existem, a extração é local
abstrato → concreto caro: remover camada usada por muitos exige
mapear dependentes, e o medo trava
A assimetria é a mesma de simplicidade vs. flexibilidade, e com a mesma conclusão: na dúvida, comece concreto. Abstrair depois é feito com informação; remover abstração é feito com incerteza.
Modelo Mental
Esconde mais do que exige entender? Se não, a camada é custo. E antes de abstrair, tente nomear.
Quando Usar
Abstraia quando:
- A implementação é substancial e a interface pode ser pequena.
- Há mais de um caso real, com variação conhecida.
- A abstração fecha — o usuário não precisa do nível de baixo.
- O nome expressa intenção, não mecanismo.
- O que se esconde é volátil e o que se expõe é estável.
Prefira concreto quando:
- A camada apenas repassaria.
- Há um caso só.
- A abstração vazaria comportamento essencial.
- Nomear uma função já resolve.
- A estrutura é ditada por convenção e não por necessidade.
Quando Não Usar
Por convenção de camadas, sem que cada uma esconda algo.
Com um único implementador, indefinidamente.
Quando ela vaza o que o usuário precisa saber.
Antes do segundo caso. Este piso é mais baixo que o da regra de três, e a diferença é de operação: ali se decide unificar trechos que se repetem, e o terceiro caso existe para revelar o eixo da variação; aqui se decide esconder uma implementação atrás de uma interface, e o segundo caso já basta porque a interface não precisa acomodar variação nenhuma — precisa esconder algo volátil. Se a abstração também unifica duplicação, vale o piso de lá.
Para parecer organizado — organização visual não é abstração.
Alternativas
- Nomear — extrair função com nome de intenção; o mais barato.
- Módulo com fronteira, sem interface — agrupamento sem indireção.
- Composição em vez de hierarquia — evita profundidade de herança.
- Achatar — remover camada e aceitar o concreto onde ele é claro.
A última é a mais subutilizada: retirar uma camada de repasse melhora legibilidade e desempenho ao mesmo tempo, e quase nunca é proposta.
Trade-offs
| Abstração | Concreto |
|---|---|
| Esconde detalhe | Direto de ler |
| Reuso e substituição | Sem indireção |
| Custo de entender a camada | Repetição possível |
| Difícil de remover | Fácil de abstrair depois |
| Interface pequena | Interface completa |
|---|---|
| Fácil de usar | Não força contorno |
| Pode esconder o essencial | Mais conceitos |
| Profunda | Rasa se expuser tudo |
Modos de Falha
Camada de repasse. Custo sem benefício.
Abstração vazada. Dois níveis para entender em vez de um.
Interface simplista. Força contorno; paga-se a camada e não se usa.
Hierarquia profunda. Navegação cara para achar a lógica.
Abstração antes do segundo caso. Forma do primeiro.
Ausência de nomes. Complexidade atribuída à falta de estrutura.
Erros Comuns
Criar camada por convenção arquitetural, sem perguntar o que ela esconde.
Medir qualidade pelo número de interfaces.
Não considerar remover camada como refatoração.
Abstrair antes de nomear.
Ignorar que abstrações vazam ao comparar custos.
Exemplo Real
Uma empresa de saúde adotou uma arquitetura em camadas padronizada para todos os seus serviços, definida por um guia interno. Cada entidade exigia:
Controlador
DTO de entrada e de saída
Mapeador de DTO
Interface de Serviço
Implementação de Serviço
Interface de Repositório
Implementação de Repositório
Entidade
Mapeador de Entidade
Nove artefatos por entidade. Uma medição sobre 41 entidades de três serviços:
interfaces com um único implementador 78 de 82
métodos que apenas repassam, sem lógica ~64% do total
arquivos tocados numa mudança simples de campo média de 6,8
tempo médio até uma pessoa nova localizar
onde uma regra de negócio mora ~25 min, medido em
exercício com 6 pessoas
regras de negócio na camada de Serviço presentes em 12 de 41 casos
A última linha é o diagnóstico. Em 29 das 41 entidades, a camada de serviço não continha nenhuma lógica — ela existia porque o guia mandava.
E o efeito colateral: como a estrutura padrão não acomodava a lógica que de fato existia, regras acabavam nos controladores e nos mapeadores, que era onde havia espaço.
A revisão do guia:
Camada obrigatória apenas quando esconde algo. A regra escrita: uma camada precisa responder "o que eu não preciso saber por causa dela?". Se a resposta for "nada", ela não é criada.
Interface só com segundo implementador — ou com necessidade concreta de substituição em teste que não seja atendida de outra forma.
Entidades simples sem camada de serviço. Controlador conversa com repositório diretamente quando não há regra.
Camada de domínio real para as 12 entidades com lógica substantiva, desta vez desenhada a partir das regras e não do gabarito. Ver desenho tático.
Nomear antes de estruturar como orientação explícita no guia, com exemplos.
Achatamento das 29 entidades sem lógica, feito de forma incremental ao longo de sete meses.
Resultados:
artefatos por entidade simples de 9 para 4
interfaces com um implementador 9 (todas justificadas)
arquivos tocados numa mudança de campo média de 2,3
tempo até localizar uma regra de negócio ~6 min
regras de negócio em controlador ou mapeador 0
linhas de código nos três serviços -31%
cobertura de teste inalterada
O dado que a equipe destaca: as regras de negócio pararam de vazar para os controladores depois que as camadas foram reduzidas. A estrutura excessiva não estava protegendo o domínio; ela estava empurrando a lógica para onde coubesse.
Na retrospectiva: o guia original tinha sido escrito com boa intenção — padronizar para facilitar a leitura entre serviços. Ele padronizou a forma e não a substância, e a forma sem substância é exatamente o que Ousterhout chama de camada rasa: custo de leitura para todos, benefício para ninguém.
Conceitos Relacionados
- Abstração e Complexidade.
- Simplicidade vs. Flexibilidade.
- Acoplamento vs. Duplicação.
- Desenho Modular.
Exercício Prático
Pegue uma camada do seu sistema e responda: o que eu não preciso saber por causa dela?
Se você não conseguir responder, ou se a resposta for "nada", ela é candidata a achatamento.
Perguntas de Entrevista
- Como se mede se uma abstração se paga?
- Por que uma abstração vazada custa mais que a ausência de abstração?
- Por que nomear costuma resolver o que se atribui à falta de estrutura?
Para Aprofundar
- Ousterhout, John. A Philosophy of Software Design. 2ª ed. Yaknyam Press, 2021.
- Spolsky, Joel. The Law of Leaky Abstractions. 2002.
- Parnas, David. On the Criteria To Be Used in Decomposing Systems into Modules. CACM, 1972.