Seu Código Limpo Pode Ser Mais Lento do Que Você Imagina

Seu Código Limpo Pode Ser Mais Lento do Que Você Imagina

📅 terça-feira, 14 de julho de 2026 🕒 10 min

Escrever código limpo é importante, mas não suficiente. Neste artigo veremos por que problemas como N+1 Query, lazy loading, índices ausentes e falta de observabilidade derrubam aplicações perfeitamente organizadas, além de entender por que ferramentas de IA aceleram a escrita de código, mas não substituem o entendimento da arquitetura do sistema.

Índice

"Funciona na minha máquina."

Provavelmente nenhuma frase já causou tantos problemas em projetos de software quanto essa.

É comum associarmos qualidade de código a boas práticas como funções pequenas, nomes claros, documentação, testes e uma arquitetura organizada. Tudo isso realmente importa. Mas existe um detalhe que muitos desenvolvedores descobrem apenas quando colocam um sistema em produção:

código limpo não é sinônimo de código preparado para produção.

Uma aplicação pode ser elegante, seguir todos os princípios do Clean Code, passar em todos os testes e ainda assim falhar miseravelmente quando recebe centenas de usuários simultaneamente.

Na maioria das vezes, o problema não está na sintaxe nem na organização do projeto. Está no comportamento do sistema sob carga.

É aí que aparecem armadilhas como consultas N+1, lazy loading, índices inexistentes, pools de conexão saturados e a completa falta de observabilidade — problemas que dificilmente aparecem durante o desenvolvimento local, mas que podem derrubar um sistema inteiro em questão de minutos.

Com a popularização de ferramentas de IA como GitHub Copilot, Cursor e Claude Code, esse cenário se tornou ainda mais interessante. Nunca foi tão fácil produzir código bonito, organizado e aparentemente profissional. Ao mesmo tempo, nunca foi tão fácil gerar soluções que escondem gargalos de desempenho difíceis de perceber até que seja tarde demais.

Neste artigo vamos entender por que isso acontece, analisar um caso típico de falha em produção e discutir os quatro pilares que diferenciam um código que apenas passa em um code review de um sistema que continua funcionando quando a carga aumenta.


Código limpo não significa código rápido

Durante muitos anos, a comunidade de desenvolvimento promoveu uma série de boas práticas que realmente tornam o código mais fácil de entender e manter. Funções pequenas, nomes descritivos, baixo acoplamento, alta coesão, testes automatizados e princípios como SOLID são fundamentais para qualquer projeto de médio ou grande porte.

O problema começa quando confundimos essas práticas com desempenho.

Um código pode ser extremamente bem organizado e, ainda assim, executar centenas de consultas desnecessárias ao banco de dados, desperdiçar memória, bloquear threads ou gerar um volume excessivo de chamadas para serviços externos.

Em outras palavras, qualidade estrutural e qualidade operacional são coisas diferentes.

Imagine duas implementações que retornam exatamente os mesmos dados:

  • uma executa apenas uma consulta SQL;
  • outra executa 501 consultas.

Ambas podem ser igualmente legíveis. Ambas podem passar no code review. Ambas podem ter cobertura de testes próxima de 100%.

Mas apenas uma delas continuará funcionando quando centenas de usuários fizerem a mesma requisição simultaneamente.

É importante entender que Clean Code nunca prometeu resolver problemas de desempenho. Seu objetivo é tornar o software compreensível e sustentável ao longo do tempo.

O problema surge quando acreditamos que um código elegante automaticamente será eficiente.

Em produção, o sistema é julgado por métricas completamente diferentes:

  • tempo de resposta;
  • consumo de CPU;
  • uso de memória;
  • quantidade de consultas ao banco;
  • utilização do pool de conexões;
  • throughput;
  • capacidade de suportar carga.

Nenhuma dessas métricas pode ser inferida apenas olhando para a organização do código.

É justamente nesse ponto que muitos desenvolvedores descobrem uma das armadilhas mais famosas do desenvolvimento backend.


A armadilha invisível das consultas N+1

O N+1 Query Problem é um dos problemas de desempenho mais comuns em aplicações que utilizam ORMs, como Eloquent, Doctrine, SQLAlchemy, Entity Framework, Hibernate, Sequelize, Prisma ou Django ORM.

O motivo é simples: a maioria desses frameworks utiliza lazy loading por padrão.

Considere o seguinte exemplo em Laravel:

$products = Product::all();

foreach ($products as $product) {
    echo $product->category->name;
}

À primeira vista, o código parece impecável.

Ele é curto, expressivo e fácil de entender.

Mas dependendo da configuração do ORM, ele executa:

  • uma consulta para buscar todos os produtos;
  • uma consulta adicional para cada categoria acessada.

Se existirem 500 produtos, teremos aproximadamente:

1 + 500 = 501 consultas

O problema fica ainda mais evidente quando visualizamos o fluxo:

SELECT * FROM products;

SELECT * FROM categories WHERE id = 1;
SELECT * FROM categories WHERE id = 2;
SELECT * FROM categories WHERE id = 3;
...

A solução costuma ser extremamente simples:

$products = Product::with('category')->get();

Agora o Eloquent realiza o carregamento antecipado (eager loading), reduzindo centenas de consultas para apenas duas.

O mesmo acontece em SQLAlchemy:

products = session.query(Product).options(
    selectinload(Product.category)
).all()

Ou utilizando o Django ORM:

products = Product.objects.select_related("category")

No Prisma:

const products = await prisma.product.findMany({
  include: {
    category: true
  }
});

Perceba que o problema nunca foi a linguagem.

O problema é o comportamento da aplicação.

Em um ambiente de desenvolvimento, com apenas dez registros de teste, dificilmente alguém perceberá a diferença.

Em produção, com milhares de registros e centenas de usuários simultâneos, a situação muda completamente.

Cada consulta adicional consome:

  • uma conexão;
  • tempo de processamento;
  • largura de banda entre aplicação e banco;
  • recursos do servidor.

O efeito é cumulativo.

Uma consulta lenta afeta um usuário.

Centenas de consultas desnecessárias afetam todos.


O banco de dados também faz parte do seu código

Existe uma frase bastante conhecida entre administradores de banco de dados:

O banco de dados é a aplicação.

Embora seja um exagero, ela transmite uma ideia importante: não existe aplicação rápida com consultas lentas.

Muitos desenvolvedores tratam SQL como um detalhe de implementação.

Na prática, ele é um dos principais fatores que determinam a escalabilidade de um sistema.

Considere esta consulta:

SELECT *
FROM orders
WHERE customer_id = 123;

Sem um índice sobre customer_id, o banco pode precisar percorrer milhões de registros até encontrar os resultados desejados.

Com um índice adequado, a diferença pode ser de segundos para poucos milissegundos.

O mesmo vale para:

  • índices ausentes;
  • JOINs mal planejados;
  • consultas que retornam colunas desnecessárias;
  • filtros aplicados apenas na aplicação;
  • paginação incorreta;
  • ordenações sem índice;
  • uso excessivo de SELECT *.

Outro aspecto frequentemente ignorado é o número de viagens entre a aplicação e o banco de dados.

Cada consulta representa uma comunicação pela rede, processamento no servidor, serialização dos dados e envio da resposta.

Mesmo consultas extremamente rápidas possuem um custo.

Executar uma consulta de 2 ms quinhentas vezes costuma ser muito pior do que executar uma única consulta de 30 ms.

Por isso, quando pensamos em desempenho, não devemos analisar apenas o código da aplicação.

Precisamos entender também:

  • como os dados estão modelados;
  • quais índices existem;
  • quais consultas são executadas;
  • como o banco escolhe seus planos de execução;
  • quantas conexões simultâneas o servidor suporta.

Ignorar essa camada é como tentar otimizar um carro analisando apenas o volante.


Observabilidade: você não otimiza aquilo que não mede

Existe um padrão curioso em sistemas lentos.

Quase nunca alguém percebe o problema olhando para o código.

O problema aparece quando alguém observa o comportamento da aplicação.

É justamente essa capacidade que chamamos de observabilidade.

Ela consiste em coletar informações suficientes para entender o estado interno do sistema a partir de seus sinais externos.

Na prática, isso significa monitorar métricas como:

  • tempo de resposta;
  • quantidade de consultas SQL;
  • consumo de memória;
  • uso de CPU;
  • erros;
  • exceções;
  • tempo gasto em chamadas externas;
  • latência do banco de dados.

Ferramentas como Laravel Telescope, Laravel Debugbar, Django Debug Toolbar, OpenTelemetry, Prometheus e Grafana ajudam justamente nessa tarefa.

Imagine que uma requisição gere o seguinte registro:

GET /products

Tempo: 842 ms

Consultas SQL: 487

A partir dessa única informação, o problema praticamente se revela sozinho.

Sem observabilidade, o cenário costuma ser diferente.

Os usuários reclamam.

O servidor apresenta lentidão.

O banco de dados consome 100% da CPU.

E a equipe começa uma investigação que pode durar horas.

Não porque o problema seja complexo.

Mas porque ninguém estava medindo aquilo que realmente importava.

Antes de otimizar qualquer sistema, a primeira pergunta deveria ser:

O que exatamente está lento?

Sem essa resposta, toda tentativa de otimização é apenas um palpite.


O contexto é a parte que quase ninguém ensina

Tutoriais ensinam APIs.

Cursos ensinam frameworks.

Documentações ensinam funcionalidades.

Produção ensina contexto.

Esse talvez seja o maior salto na carreira de um desenvolvedor.

Quando estamos aprendendo uma tecnologia, normalmente trabalhamos com:

  • poucos registros;
  • um único usuário;
  • banco de dados local;
  • ausência de concorrência;
  • ambiente controlado.

Esse ambiente é excelente para aprender.

Mas ele não representa a realidade.

Na produção existem fatores que simplesmente não aparecem durante os estudos.

Por exemplo:

  • milhares de usuários simultâneos;
  • filas de processamento;
  • pools de conexão limitados;
  • picos de acesso;
  • redes instáveis;
  • serviços externos indisponíveis;
  • tabelas com milhões de registros.

É nesse momento que percebemos que uma aplicação precisa ser construída para o pior cenário esperado, não para o cenário médio.

Esse raciocínio também ajuda a entender as limitações das ferramentas de IA.

GitHub Copilot, Cursor, Claude Code e outras soluções conseguem produzir código extremamente organizado em poucos segundos.

Mas elas não conhecem:

  • o volume de dados da sua aplicação;
  • a estratégia de índices do banco;
  • o número de usuários simultâneos;
  • os horários de maior tráfego;
  • as limitações da infraestrutura.

Elas escrevem código.

Quem precisa entender o sistema continua sendo o desenvolvedor.

A IA acelera a implementação.

Ela não substitui a engenharia.


Conclusão

Escrever código limpo continua sendo uma habilidade essencial. Um código bem organizado facilita a manutenção, reduz erros e torna o trabalho em equipe muito mais eficiente.

Mas produção exige mais do que isso.

Ela exige compreender como o software se comporta sob carga, como o banco de dados responde às consultas, quais recursos são consumidos, como identificar gargalos rapidamente e como projetar sistemas pensando em crescimento.

O N+1 Query Problem é apenas um exemplo desse desafio. Existem muitos outros: índices ausentes, consultas mal planejadas, falta de cache, contenção de conexões, chamadas desnecessárias a serviços externos e diversos outros fatores que dificilmente aparecem em um projeto de estudos.

O que diferencia um desenvolvedor experiente não é apenas a capacidade de escrever código elegante, mas a habilidade de enxergar o sistema como um todo.

Antes de colocar uma aplicação em produção, vale a pena fazer algumas perguntas:

  • Quantas consultas essa requisição executa?
  • Todas elas são realmente necessárias?
  • Os índices do banco atendem aos filtros utilizados?
  • O tempo de resposta continua aceitável sob carga?
  • Existem métricas e logs suficientes para identificar problemas rapidamente?

Responder a essas perguntas pode evitar horas de investigação, indisponibilidade do sistema e prejuízos para o negócio.

No fim das contas, usuários não avaliam a beleza do seu código. Eles avaliam apenas uma coisa: se o sistema continua funcionando quando mais precisam dele.

Bons desenvolvedores escrevem código que funciona.

Desenvolvedores experientes escrevem código que continua funcionando quando tudo começa a dar errado.

A diferença entre eles raramente está na linguagem, no framework ou na ferramenta utilizada. Ela está na forma como enxergam o sistema como um todo.