5 pontos por GN⁺ 2023-11-12 | 1 comentários | Compartilhar no WhatsApp
  • Se você já consegue usar as ferramentas de desenvolvedor do navegador em nível intermediário ou acima, pode expandir os breakpoints condicionais para além de simples condições de parada, usando-os como ferramentas de log, rastreamento, alteração de estado e medição de tempo
  • Se colocar APIs de console como console.log, console.count, console.trace e console.time em um breakpoint ou no Watch Pane, dá para registrar fluxo e estado sem interromper a execução
  • Combinando quantidade de argumentos de função, tempo, valores calculados de CSS, número de execuções, amostragem aleatória e booleanos globais, é possível controlar com muito mais precisão o momento em que a execução para
  • No Chrome, monitor, debug, copy, $0, getEventListeners e monitorEvents permitem lidar rapidamente com chamadas de função, DOM, eventos e operações de área de transferência direto no console
  • Em telas onde o DOM muda o tempo todo por JavaScript ou eventos do usuário, pausar o JS, usar setTimeout(debugger), snapshots do DOM e monitoramento de foco é especialmente útil para depuração de HTML/CSS

Expandindo breakpoints condicionais como ferramenta de depuração

  • Em breakpoints condicionais, dá para inserir não só condições verdadeiro/falso, mas também expressões JavaScript com efeitos colaterais

  • Com isso, um breakpoint comum pode ser usado como ferramenta de log, rastreamento, alteração de estado ou medição de tempo

  • Logpoints e tracepoints

    • Se você chamar console.log em um breakpoint condicional, ele funciona como um logpoint, registrando valores no console sem interromper a execução
    • Com console.count, também dá para contar quantas vezes aquela linha foi executada
    • Na atualização de maio de 2020, os principais navegadores já ofereciam suporte nativo a logpoints/tracepoints
  • Uso de APIs de console no Watch Pane

    • Também é possível colocar expressões como console.log no Watch Pane
    • Sempre que a aplicação parar no depurador, você pode tirar um snapshot do localStorage com console.table(localStorage)
    • Se combinar breakpoints de alteração do DOM com expressões no Watch, pode salvar snapshots em um array após mudanças na subárvore do DOM
    (window.doms = window.doms || []).push(document.documentElement.outerHTML)
    
    • Breakpoints de alteração do DOM não podem ser configurados para não interromper a execução
  • Rastreamento da pilha de chamadas

    • Se houver uma função que mostra um spinner de carregamento e outra que o oculta, e você estiver tentando descobrir por que só a chamada de exibição permanece, pode colocar console.trace em um breakpoint condicional
    • Ao clicar no chamador no último stack trace da função de exibição, você pode ir diretamente para o ponto da chamada
  • Alterando o comportamento do programa durante a execução

    • Ao sobrescrever argumentos de função na expressão do breakpoint condicional, você pode mudar na hora o comportamento do programa dentro do navegador
    • Por exemplo, dá para alterar o valor de id na função getPerson
    • Expressões como id=1, que são avaliadas como verdadeiras, fazem o depurador parar; se você não quiser interromper, pode acrescentar , false
  • Medição rápida de desempenho

    • Como o tempo de avaliação do breakpoint condicional entra na conta, isso não é ideal para profiling preciso de desempenho
    • Quando precisa de uma medição rápida, pode colocar console.time('label') na condição do ponto inicial e console.timeEnd('label') na do ponto final para registrar o tempo no console

Tornando as condições de parada mais precisas

  • Usando a quantidade de argumentos da função

    • Se quiser parar só quando a função atual for chamada com 3 argumentos, pode usar a condição abaixo
    arguments.callee.length === 3
    
    • Isso é útil em funções sobrecarregadas com argumentos opcionais
    • Se quiser parar apenas quando a quantidade de argumentos definidos for diferente da quantidade realmente passada, use a condição abaixo
    (arguments.callee.length) != arguments.length
    
    • Isso ajuda a encontrar bugs no ponto de chamada da função
  • Condições baseadas em tempo

    • Se você não quiser parar antes de 5 segundos após o carregamento da página, pode usar a condição abaixo
    performance.now() > 5000
    
    • Isso é adequado quando o breakpoint só interessa depois do carregamento inicial da página
    • Se quiser que o breakpoint não pare nos 5 segundos seguintes à sua configuração, mas passe a parar depois disso, pode guardar o tempo de referência em uma variável global
    window.baseline = window.baseline || Date.now(),
    (Date.now() - window.baseline) > 5000
    
    • Você pode reiniciar o contador no console com window.baseline = Date.now()
  • Condições baseadas no estado do CSS

    • Também é possível parar com base em valores calculados de CSS
    • Por exemplo, para parar somente quando a cor de fundo de document.body for vermelha, use a condição abaixo
    window.getComputedStyle(document.body).backgroundColor === "rgb(255,0,0)"
    
  • Número de execuções e amostragem

    • Você pode incrementar um contador a cada execução da linha e fazer a execução parar uma vez a cada duas
    window.counter = (window.counter || 0) + 1, window.counter % 2 === 0
    
    • Se quiser parar aleatoriamente apenas 1 vez em 10, use a condição abaixo
    Math.random() < 0.1
    
  • Nunca parar em um ponto específico

    • No Chrome, clicando com o botão direito na gutter e escolhendo “Never Pause Here”, é criado um breakpoint condicional sempre false, que impede a parada naquela linha
    • Isso pode ser usado para excluir uma linha específica de breakpoints XHR ou ignorar certas exceções
  • Atribuição automática de ID a instâncias

    • Você pode colocar um breakpoint condicional no construtor de uma classe para salvar cada instância em um array
    (window.instances = window.instances || []).push(this)
    
    • O ID único de uma instância específica pode ser verificado pelo índice no array
    window.instances.indexOf(instance)
    
  • Alternância global de breakpoints

    • Um ou mais breakpoints condicionais podem ser controlados por um booleano global
    • Você pode alterá-lo diretamente no console, em outro breakpoint ou via temporizador
    window.enableBreakpoints = true
    setTimeout(() => (window.enableBreakpoints = true), 5000)
    

Rastreamento de chamadas de funções e classes

  • Monitorando chamadas de métodos de classe

    • Com o método de linha de comando monitor do Chrome, é fácil rastrear chamadas de métodos de classe
    • Se quiser ver chamadas de método de todas as instâncias da classe Dog, pode aplicar monitor a todas as propriedades do prototype
    var p = Dog.prototype;
    Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));
    
    • O console exibirá informações de chamada como function bark called with arguments: 2
    • Se preferir interromper na chamada em vez de só registrar logs, use debug no lugar de monitor
  • Rastreando uma classe a partir de uma instância específica

    • Se você não souber o nome da classe, mas tiver uma instância, pode monitorá-la do mesmo jeito por meio do prototype do construtor
    var p = instance.constructor.prototype;
    Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));
    
    • Isso é útil ao criar funções que operam sobre instâncias arbitrárias sem depender de uma classe específica
  • Depurando logo antes de chamar uma função no console

    • Antes de chamar no console a função que deseja depurar, execute debugger; em seguida, faça Step into na próxima chamada para entrar na implementação da função
    debugger; fn(1);
    
    • Isso é útil quando dá preguiça de procurar onde a função foi definida ou quando fn é vinculada dinamicamente e você não sabe sua posição no código-fonte
    • No Chrome, também é possível chamar debug(fn) para fazer a execução parar dentro de fn sempre que ela for chamada

Depuração de mudanças de URL e leitura de propriedades

  • Parar antes do roteamento em SPAs

    • Para parar imediatamente antes de uma aplicação de página única mudar a URL, você pode conectar uma função com debugger em history.pushState, history.replaceState, window.onhashchange e window.onpopstate
    const dbg = () => {
      debugger;
    };
    history.pushState = dbg;
    history.replaceState = dbg;
    window.onhashchange = dbg;
    window.onpopstate = dbg;
    
    • Esta versão de dbg não inclui uma implementação que faça a pausa sem quebrar a navegação
    • Se window.location.replace ou window.location.assign forem chamados diretamente, a página será descarregada logo após a atribuição, então esse método não cobre esse caso
    • No Chrome, você pode usar debug nos métodos relacionados para ver a origem do redirecionamento e o estado naquele momento
    debug(window.location.replace);
    debug(window.location.assign);
    
  • Encontrando o ponto em que uma propriedade é lida

    • Se quiser saber o instante em que uma propriedade específica de um objeto é lida, pode colocar debugger dentro de um getter
    { get configOption() { debugger; return true; } }
    
    • Isso é útil para verificar como determinado código usa uma opção de configuração
    • Você pode aplicar isso alterando o código-fonte original ou usando um breakpoint condicional

Copiando dados pelo console

  • Com a API de console copy(), você pode copiar informações do navegador para a área de transferência sem truncamento de strings
  • Isso pode ser usado para copiar o snapshot atual do DOM, metadados de recursos como imagens, grandes blobs de JSON ou dumps de localStorage
copy(document.documentElement.outerHTML)
copy(performance.getEntriesByType("resource"))
copy(JSON.parse(blob))
copy(localStorage)

Depuração de HTML/CSS

  • Inspecionando o DOM com a execução do JS pausada

    • No DOM inspector, pressionando ctrl+\ no Chrome/Windows, você pode pausar a execução do JS a qualquer momento
    • Mesmo em situações em que o JS altera o DOM ou eventos como mouseover também o alteram, é possível inspecionar com estabilidade o snapshot atual do DOM
  • Inspecionando elementos que desaparecem

    • Se um elemento condicional do DOM desaparecer ao mover o mouse, você pode inspecioná-lo iniciando um temporizador de 5 segundos no console para executar o depurador
    setTimeout(function() { debugger; }, 5000);
    
    • Se a UI aparecer durante esses 5 segundos e a execução do JS parar depois disso, o elemento não desaparecerá, permitindo que você mude para as ferramentas de desenvolvedor e o inspecione
    • Enquanto a execução do JS estiver pausada, é possível inspecionar elementos, editar CSS e executar comandos no console JS
    • Isso é eficaz para inspecionar DOMs que dependem da posição do cursor ou do foco
  • Registrando snapshots do DOM

    • Para copiar o DOM atual, use o comando abaixo
    copy(document.documentElement.outerHTML);
    
    • Para salvar a string do DOM em um array a cada 1 segundo, use setInterval assim
    doms = [];
    setInterval(() => {
      const domStr = document.documentElement.outerHTML;
      doms.push(domStr);
    }, 1000);
    
    • Em vez de salvar no array, você também pode imprimir continuamente no console
  • Monitorando o elemento com foco

    • Você pode comparar document.activeElement periodicamente e registrar no console as mudanças de foco
    (function () {
      let last = document.activeElement;
      setInterval(() => {
        if (document.activeElement !== last) {
          last = document.activeElement;
          console.log("Focus changed to: ", last);
        }
      }, 100);
    })();
    
  • Encontrando elementos em negrito

    • É possível encontrar elementos cujo fontWeight calculado seja "bold" ou "700"
    const isBold = (e) => {
      let w = window.getComputedStyle(e).fontWeight;
      return w === "bold" || w === "700";
    };
    Array.from(document.querySelectorAll("*")).filter(isBold);
    
    • Se quiser procurar apenas entre os descendentes do elemento atualmente selecionado no inspector, pode usar $0 como base
    Array.from($0.querySelectorAll("*")).filter(isBold);
    
  • Trabalhando com o elemento selecionado e eventos

    • No console, $0 é uma referência automática ao elemento atualmente selecionado no Element Inspector
    • No Chrome e no Edge, você pode acessar o elemento inspecionado anteriormente com $1, e o anterior a ele com $2
    • No Chrome, é possível verificar os event listeners do elemento atualmente selecionado
    getEventListeners($0)
    
    • Para depurar todos os eventos do elemento selecionado, use monitorEvents($0)
    • Se quiser ver apenas grupos específicos de eventos, pode passar um array
    monitorEvents($0, ["control", "key"])
    

1 comentários

 
GN⁺ 2023-11-12
Opiniões do Hacker News
  • Nas últimas décadas, as ferramentas de depuração embutidas nos navegadores evoluíram muito
    Como alguém que usa JS há muito tempo, sou profundamente grato às pessoas que tornaram a depuração de código dentro do navegador tão intuitiva
    Quando vou para outras áreas de desenvolvimento, como backend ou outras linguagens, sinto falta desse ecossistema de ferramentas de depuração que os navegadores modernos oferecem por padrão

    • Também quero aplaudir quem criou a extensão Firebug, que realmente elevou o nível das ferramentas de desenvolvedor dos navegadores
    • Tenho curiosidade sobre o que, em especial, parece tão excepcional na experiência de depuração em JS
      Depurei bastante em Python, Java e C++ por anos, mas nunca senti falta de ferramentas
      Claro, já vi muita gente que não conhece as opções disponíveis nessa área
    • Não é perfeito, mas é muito melhor do que a experiência de depurar Node.js no VS Code que um colega desenvolvedor defendia
      Prefiro o console àquele inferno
  • setTimeout(function() { debugger; }, 5000); é uma sacada inteligente
    No fim, a única maneira de vencer a pilha recursiva de tartarugas em que o depurador do Chrome depura a si mesmo é a instrução debugger
    O sam.pl do infame worm Sammy do Myspace também usava armadilhas de depuração para impedir que visitantes destrinchassem a página inicial em HTML ofuscado

    • É um dos bookmarklets que uso com frequência: https://darekkay.com/blog/debugging-dynamic-content/
    • Isso acontece quando um site bloqueia o painel lateral Console/DevTools aberto
      Ele roda o loop principal inserindo instruções debugger; irritantes aqui e ali, executando em 30 FPS / intervalos de 32 ms, tornando o DevTools inútil
      Porque não há como “ignorar” a instrução debugger
    • Se você não quiser esperar 5 segundos, pode usar o atalho de pausar a execução do script, cmd-\
      A página para na linha de JS que estava em execução no momento, e você pode inspecionar o elemento problemático como de costume
    • Gostaria que os fabricantes de navegadores deixassem isso como uma opção ativada explicitamente no DevTools e, por padrão, ignorassem instruções debugger
      A maioria dos casos legítimos de uso de debugger pode ser substituída por um breakpoint simples se você estiver trabalhando no seu próprio código
    • Espera, é só isso? Simples demais, mas bom demais
  • É notável a ausência de queryObjects
    É uma API bem insana que retorna a lista de todos os objetos criados por um determinado construtor
    Por exemplo, com queryObjects(Function) você consegue obter a lista de todas as funções no heap
    Ela retorna até funções “private” que estão dentro de algum módulo

    • Sinceramente, dá para justificar não querer divulgar isso
      É um recurso realmente insano e provavelmente exclusivo do Chromium
  • Nunca consigo usar variáveis monitoradas direito
    As regras de escopo e atualização são muito indecifráveis, e até parece que só dá para monitorar variáveis globais, mas nem isso funciona como esperado; no fim, quando estou testando, acabo enchendo os valores de logs
    Há anos penso que o console deveria incluir uma UI no estilo Data.gui [1], para permitir ver e testar variáveis e valores de configuração
    Dá para ver funcionando neste CodePen [2]

    1. https://github.com/dataarts/dat.gui
    2. https://codepen.io/russellbeattie/full/kGxaqM
    • Senti a mesma frustração
      Em teoria, os navegadores têm muitos recursos excelentes de depuração, mas na prática eles não parecem funcionar de forma confiável
      Nem consigo fazer com que todos os breakpoints sejam acionados de maneira confiável
      Quando o código está aberto, tudo bem, mas assim que ele é empacotado, mesmo sem minificação, muitos recursos do depurador parecem quebrar
      Como não sou engenheiro de frontend, talvez eu esteja fazendo algo errado
    • É uma boa desligar os source maps no depurador
      Eles provavelmente são a causa da frustração, e ainda não entendo por que ficam ativados por padrão, já que a experiência de depurar ativamente com eles ligados é tão ruim
    • No painel lateral do Chrome, variáveis minificadas aparecem com o nome correto, mas em watchpoints dá erro dizendo que não foram definidas, o que é irritante
  • Seria bom se houvesse uma forma de acessar variáveis locais de uma IIFE sem pausar a execução do código dentro do escopo da IIFE
    Será que existe um jeito de convencer o debugger a fazer isso?

    • IIFE é uma expressão de função invocada imediatamente
      https://en.m.wikipedia.org/wiki/Immediately_invoked_function...
    • Se você entrou em um breakpoint em uma função chamada pela IIFE, basta subir pela stack
      Se estiver completamente fora da IIFE, eu diria que é impossível
      A variável que você procura pode nem existir na memória antes ou depois da execução
      JavaScript não tem variáveis estáticas como outras linguagens, então as variáveis internas são descartadas a cada chamada da IIFE
      JavaScript tem um modelo de thread única bastante forte, a menos que você use algo como web workers; portanto, se você não estiver criando diretamente uma condição de corrida com chamadas async/await, é difícil até sustentar a ideia de que tal variável continue na memória fora do escopo da IIFE
      Se você abusar de var e inicializar a variável fora do escopo da IIFE, dá para ver o valor criado pela última chamada da IIFE
    • Uns 12 anos atrás, criei uma ferramenta que fazia isso
      Precisa de alguns ajustes, mas o retorno pelo esforço é grande
      https://github.com/kristopolous/_inject
      Ela permite circular por praticamente qualquer contexto de função em momentos arbitrários e ver o que estava acontecendo
      O método é abusar do contador de referências para impedir que o contexto seja destruído
      Quando eu fazia muito JS do lado do cliente, era realmente útil
      A versão killer app disso, bem feita, seria abrir um REPL em qualquer contexto
      Mesmo no estado atual, é preciso bastante habilidade para usar bem
    • O que você está procurando é um logpoint?
      https://firefox-source-docs.mozilla.org/devtools-user/debugg...
      Edit: acabei de perceber que isso é literalmente a primeira coisa abordada no artigo linkado
    • No Chrome, dá para inspecionar o closure descrito em https://news.ycombinator.com/item?id=38226743#38231705 pelo painel Watch e então ver a pseudo-propriedade [[Scopes]]
      No Firefox, não parece haver uma forma
  • Para completar, dá para recomendar o Werkzeug
    Uso no desenvolvimento de backend com Django e é extremamente útil
    Ele permite usar um shell “PDB” dentro do navegador, a qualquer hora e em qualquer lugar em que ocorra uma exceção

    • É fácil: basta colocar 1/0 no ponto em que você quer abrir o Werkzeug no navegador
    • Gostaria de ter essa experiência em qualquer lugar, incluindo nose.js e o navegador
      É muito simples e intuitivo
    • Ainda é preciso usar django-extension/runserver_plus?
  • Um truque que sempre uso é buscar nos scripts carregados por uma string da UI para debugar
    Vá ao painel Network, comece a registrar as requisições de rede, abra a barra lateral esquerda e procure pelo código ou pela string da UI que você quer encontrar
    Normalmente ela aparece dentro de algum arquivo estranho de chunk JS empacotado; ao clicar no resultado, a requisição de rede desse arquivo é aberta
    Clique com o botão direito em qualquer lugar dentro do arquivo e escolha algo como “Open in Sources”, e você irá para o debugger
    Agora é só colocar uma instrução debugger; nesse processo, os source maps provavelmente também serão carregados

  • Trabalhei por muito tempo como programador Python/Elixir e usei muito pdb.set_trace() e IEx.pry()
    Recentemente herdei um backend NodeJS muito bagunçado e estou arrancando o pouco cabelo que me resta sem ferramentas de debug adequadas
    Voltei a debugar com console.log, mas me sinto um homem das cavernas
    Não consigo acreditar que um ecossistema tão popular não tenha um REPL de debug decente; alguém pode me dizer para onde devo ir?

    • Tem, sim
      Use node --inspect-brk, e você pode se conectar com o VS Code ou o Chrome DevTools
      A parte complicada é se alguma ferramenta de build, como a transformação TypeScript, fica antes do comando nodejs
      Se você estiver simplesmente executando node, é bem fácil
      O REPL de debug é o próprio console JavaScript
    • Usando as ferramentas de desenvolvedor do VSCode, dá para debugar NodeJS de forma bastante sólida
      Ao adicionar nas opções de debug, você pode marcar breakpoints e executar passo a passo para dentro das funções
      Como quase tudo é encapsulado em objetos, JavaScript é bem agradável de debugar
    • Como outros disseram, o que você quer é --inspect ou --inspect-brk
      No VSCode, também dá para usar o comando “open JavaScript debug terminal”
      Esse comando abre um terminal no qual qualquer comando node já é iniciado automaticamente com o debugger anexado
    • Talvez não seja suficiente, mas acabei de conhecer o python snoop
      https://pypi.org/project/snoop/
      É um decorator para rastreamento profundo
    • O ponto bom no REPL do Elixir, que me parece parecido com o REPL do Erlang, é que ele permite conectar-se a um processo em execução
      No ecossistema Node JS, nunca vi esse recurso
  • Na seção “Debugging Property Reads”, fiquei curioso sobre como usar um breakpoint condicional para transformar {configOption: true} em {get configOption() { debugger; return true; }}

    • Isso não fica sob o título de breakpoints condicionais
      Basta sobrescrever o valor com um getter no console; se você tiver permissão de escrita, também pode alterar diretamente no código-fonte
  • Parece algo que eu definitivamente deveria aprender, mas fico me perguntando se existe algum livro ou material de estudo dedicado a isso.
    Ou será que não tem outro jeito além de mergulhar sem parar em desenvolvimento web e front-end?

    • Recomendo muito acompanhar a série “What’s new in DevTools” da equipe do Chrome.
      Quando você está no meio do trabalho, pode não dar muita vontade de clicar em um link para ler as notas de versão, mas vale muito a pena passar só 5 minutos dando uma olhada sempre que sai uma nova versão.
      Também há vídeos curtinhos e fáceis de consumir, que dão uma visão geral rápida.
      O objetivo é apresentar novos recursos, mas, pela minha experiência, isso também ajuda bastante a entender as limitações atuais da ferramenta.