Capa do artigo: Overengineering não é sobre excesso de tecnologia

Overengineering não é sobre excesso de tecnologia

Vamos discorrer um pouco sobre um dos lados do prisma do overengineering.

Esse assunto costuma ser tratado de forma simplista demais. Em muitas conversas, overengineering vira uma crítica automática ao uso de frameworks, ferramentas novas ou abordagens arquiteturais mais sofisticadas. Microserviços viram overengineering, Kubernetes vira overengineering, mensageria vira overengineering, um framework novo vira overengineering.

Esse tipo de leitura empobrece demais a discussão.

Uma decisão técnica não é boa ou ruim fora de contexto. O que define a qualidade de uma decisão é a relação entre o problema real que ela resolve, o momento do produto, o risco envolvido, o custo de adoção, o custo de manutenção e a capacidade do time de sustentar aquela escolha depois.

Para mim, um dos lados mais perigosos do overengineering surge quando tomamos decisões sem informação suficiente, sem análise e sem explicitar os trade-offs. Não é necessariamente vaidade técnica. Em muitos casos, nasce de uma tentativa legítima de proteção. Alguém quer evitar um problema futuro, quer deixar o sistema preparado, quer garantir que a arquitetura não se torne um gargalo.

A intenção é boa, mas o problema começa quando a hipótese passa a ser tratada como requisito.

Imagine uma aplicação que hoje não chega a 500 requisições por minuto. Em algum momento da discussão, alguém diz que precisamos garantir elasticidade para suportar 5.000 requisições por minuto. A frase soa madura, parece visão de futuro, parece preocupação técnica legítima.

Mas antes de transformar essa frase em arquitetura, algumas perguntas precisam ser feitas.

De onde veio esse número? Existe projeção de crescimento? Existe uma campanha comercial prevista? Existe contrato assinado que justifique esse volume? Já tivemos picos próximos disso? O impacto de não suportar esse volume seria grave? O gargalo atual é mesmo requisição por minuto, ou apenas escolhemos um número grande o suficiente para parecer prudente?

Essa diferença importa porque construir para 500 requisições por minuto não custa a mesma coisa que construir para 5.000. E aqui não estou falando apenas de infraestrutura. Estou falando do desenho inteiro da solução e da capacidade de desenvolver, entregar e manter.

Quando você decide arquitetar para um volume dez vezes maior do que o cenário atual, muda as preocupações que entram no sistema. Começa a antecipar separações, cria abstrações para cenários que ainda não existem, introduz mecanismos de escala antes de mapear onde o gargalo vai aparecer, sofistica o deploy, sofistica o ambiente local, sofistica os testes, cria ferramentas para investigação de erro, aumenta o número de partes móveis antes de existir um problema real que justifique esse aumento.

Esse é o custo que costuma ficar escondido.

Complexidade não aparece apenas no desenho da solução. Ela aparece no dia seguinte, quando alguém precisa alterar uma regra simples. Aparece quando uma falha atravessa componentes demais. Aparece quando o time precisa explicar o fluxo para uma pessoa nova. Aparece quando o ambiente local deixa de subir com facilidade. Aparece quando uma decisão tomada para um futuro possível começa a cobrar juros no presente.

Fred Brooks separava a complexidade essencial da complexidade acidental. A essencial nasce do próprio problema que precisamos resolver. A acidental nasce das estruturas, ferramentas, integrações e processos que criamos ao redor dele. Essa distinção importa porque overengineering quase sempre aumenta a segunda enquanto tenta se justificar pela primeira. O problema de negócio continua o mesmo, mas agora ele precisa atravessar mais camadas, mais conceitos, mais mecanismos e mais dependências para ser resolvido.

Overengineering, nesse sentido, não é usar tecnologia demais, mas assumir certezas demais.

É quando a arquitetura passa a responder a medos, apostas e cenários imaginados sem que essas hipóteses tenham sido tratadas como hipóteses. O sistema deixa de ser desenhado a partir de restrições observadas e passa a ser desenhado a partir de projeções pouco discutidas.

Existe uma diferença enorme entre preparar um sistema para evoluir e construir agora tudo que ele pode precisar um dia. Preparar para evoluir é manter o código compreensível, cuidar dos limites do domínio, evitar acoplamentos difíceis de remover, medir o comportamento da aplicação e saber quais sinais indicam a necessidade de mudança. É construir uma solução que atende bem o momento atual sem impedir o próximo passo.

Isso também vale para a adoção de ferramentas novas. Trazer uma tecnologia para dentro do sistema não é apenas instalar uma dependência, criar um chart, configurar um pipeline ou subir um serviço. É preparar o terreno para que o time consiga operar aquilo sem depender de uma ou duas pessoas como gargalo. A ferramenta precisa ser entendida, documentada, difundida e incorporada ao modo de trabalho. Caso contrário, a solução que nasceu para aumentar capacidade cria uma nova forma de fragilidade. A do conhecimento concentrado.

Construir tudo antes da hora é outra coisa. É tratar crescimento possível como crescimento confirmado, tratar variação possível como variação inevitável, tratar risco imaginado como risco prioritário, transformar "pode acontecer" em "precisa estar pronto agora".

A pergunta central nesse tipo de discussão não deveria ser apenas "isso escala?". Quase tudo escala de algum jeito quando existe dinheiro, tempo e energia suficientes. A melhor pergunta é: qual problema estamos comprando ao adotar essa solução agora?

Toda decisão técnica compra alguma coisa e vende outra.

Uma decisão pode comprar elasticidade e vender simplicidade, pode comprar isolamento e vender velocidade de desenvolvimento, pode comprar flexibilidade e vender clareza, pode comprar resiliência e vender custo operacional, pode comprar padronização e vender autonomia. Isso não torna a decisão errada, mas exige consciência.

Overengineering surge quando olhamos apenas para o benefício prometido pela solução e ignoramos todo o custo operacional que vem junto.

"Vamos deixar preparado" é uma frase perigosa quando não vem acompanhada de análise.

Preparado para quê? Com qual evidência? A que custo? Por quanto tempo vamos carregar esse custo antes de ele se pagar? Quem vai operar isso no dia a dia? O conhecimento está distribuído no time ou concentrado em quem propôs a solução? Como saberemos que chegou a hora de ampliar a solução?

O ponto não é defender sistemas frágeis, improvisados ou incapazes de crescer. O ponto é não confundir responsabilidade técnica com antecipação descontrolada.

Boa engenharia não ignora o futuro, mas também não transforma todo futuro possível em trabalho presente. Ela cria margem, mede, observa e decide com proporcionalidade. Um sistema bem projetado não precisa nascer pronto para o maior cenário imaginável. Ele precisa sustentar o momento atual e preservar capacidade de mudança.

Essa proporcionalidade também aparece no DDD estratégico, quando Vlad Khononov retoma a diferença entre core domain, supporting subdomain e generic subdomain. O investimento em design não deveria ser distribuído de forma homogênea pelo sistema inteiro. O core domain merece mais cuidado porque é nele que o negócio se diferencia, aprende, evolui e disputa vantagem contra os concorrentes. Partes supporting e generic precisam ser tratadas com outro nível de ambição arquitetural. Algumas podem ser simples, outras podem ser compradas, pagas como serviço ou resolvidas com soluções menos sofisticadas. Colocar o mesmo grau de primazia técnica em tudo é outra forma de overengineering: você gasta tempo, pessoas, energia e infraestrutura em áreas que não devolvem esse investimento na mesma proporção. Saber onde preparar melhor é tão importante quanto saber onde não transformar arquitetura em vaidade.

Essa é uma das formas mais honestas de olhar para overengineering: não como excesso de ferramentas, mas como falta de proporcionalidade.

A solução pode ser tecnicamente interessante, elegante e defensável em outro contexto e ainda assim, naquele momento, para aquele produto, com aquele volume, aquele time, aquele risco e aquele horizonte, ela pode estar apenas antecipando uma complexidade que não precisava existir.

E complexidade antecipada não é gratuita. Ela vira manutenção, acoplamento, custo cognitivo, tempo de onboarding, dificuldade de teste, ruído operacional, concentração de conhecimento. Uma arquitetura que parecia prudente no slide e pesada no cotidiano.

Overengineering é, muitas vezes, uma decisão tecnicamente sofisticada tomada antes de existir informação suficiente para justificá-la.

Não é falta de capacidade técnica.

É falta de análise, falta de trade-off explícito, falta de perguntar com honestidade se esse problema existe agora e o que precisa ser feito hoje para resolvê-lo bem quando ele realmente aparecer.

Essa resposta costuma ser mais útil do que qualquer discussão abstrata sobre ferramenta, framework ou estilo arquitetural.