Debugging Mental: Como Investigar Bugs em JavaScript Sem Depender do Console.log
Um console.log resolve bugs simples. O problema é que bugs simples não são os que consomem horas do seu dia. Quando o estado muda entre dois renders, quando uma promise resolve com valor inesperado, quando o payload chega correto na API mas o objeto no handler já está diferente: nesses cenários, console.log vira um ciclo de adicionar, salvar, recarregar, ler, adicionar mais um, salvar de novo. Esse ciclo tem nome: printf debugging. E ele escala mal.
Este post cobre as ferramentas que já existem no seu ambiente (DevTools, debugger statement, Node.js inspector) e técnicas de raciocínio que reduzem o tempo entre "tem um bug" e "achei a causa raiz".
O custo real do printf debugging
O fluxo típico de quem depende de console.log:
- Suspeita de onde o bug está.
- Adiciona logs em 3-5 pontos.
- Reproduz o bug.
- Lê o output, percebe que precisa de mais contexto.
- Adiciona mais logs.
- Repete até encontrar (ou até desistir e reescrever o trecho).
Cada iteração desse ciclo custa entre 30 segundos e 2 minutos. Em bugs de estado ou de concorrência assíncrona, são 10-15 iterações. Faça a conta: 15 a 30 minutos gastos em instrumentação, não em investigação.
Breakpoints eliminam esse ciclo porque você inspeciona qualquer variável no escopo sem modificar código. A diferença não é filosófica. É operacional.
Breakpoints condicionais: o substituto direto
O Chrome DevTools (e qualquer navegador baseado em Chromium) aceita breakpoints com condição. Você clica com botão direito na linha, escolhe "Add conditional breakpoint" e escreve uma expressão JavaScript. A execução só pausa quando a expressão retorna truthy.
// Cenário: uma lista de pedidos renderiza com preço errado.
// Você suspeita que o problema está na função de cálculo.
function calcularTotal(pedido) {
const subtotal = pedido.itens.reduce(
(acc, item) => acc + item.preco * item.quantidade,
0
);
// Breakpoint condicional aqui: pedido.id === 'ped-4821'
// Isso pausa APENAS no pedido problemático, ignorando os outros 200.
const desconto = pedido.cupom ? aplicarCupom(pedido.cupom, subtotal) : 0;
return subtotal - desconto;
}
No painel de Sources do DevTools, ao pausar, você tem acesso ao Scope completo: variáveis locais, closure, módulo. Pode avaliar expressões no Console enquanto pausado. Pode modificar valores em tempo real para testar hipóteses.
Logpoints: console.log sem tocar no código
Logpoints são a ponte para quem não quer largar o hábito de ler logs mas quer parar de editar arquivos. No Chrome DevTools, clique com botão direito na linha e escolha "Add logpoint". A sintaxe aceita interpolação:
// Logpoint configurado no DevTools (não é código no arquivo):
// "calcularTotal chamado: pedido.id={pedido.id}, subtotal={subtotal}"
// Isso imprime no Console sem pausar a execução,
// sem modificar o arquivo fonte,
// sem precisar de rebuild.
A vantagem sobre console.log é que logpoints não poluem o código, não precisam de commit acidental, e você remove fechando a aba. Para projetos com hot reload lento ou builds pesados, a economia de tempo é significativa.
O debugger statement: quando você quer controle no código
Existe um caso legítimo para inserir algo no código: quando o bug só reproduz em um ambiente específico e você precisa que qualquer pessoa do time consiga pausar ali.
async function processarWebhook(req, res) {
const evento = req.body;
// Pausa aqui quando o Node.js inspector está conectado.
// Se nenhum debugger está conectado, o runtime ignora este statement.
if (evento.tipo === "pagamento.falhou") {
debugger;
}
const resultado = await tratarEvento(evento);
res.status(200).json(resultado);
}
O debugger statement funciona tanto no navegador quanto no Node.js (com inspector ativo). A diferença de comportamento entre os dois runtimes é que no Node.js você precisa iniciar o processo com a flag --inspect ou --inspect-brk.
Node.js Inspector: debugging no back
Leia o artigo completo em https://www.vivodecodigo.com.br/backend/debugging-javascript-sem-console-log-tecnicas-investigacao