Depurar código é uma habilidade essencial para qualquer desenvolvedor. Embora o console.log seja o método mais comum e rápido, ele tem limitações: polui o código, exige remoção posterior e nem sempre fornece o contexto necessário. Nesta aula, você aprenderá técnicas profissionais de debugging em JavaScript, usando ferramentas do navegador e métodos avançados do console para identificar e corrigir bugs de forma mais eficiente.

Vamos explorar desde os métodos menos conhecidos do console até o uso de breakpoints e a análise de stack traces, passando pela instrução debugger. Ao final, você terá um arsenal completo para depurar qualquer aplicação JavaScript, seja no front-end ou no back-end (Node.js).

console além do log

O objeto console oferece muito mais do que log. Métodos como info, warn e error permitem categorizar mensagens, facilitando a filtragem no DevTools. Por exemplo, console.error exibe a mensagem com um ícone vermelho e inclui um stack trace, essencial para erros. Já console.warn mostra um aviso amarelo, útil para alertas não críticos.

Além disso, console.table formata arrays e objetos em uma tabela legível, ideal para inspecionar dados complexos. console.group e console.groupCollapsed organizam logs em grupos expansíveis, mantendo o console limpo. console.time e console.timeEnd medem a duração de operações, essenciais para análise de desempenho. E console.assert só exibe uma mensagem se uma condição for falsa, permitindo testes rápidos.

// Exemplo de uso dos métodos do console
const usuarios = [
  { nome: 'Ana', idade: 25 },
  { nome: 'Bruno', idade: 30 },
  { nome: 'Carla', idade: 28 }
];

console.info('Lista de usuários:');
console.table(usuarios);

console.warn('Atenção: dados de teste!');

console.time('loop');
for (let i = 0; i < 1000000; i++) { /* simulação */ }
console.timeEnd('loop');

console.assert(usuarios.length === 3, 'Número de usuários diferente de 3');

console.group('Detalhes do primeiro usuário');
console.log('Nome:', usuarios[0].nome);
console.log('Idade:', usuarios[0].idade);
console.groupEnd();

Breakpoints no DevTools

Breakpoints são pontos de parada no código que interrompem a execução em um momento específico, permitindo inspecionar variáveis, chamar funções e avançar passo a passo. Diferente do console.log, os breakpoints não exigem alteração no código e podem ser adicionados diretamente no painel Sources do DevTools. Basta clicar no número da linha desejada para criar um breakpoint.

Além dos breakpoints tradicionais, o DevTools oferece breakpoints condicionais, que só pausam quando uma expressão é verdadeira, e breakpoints de exceção, que pausam quando um erro é lançado. Durante a pausa, você pode inspecionar o escopo, observar variáveis específicas e até avaliar expressões no console. Isso dá um controle muito maior sobre a depuração, especialmente em fluxos complexos.

// Exemplo de código para testar breakpoints
function calcularTotal(produtos) {
  let total = 0;
  for (let produto of produtos) {
    total += produto.preco; // Adicione um breakpoint aqui
  }
  return total;
}

const carrinho = [
  { nome: 'Notebook', preco: 3000 },
  { nome: 'Mouse', preco: 150 },
  { nome: 'Teclado', preco: 200 }
];

console.log(calcularTotal(carrinho));

Stack traces

Quando um erro é lançado, o JavaScript gera um stack trace, que é a sequência de chamadas de função que levaram ao erro. Essa informação é crucial para rastrear a origem do problema, especialmente em aplicações com muitas funções aninhadas. Você pode ver o stack trace no console quando um erro não é tratado, ou capturá-lo programaticamente usando console.trace().

Analisar um stack trace envolve ler de baixo para cima: a primeira linha indica a função onde o erro ocorreu, e as linhas seguintes mostram as chamadas que levaram até lá. Isso ajuda a identificar se o erro é causado por dados inválidos, chamadas incorretas ou problemas em funções externas. Em aplicações complexas, um stack trace bem interpretado pode economizar horas de depuração.

function funcaoA() {
  funcaoB();
}

function funcaoB() {
  funcaoC();
}

function funcaoC() {
  console.trace('Rastreando chamadas');
  throw new Error('Erro proposital');
}

try {
  funcaoA();
} catch (erro) {
  console.error(erro.stack);
}

debugger

A instrução debugger é uma ferramenta que, quando executada, pausa a execução do código no ponto exato, desde que o DevTools esteja aberto. É como um breakpoint programático, mas adicionado no código fonte. Isso é útil quando você precisa pausar em uma condição específica ou quando está depurando código que não pode ser facilmente modificado no DevTools (por exemplo, em Node.js).

Para usar, basta inserir a palavra debugger; no local desejado. Quando o interpretador chegar lá, o depurador será ativado. No Node.js, você pode usar junto com o inspetor (por exemplo, node inspect app.js). Lembre-se de remover as instruções debugger antes de publicar o código, pois elas podem causar pausas indesejadas em produção.

function processarDados(dados) {
  // Simulando processamento complexo
  let resultado = dados.map(item => item * 2);
  debugger; // Pausa aqui para inspecionar 'resultado'
  return resultado;
}

const dados = [1, 2, 3, 4];
console.log(processarDados(dados));

Boas práticas e observações finais

Para uma depuração eficiente, combine várias técnicas: use console.table para dados tabulares, breakpoints para fluxos complexos e stack traces para erros inesperados. Evite deixar logs de depuração no código final; se precisar, use um sistema de logging condicional. No DevTools, familiarize-se com os atalhos (F8 para pausar, F10 para avançar).

Lembre-se de que a depuração é uma habilidade que melhora com a prática. Sempre que encontrar um bug, tente entender a causa raiz em vez de aplicar correções superficiais. Use os breakpoints para explorar o estado das variáveis e o fluxo de execução, e os stack traces para rastrear a origem do problema. Com essas ferramentas, você se tornará um desenvolvedor muito mais produtivo.

Referências

Exercícios

  1. Escreva uma função que receba um array de números e retorne a soma, mas use console.table para exibir o array e um console.time/console.timeEnd para medir o tempo de execução. Depois, execute no console do navegador e observe a saída.

    ✓ Resposta:
    function somarNumeros(numeros) {
      console.table(numeros);
      console.time('soma');
      let total = 0;
      for (let num of numeros) {
        total += num;
      }
      console.timeEnd('soma');
      return total;
    }
    
    somarNumeros([1, 2, 3, 4, 5]);
  2. Crie um código que lance uma exceção dentro de uma função chamada por outra, e use console.trace() dentro da função interna para rastrear a sequência de chamadas. Observe o stack trace no console.

    ✓ Resposta:
    function externa() {
      interna();
    }
    
    function interna() {
      console.trace('Debug do stack trace');
      throw new Error('Erro na função interna');
    }
    
    try {
      externa();
    } catch (e) {
      console.error(e.stack);
    }
  3. Explique a diferença entre console.log e console.error no contexto de depuração. Dê um exemplo de quando usar cada um.

    ✓ Resposta: console.log é para mensagens gerais de depuração, sem ênfase especial. console.error exibe a mensagem em vermelho e inclui um stack trace, sendo ideal para erros e situações excepcionais. Use console.log para acompanhar o fluxo normal e console.error para capturar falhas, facilitando a filtragem no console.
  4. Descreva um cenário em que você preferiria usar um breakpoint condicional em vez de um console.log dentro de um loop. Implemente esse cenário com um exemplo de código e explique como você configuraria o breakpoint.

    ✓ Resposta: Se você tem um loop que itera 1000 vezes e quer inspecionar apenas quando o índice for 500, um breakpoint condicional evita parar em todas as iterações. No DevTools, você adiciona um breakpoint na linha do loop e clica com o botão direito para editar a condição, inserindo i === 500. Exemplo:
    for (let i = 0; i < 1000; i++) {
      // breakpoint condicional aqui: i === 500
      console.log(i);
    }
  5. Escreva um trecho de código com a instrução debugger e explique como você a usaria para inspecionar variáveis em um cenário de depuração no navegador.

    ✓ Resposta:
    function calcularDesconto(preco, percentual) {
      let desconto = preco * (percentual / 100);
      debugger; // Pausa aqui para inspecionar 'preco', 'percentual' e 'desconto'
      return preco - desconto;
    }
    
    console.log(calcularDesconto(100, 10));
    Com o DevTools aberto, a execução pausa na linha do debugger, permitindo passar o mouse sobre as variáveis para ver seus valores, ou avaliar expressões no console.