Ache O Erro Dificil - Jogo dos 7 erros difícil turma da Mônica com respostas • Ache o erro na ...
Jogo dos 7 erros difícil turma da Mônica com respostas • Ache o erro na ...

Depuração não é mágica, é processo

A maior parte dos desenvolvedores gasta mais tempo caçando bugs do que escrevendo código novo. Isso não é um problema de competência, é um problema de método. Quando você para de tratar cada erro como um incidente único e começa a ver padrões, o trabalho fica significativamente mais rápido. Eu aprendi isso da pior maneira possível, passando três dias num erro que na verdade era uma configuração errada em três linhas de JSON.

Como ache o erro dificil quando ele se recusa a aparecer

O primeiro passo é parar de adivinhar. A maioria das pessoas olha para um stack trace e começa a mudar coisas aleatoriamente até o erro sumir. Isso funciona às vezes, mas também cria problemas novos que você não vê na hora. O jeito certo começa com informação. Você precisa saber exatamente o que está acontecendo antes de tentar qualquer coisa. Logging é óbvio, mas a maior parte do pessoal faz errado. Eles colocam log em tudo ou não colocam log em lugar nenhum. O meio-termo é registrar o estado do sistema nos pontos de decisão. O que entrou na função. O que saiu. Que condição foi tomada. Se você tiver isso, consegue reconstruir a execução sem depender de breakpoint every time.

Aqui vai um exemplo prático. Eu estava debugando um serviço de processamento de pagamentos onde os erros aconteciam de forma intermitente, talvez uma vez a cada duzentas requisições. O log padrão não mostrava nada porque o erro só aparecia sob carga. A solução foi adicionar um identificador de transação em cada request e rastrear esse ID por todos os logs do sistema. Isso levou trinta minutos. O problema era um race condition num cache que eu tinha esquecido que existia. O que ajuda muito é isolar o problema. Se o sistema tem mil componentes e algo falha, você não vai achar a causa original navegando por tudo. Pegue o menor subconjunto possível onde o erro ainda acontece. Um teste unitário reprodutível vale mais do que cinquenta horas de investigação em produção. Se você não consegue reproduzir localmente, o próximo passo é instrumentar o código de produção com cuidado, não colocar print em todo lugar.

Outra coisa que ninguém fala: o bug muitas vezes não está onde você espera. Eu passei duas semanas investigando um problema de memória em um sistema Python que no final se revelou causado por uma query SQL mal otimizada num banco Postgres. O GC do Python estava sendo chamado continuamente porque a aplicação consumia muita memória, mas a raiz era outra. Sempre verifique as camadas abaixo do que parece quebrado.

👉 Clique no botão abaixo para saber mais sobre o assunto!

Erros que parecem impossíveis

Tem um tipo de problema que aparece quando menos se espera: o erro que some quando você adiciona log. Isso não é piada. É o efeito observador clássico em sistemas concorrentes. A simples ação de medir altera o timing, e o bug depende de um timing exato. Nesses casos, breakpoints e logs são o inimigo. Você precisa de outra abordagem. A solução é usar ferramentas que tenham overhead mínimo. No meu caso, quandoWorking com Java, eu costumava usar async-profiler para capturar flame graphs sem alterar o timing do sistema. Em vez de tentar reproduzir o bug em produção, eu capturava o estado do sistema nos momentos certos e depois analisava offline. Isso reduziu meu tempo médio de investigação de horas para minutos em problemas de concorrência.

Para problemas em JavaScript, especialmente em ambientes Node.js, o diagnóstico é diferente. O event loop é.single-threaded na prática, então blocking operations são os principais suspeitos. Se um serviço para de responder de vez em quando, a primeira coisa que eu verifico é se há operações síncronas pesadas sendo executadas no thread principal. Uma simples operação de parsing de JSON grande pode causar delays visíveis. Em sistemas distribuídos, a complexidade sobe porque o erro pode estar em qualquer nó da cadeia. Aqui, tracing distribuído é essencial. SEM o trace id passando por todas as chamadas entre serviços, você está basicamente adivinhando. Ferramentas como Jaeger ou OpenTelemetry são padrão no setor agora. Configurar isso leva tempo, mas compensa na primeira vez que você precisa diagnosticar um problema que envolve cinco microserviços.

O que fazer quando nada funciona

Tem um ponto em que continuar investigando sozinho é perda de tempo. Se você já gastou quatro horas e não avançou, o melhor movimento é abandonar temporariamente o problema. Não porque você seja fraco, mas porque seu cérebro está em modo de fixação e não consegue ver a solução óbvia. Eu já voltei num bug depois de uma semana e resolvi em dez minutos porque finalmente consegui enxergar o que estava ignorando. Outra opção é explicar o problema para alguém. Isso se chama rubber duck debugging e funciona porque o simples ato de articular o problema em palavras te força a organizar o raciocínio. Às vezes você chega no final da explicação e percebe que já sabia a resposta mas não tinha percebido.

Também vale considerar quando desistir. Alguns bugs são aceitáveis comoKnown Limitation se o workaround for documentado e o custo de corrigir for maior do que o custo de viver com eles. Eu trabalho num ambiente onde aceitamos esse trade-off deliberadamente em cerca de quinze por cento dos casos. O importante é registrar o que é isso, por que existe, e qual o impacto esperado. Bug não documentado é dívida técnica secreta, e dívida secreta é a pior kinds de dívida. O erro difícil raramente se resolve com uma técnica única. É uma combinação de bom registro, isolamento sistemático, compreensão das camadas do sistema e sabedoria para saber quando parar. A habilidade mais importante não é saber debugar, é saber quão longe você deve ir antes de pedir ajuda ou mudar de estratégia.