Todo desenvolvedor gasta uma parte significativa do seu tempo depurando. A diferença entre um desenvolvedor júnior e um sênior muitas vezes não é o número de bugs que eles encontram — é a rapidez e a sistematicidade com que os identificam e corrigem. JavaScript, com sua natureza assíncrona, tipagem dinâmica e complexidade do ambiente do navegador, apresenta desafios únicos de depuração. Este guia percorre o kit de ferramentas completo de depuração, desde métodos de console que você talvez não conheça até técnicas avançadas do DevTools para código assíncrono.
1. Métodos de Console Além do console.log()
A maioria dos desenvolvedores usa console.log() por padrão, mas a API do Console possui um rico conjunto de métodos projetados para diferentes cenários de depuração. Usar o método correto economiza tempo e torna a saída de depuração muito mais legível.
console.table() — Dados em Tabela
Ao depurar arrays de objetos, console.table() os renderiza como uma tabela classificável e filtrável no DevTools. Isso é vastamente superior a rolar pela saída de objetos aninhados.
const usuarios = [
{ id: 1, nome: 'Alice', papel: 'admin' },
{ id: 2, nome: 'Bob', papel: 'editor' },
{ id: 3, nome: 'Charlie', papel: 'viewer' },
];
// Exibir como uma tabela classificável
console.table(usuarios);
// Mostrar apenas colunas específicas
console.table(usuarios, ['nome', 'papel']);
console.trace() — Rastreamento de Pilha de Chamada
Quando você precisa saber como uma função foi chamada, console.trace() imprime a pilha de chamadas completa no ponto de invocação.
function processarPedido(pedido) {
console.trace('processarPedido chamado');
// Você verá a cadeia completa:
// processarPedido < handleCheckout < onSubmit < addEventListener
}
console.group() & console.groupEnd() — Saída Organizada
Agrupe mensagens de log relacionadas para manter a saída do console limpa, especialmente ao depurar loops ou funções recursivas.
function depurarUsuario(usuario) {
console.group(`Usuário: ${usuario.nome}`);
console.log('ID:', usuario.id);
console.log('Email:', usuario.email);
console.log('Permissões:', usuario.permissoes);
console.groupEnd();
}
// Use console.groupCollapsed() para grupos recolhidos por padrão
usuarios.forEach(usuario => {
console.groupCollapsed(`Usuário #${usuario.id}`);
console.log('Detalhes:', usuario);
console.groupEnd();
});
console.time() & console.timeEnd() — Medição de Desempenho
Meça quanto tempo um bloco de código leva para executar sem ferramentas externas.
console.time('fetchDados');
const resposta = await fetch('/api/usuarios');
const dados = await resposta.json();
console.timeEnd('fetchDados');
// Saída: "fetchDados: 142.5ms"
console.assert() — Logging Condicional
Registre uma mensagem apenas quando uma condição for falsa — perfeito para verificações de sanidade que não devem poluir o console quando as coisas estão funcionando.
console.assert(usuario !== null, 'Usuário não deveria ser nulo neste ponto'); console.assert(itens.length > 0, 'Carrinho não deveria estar vazio durante o checkout');
console.count() & console.countReset()
Rastreie quantas vezes uma parte do código é executada — útil para detectar re-renderizações inesperadas em frameworks ou chamadas de função redundantes.
function renderizarComponente(nome) {
console.count(`render:${nome}`);
// Saída: "render:Header: 1", "render:Header: 2", etc.
}
// Reiniciar quando necessário
console.countReset('render:Header');
2. Mergulho Profundo no Chrome DevTools
Breakpoints
Breakpoints são mais poderosos que console.log() porque pausam a execução e permitem inspecionar todo o estado da aplicação. O DevTools suporta vários tipos:
- Breakpoints de linha de código — clique no número da linha no painel Sources. O tipo mais comum.
- Breakpoints condicionais — clique com o botão direito em um número de linha e adicione uma condição como
usuario.id === 42. A execução só pausa quando a condição é verdadeira. - Breakpoints DOM — clique com o botão direito em um elemento no painel Elements e selecione "Break on…" para pausar quando o nó DOM for modificado, removido ou tiver alterações de subárvore.
- Breakpoints XHR/Fetch — pause quando uma requisição de rede corresponder a um padrão de URL. Encontrado no painel Sources sob "XHR/fetch Breakpoints".
- Breakpoints de listener de eventos — pause em eventos específicos como
click,keydownouscroll.
function processarPagamento(valor) {
if (valor > 10000) {
debugger; // A execução pausa aqui quando o DevTools está aberto
}
// ... resto da lógica
}
A Aba Network
A aba Network é essencial para depurar chamadas de API, recursos lentos e erros CORS. Recursos-chave incluem:
- Filtrar por tipo — isole requisições XHR/Fetch, imagens, scripts ou folhas de estilo.
- Inspecionar cabeçalhos de requisição/resposta — crítico para depurar problemas de autenticação, cache e CORS.
- Throttling — simule condições de 3G lenta ou offline para testar estados de carregamento e tratamento de erros.
- Copiar como cURL — clique com o botão direito em qualquer requisição para copiá-la como um comando cURL para teste no terminal.
- Visualização de Waterfall — visualize a sequência de carregamento e identifique gargalos.
Profiler de Desempenho
A aba Performance grava uma linha do tempo de tudo o que o navegador faz — execução de scripts, layout, pintura e composição. Para usá-la efetivamente:
- Clique no botão de gravação ou pressione
Ctrl+E. - Execute a ação que você deseja perfilar (por exemplo, rolar, clicar em um botão, navegar).
- Pare a gravação e analise o gráfico de chama (flame chart).
- Procure por tarefas longas (blocos que excedem 50ms) — elas causam "jank" e pontuações INP ruins.
- Aprofunde-se na árvore de chamadas para encontrar as funções específicas que consomem mais tempo.
3. Source Maps
JavaScript moderno é tipicamente empacotado, minificado e, às vezes, transpilado antes de chegar ao navegador. Source maps preenchem a lacuna entre seu código fonte original e a saída transformada, permitindo que você depure contra código legível no DevTools.
// webpack.config.js
module.exports = {
// Desenvolvimento: rebuilds rápidos, mapeamento de linha preciso
devtool: 'eval-source-map',
// Produção: arquivos .map separados, não enviados aos usuários
// devtool: 'source-map',
};
Melhores práticas para source maps:
- Use
eval-source-mapoucheap-module-source-mapem desenvolvimento para rebuilds rápidos. - Use
source-mapem produção — isso gera um arquivo.mapseparado. - Configure seu servidor para não servir arquivos
.mappublicamente se você não quiser que os usuários inspecionem seu código. Alternativamente, faça upload dos mapas para seu serviço de rastreamento de erros (Sentry, Bugsnag). - Ao "embelezar" (beautify) JS minificado com a ferramenta JS Beautify do Pan Tool, você obtém código legível para inspeção rápida, mesmo sem source maps.
4. Depurando Código Assíncrono
Bugs assíncronos estão entre os mais difíceis de rastrear porque a pilha de chamadas não conta toda a história. O Chrome DevTools melhorou significativamente nesta área.
Pilha de Chamadas Assíncronas
O DevTools captura automaticamente pilhas de chamadas assíncronas para Promises, setTimeout, requestAnimationFrame e outras APIs assíncronas. Quando você atinge um breakpoint dentro de uma função async, o painel da pilha de chamadas mostra a cadeia completa de chamadores assíncronos.
async function carregarPerfilUsuario(userId) {
try {
const resposta = await fetch(`/api/usuarios/${userId}`);
if (!resposta.ok) {
// Defina um breakpoint aqui para inspecionar a resposta
throw new Error(`HTTP ${resposta.status}: ${resposta.statusText}`);
}
const usuario = await resposta.json();
return usuario;
} catch (error) {
console.error('Falha ao carregar usuário:', error);
throw error; // Relança para que o chamador lide com isso
}
}
// Erro comum: esquecer de usar await
async function init() {
// BUG: await ausente — carregarPerfilUsuario retorna um Promise, não o usuário
const usuario = carregarPerfilUsuario(42);
console.log(usuario.nome); // undefined!
// CORREÇÃO:
const usuario = await carregarPerfilUsuario(42);
console.log(usuario.nome); // "Alice"
}
Depurando Rejeições de Promise
Ative "Pausar em exceções capturadas" no painel Sources do DevTools para capturar promises rejeitadas. Também escute por rejeições não tratadas globalmente:
window.addEventListener('unhandledrejection', (event) => {
console.error('Rejeição de promise não tratada:', event.reason);
// Enviar para o seu serviço de rastreamento de erros
});
5. Tipos Comuns de Erro em JavaScript
Entender os tipos de erro ajuda você a diagnosticar problemas mais rapidamente. Aqui estão os mais comuns:
- ReferenceError — acessando uma variável que não foi declarada. Causa comum: erros de digitação, problemas de escopo ou uso de uma variável antes da declaração
let/const(zona morta temporal). - TypeError — realizando uma operação no tipo errado, como chamar
undefinedcomo uma função ou acessar uma propriedade emnull. O erro de tempo de execução mais frequente em JavaScript. - SyntaxError — estrutura de código inválida. Geralmente capturado em tempo de análise (parse time), mas pode aparecer em tempo de execução com
eval()ouJSON.parse(). - RangeError — um valor está fora de um intervalo permitido. Comum com recursão infinita (
Maximum call stack size exceeded) ou comprimentos de array inválidos. - URIError — uso incorreto de funções de manipulação de URI como
decodeURIComponent()com entrada malformada.
6. Padrões Robustos de Try/Catch
O tratamento de erros não se trata apenas de prevenir falhas — trata-se de fornecer feedback significativo e manter o estado da aplicação.
// Classes de erro personalizadas para erros específicos do domínio
class ApiError extends Error {
constructor(message, statusCode, responseBody) {
super(message);
this.name = 'ApiError';
this.statusCode = statusCode;
this.responseBody = responseBody;
}
}
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
// Use blocos catch específicos (ou verifique os tipos de erro)
async function submeterFormulario(formData) {
try {
validar(formData);
const resultado = await enviarParaApi(formData);
return resultado;
} catch (error) {
if (error instanceof ValidationError) {
mostrarErroCampo(error.field, error.message);
} else if (error instanceof ApiError) {
if (error.statusCode === 401) {
redirecionarParaLogin();
} else {
mostrarNotificacao('Erro no servidor. Por favor, tente novamente.');
}
} else {
// Erro inesperado — registrar e relatar
console.error('Erro inesperado:', error);
relatarParaServicoDeErros(error);
mostrarNotificacao('Algo deu errado.');
}
} finally {
// Sempre executa — limpar estados de carregamento
esconderSpinnerDeCarga();
}
}
Antipadrões de Tratamento de Erros a Evitar
- Blocos catch vazios —
catch (e) {}engole silenciosamente erros, tornando os bugs invisíveis. - Capturar muito amplamente — capturar todos os erros em um nível baixo impede que os chamadores os tratem apropriadamente.
- Não relançar — se você não pode tratar completamente um erro, registre-o e relance-o para que o chamador possa responder.
- Usar try/catch em torno de código síncrono que não pode lançar erros — encapsulamento desnecessário adiciona ruído.
7. Dicas e Fluxo de Trabalho de Depuração
Adote esses hábitos para depurar de forma mais rápida e sistemática:
- Reproduza primeiro — antes de corrigir qualquer coisa, encontre o menor conjunto de passos que reproduza o bug de forma confiável.
- Bisseccionar o problema — use declarações
debuggerou breakpoints para reduzir onde o comportamento inesperado começa. Comente seções de código para isolar o problema. - Leia a mensagem de erro — as mensagens de erro do JavaScript são mais informativas do que os desenvolvedores lhes dão crédito. Leia a pilha de chamadas completa, não apenas a primeira linha.
- Use o prettifier — se você estiver depurando código de produção, use um beautifier de JS para tornar o código minificado legível antes de definir breakpoints.
- Verifique a aba Network primeiro — muitos "bugs de JavaScript" são, na verdade, erros de API, recursos ausentes ou problemas de CORS.
- Escreva um teste que falhe — antes de corrigir o bug, escreva um teste que o reproduza. Isso garante que a correção funcione e previne regressões.
Conclusão
Depuração eficaz é uma habilidade que se compõe ao longo do tempo. Ao dominar a API do Console, alavancar breakpoints e o profiler de Desempenho, entender source maps e escrever padrões robustos de tratamento de erros, você passará menos tempo caçando bugs e mais tempo construindo recursos. Da próxima vez que encontrar um problema complicado, resista à tentação de espalhar console.log() por toda parte — use a ferramenta certa em vez disso.
Torne o JavaScript Minificado Imediatamente Legível
Depurando código de produção? Use o JS Beautify do Pan Tool para formatar JavaScript minificado em código fonte legível e devidamente indentado.