Pedro Sousa
← Artigos

Artigo · Blog

A abstração que você criou para simplificar vai ser o próximo problema de manutenção

Toda abstração nasce de uma intenção boa. O problema é que ela envelhece sem aviso, e quando você percebe, ela já está espalhada por dez lugares que você não controla mais. Esse é o custo que ninguém coloca no PR.

A abstração que você criou para simplificar vai ser o próximo problema de manutenção

Num app financeiro com alguns milhões de usuários, criamos um wrapper de networking. A ideia era boa: centralizar tratamento de erros, cabeçalhos de autenticação, retry logic e logging num lugar só. Qualquer camada do app chamava esse wrapper. Nenhuma camada sabia o que tinha dentro.

Funcionou por meses.

Depois alguém precisou adicionar um header específico para uma rota de pagamento. Adicionamos um parâmetro. Depois vieram headers condicionais para outro serviço. Adicionamos mais um parâmetro com valor default. Depois alguém precisou de retry com backoff exponencial só para endpoints críticos. Mais um parâmetro. Mais um enum. Mais um case no switch interno.

Em dois anos, o wrapper tinha 14 parâmetros opcionais, lógica condicional espalhada em quatro métodos privados e nenhum dos parâmetros tinha documentação útil. A abstração que deveria esconder complexidade estava gerando complexidade nova.

Quando alguém do time abria aquele arquivo, fechava em seguida e ia perguntar no Slack o que passava onde.


O problema não é a abstração em si

A maioria dos debates sobre abstrações parte do lugar errado. A discussão costuma ser "você está abstraindo demais" ou "essa abstração é prematura". Essas afirmações não são erradas, mas chegam tarde demais e não ajudam a tomar decisão nenhuma no momento certo.

O problema real não é criar a abstração. É não reconhecer quando ela parou de servir ao propósito original e virou um acúmulo de casos especiais.

Toda abstração tem um ponto de virada. Antes desse ponto, ela simplifica. Depois dele, ela disfarça.

O que torna isso difícil é que o ponto de virada raramente é um evento. É uma sequência de pequenas decisões razoáveis, cada uma fazendo sentido no contexto de quem a tomou. Nenhum PR individualmente parece errado. O problema é o resultado acumulado.


O que acontece na prática

Você cria um NetworkClient. Ele faz uma coisa bem.

Depois vem o requisito de autenticação por token. Você adiciona. Depois vem o retry. Você adiciona. Depois vem o logging estruturado. Você adiciona. Cada adição parece o lugar certo, porque o cliente já sabe de rede, de autenticação, de headers.

Mas no fim, o NetworkClient sabe de tudo. E quando alguma coisa quebra, você não sabe onde. Quando você precisa testar, você não sabe o que mockar. Quando você precisa trocar a estratégia de retry, você tem medo de quebrar o logging. Quando você precisa adicionar um header novo, você tem medo de afetar as rotas que não precisam dele.

Abstrações que crescem horizontalmente assim não são abstrações. São lugares onde a complexidade foi enterrada.


A armadilha da conveniência acumulada

Existe um padrão que eu chamo de Conveniência Acumulada. É quando cada adição a uma abstração existente parece mais simples do que criar uma nova.

E muitas vezes é, no curto prazo.

Adicionar um parâmetro a um método existente leva cinco minutos. Criar um novo protocolo, definir a responsabilidade, ajustar os pontos de injeção, atualizar os testes, discutir com o time o nome certo, leva horas.

Então a escolha óbvia é a conveniência.

O problema é que você repete essa escolha quinze vezes, e no décimo sexto você para na frente do arquivo e percebe que vai levar uma semana entender o que está acontecendo ali antes de conseguir mexer em qualquer coisa.

Abstrações que crescem por conveniência envelhecem mal porque nunca foram redesenhadas. Foram apenas estendidas.


O que funciona melhor

A ideia que mudou minha forma de pensar nisso foi simples: uma abstração não deveria crescer, deveria ser substituída.

Na prática isso significa que quando uma abstração precisa de um novo parâmetro com lógica condicional, esse é o sinal de que o design original não cobre o caso novo. A resposta correta raramente é estender. Quase sempre é criar uma nova abstração especializada e deixar a original fazer só o que ela fazia bem.

Num projeto mais recente, isso se traduziu em não ter um único NetworkClient genérico. Tínhamos um cliente base com comportamento comum, e clientes especializados por domínio, com suas próprias regras, seus próprios testes e sua própria surface de configuração. Cada um era menor e mais testável do que o cliente único teria sido.

O custo foi duplicação de alguns comportamentos comuns. Valeu a pena, porque cada parte do sistema podia ser entendida e modificada sem medo de efeito colateral.


O trade-off que ninguém fala em voz alta

Essa abordagem tem um custo real.

Mais abstrações especializadas significam mais arquivos, mais protocolos, mais pontos de injeção, mais contexto que o desenvolvedor novo vai precisar absorver. Um sistema bem decomposto pode parecer excessivamente fragmentado pra quem chega sem histórico.

E existe um ponto de equilíbrio que não é universal. Depende do tamanho do time, da frequência com que aquela área do código muda, do tempo de vida esperado do produto. Não existe uma regra que funciona pra tudo.

O que eu aprendi é que o custo da abstração errada cresce com o tempo, e o custo de criar a abstração certa é maior no início e menor depois. A maioria dos times otimiza pro curto prazo porque a pressão é de entrega imediata. Aí o débito vai se acumulando até que o próximo refactor vira um projeto de sprint inteiro.


O que eu faria diferente

Eu adicionaria uma pergunta ao processo de revisão de código.

Não "esse código está correto?", não "isso está testado?", mas: "essa mudança está dentro do escopo original dessa abstração ou está expandindo o escopo?"

Se estiver expandindo, isso não significa que a mudança está errada. Significa que vale a pena discutir se a abstração precisa ser reescrita ou se o comportamento novo deveria ficar em outro lugar.

Essa pergunta raramente aparece em PR. Aparece anos depois, quando alguém precisa mexer naquele arquivo e percebe que não consegue sem quebrar outra coisa.


O que fica

Abstrações não falham no dia em que são criadas. Elas falham no dia em que você percebe que não consegue mais modificá-las com segurança.

E esse dia quase sempre chega mais cedo do que o time esperava, porque a abstração cresceu por adições que ninguém questionou individualmente.

A pergunta que vale fazer antes de estender qualquer coisa não é "isso cabe aqui?". É "isso ainda é a mesma coisa?"

Se a resposta for incerta, provavelmente não é.