Diagnosticando erros de JavaScript mais rapidamente com o Error.stack

O IE10 no Windows 8 Consumer Preview inclui suporte para Error.stack, o que permite que os desenvolvedores da Web detectem e corrijam bugs com mais rapidez, especialmente aqueles bugs que são difíceis de reproduzir. Os desenvolvedores são capazes de criar aplicativos incríveis com os vastos recursos das plataformas Web fornecidos pelos navegadores mais modernos. No Windows 8, exploramos todo esse potencial através do Internet Explorer 10 e dos aplicativos estilo Metro no JavaScript. O potencial e a complexidade crescentes desses aplicativos significa que os desenvolvedores precisam de excelentes ferramentas como o Error.stack para lidar com os erros e diagnosticar bugs.

Depurando aplicativos

O trabalho com erros de maneira estruturada no JavaScript se baseia em throw e try/catch – no qual o desenvolvedor declara um erro e passa o fluxo de controle para uma porção do programa que lida com os erros. Quando um erro é emitido, Chakra, o mecanismo JavaScript no Internet Explorer, captura a cadeia de chamadas que indicou o local onde o erro surgiu - também referido como pilha de chamadas. Se o objeto emitido for um Error (ou uma função cuja cadeia protótipo indicou o Error), o Chakra criará um rastreamento de pilha (uma listagem legível da pilha de chamadas). A listagem é representada como uma propriedade, stack, no objeto Error. O stack inclui a mensagem de erro, nomes de função e informações do local do arquivo de origem das funções. Essas informações podem ajudar os desenvolvedores a diagnosticar defeitos rapidamente ao aprender qual função foi chamada e até mesmo em qual linha de código houve falha. Por exemplo, isso pode indicar que um parâmetro passado para a função era nulo ou de um tipo inválido.

Vamos tentar entender com um exemplo de script simples que tenta calcular a distância entre dois pontos, (0, 2) e (12, 10):

(function () {

'use strict';

function squareRoot(n) {

if (n < 0)

throw new Error('Cannot take square root of negative number.');

 

return Math.sqrt(n);

}

 

function square(n) {

return n * n;

}

 

function pointDistance(pt1, pt2) {

return squareRoot((pt1.x - pt2.x) + (pt1.y - pt2.y));

}

 

function sample() {

var pt1 = { x: 0, y: 2 };

var pt2 = { x: 12, y: 10 };

 

console.log('Distance is: ' + pointDistance(pt1, pt2));

}

 

try {

sample();

}

catch (e) {

console.log(e.stack);

}

})();

O script tem um bug que esquece de igualar as diferenças dos componentes. Como resultado, para algumas entradas, a função pointDistance retornará resultados incorretos. Em outras ocasiões, ocorrerá um erro. Para entender o rastreamento de pilha, vamos examinar o erro nas ferramentas de desenvolvedor F12 e olhar para a sua guia de script:

Captura de tela das ferramentas de desenvolvedor F12 mostrando um rastreamento de pilha registrado ao chamar o console.log(e.stack), no qual 'e' é o objeto Error passado para a cláusula catch de um bloco try/catch.

O rastreamento de pilha é despejado no console na cláusula catch e por estar no topo da pilha fica evidente que o erro tem origem na função squareRoot. Para depurar o problema, um desenvolvedor não precisaria ir muito fundo no rastreamento de pilha; a precondição do squareRoot foi violada. Observando um nível acima na pilha, fica claro o motivo: As subexpressões dentro da chamada para o squareRoot deveriam ser os parâmetros para square.

Durante a depuração, a propriedade stack pode ajudar a identificar o código para definir um ponto de interrupção. Lembre-se de que há outras maneiras de se visualizar uma pilha de chamadas: por exemplo, se você definir o depurador de script para o modo “Break on caught exception” (Quebra em exceção capturada), será possível inspecionar a pilha de chamada com o depurador. Para aplicações implantadas, é possível quebrar códigos problemáticos dentro de try/catch para capturar chamadas com erros e registrá-las em seu servidor. Os desenvolvedores poderiam, então, olhar para essa pilha de chamadas para ajudar a isolar áreas com problemas.

Exceções DOM e Error.stack

Mais cedo, observei que o objeto emitido deve ser um Error ou levado para um Error por meio de sua cadeia protótipo. Isso ocorre intencionalmente. O JavaScript oferece suporte para emissão de qualquer objeto e até mesmo de primitivos como exceções. Se por um lado tudo isso pode ser capturado e examinado, por outro, eles não são especificamente criados para conter erros ou informações de diagnóstico. Como resultado, apenas 'errors' serão atualizados com uma propriedade stack ao serem emitidos.

Mesmo que Exceções DOM sejam objetos, elas tão têm cadeias protótipos que levam ao Error e, portanto, não têm uma propriedade stack. Nas situações em que você estiver realizando uma manipulação DOM e quiser detectar erros de compatibilidade com JavaScript, você deverá quebrar o seu código de manipulação DOM dentro de um bloco try/catch e emitir um novo objeto Error dentro de uma cláusula catch:

function causesDomError() {

try {

var div = document.createElement('div');

div.appendChild(div);

} catch (e) {

throw new Error(e.toString());

}

}

No entanto, você deve ponderar se quer mesmo usar esse padrão. Em geral, isso é mais adequado para o desenvolvimento de uma biblioteca de utilitários. Leve em consideração se o objetivo de seu código é esconder uma manipulação DOM ou simplesmente executar uma tarefa. Se o objetivo for esconder uma manipulação DOM, quebrar a manipulação e emitir um Error, essa será provavelmente a melhor escolha.

Considerações de desempenho

A construção do rastreamento de pilha começa quando um objeto 'error' é emitido. Para isso, é necessário verificar a pilha de execução atual. Para evitar problemas de desempenho ao se deparar uma pilha particularmente grande (talvez até uma cadeia de pilha recursiva), por padrão, o IE acumulará apenas os 10 primeiros quadros da pilha. Essa definição é configurável, no entanto, ao se definir a propriedade estática Error.stackTraceLimit para outro valor. Essa definição é global e deve ser alterada antes de emitir o erro. Do contrário, não terá um efeito nos rastreamentos da pilha.

Exceções assíncronas

Quando um rastreamento de pilha é gerado de um retorno de chamada assíncrono (por exemplo, timeout, interval, ou XMLHttpRequest), será o retorno de chamada assíncrono, e não o código que criou o retorno de chamada assíncrono, que estará na parte mais baixa da pilha de chamada. Isso apresentará possíveis dificuldades para rastrear o código offending: Se você usar a mesma função de retorno de chamada para múltiplos retornos de chamadas assíncronos, você verá o quanto é difícil de se determinar apenas por inspeção qual retorno de chamada ocasionou o erro. Vamos modificar ligeiramente nosso exemplo anterior para que, em vez chamar o sample() diretamente, nós o colocaremos em um retorno de chamada de tempo limite:

(function () {

'use strict';

function squareRoot(n) {

if (n < 0)

throw new Error('Cannot take square root of negative number.');

 

return Math.sqrt(n);

}

 

function square(n) {

return n * n;

}

 

function pointDistance(pt1, pt2) {

return squareRoot((pt1.x - pt2.x) + (pt1.y - pt2.y));

}

 

function sample() {

var pt1 = { x: 0, y: 2 };

var pt2 = { x: 12, y: 10 };

 

console.log('Distance is: ' + pointDistance(pt1, pt2));

}

 

setTimeout(function () {

try {

sample();

}

catch (e) {

console.log(e.stack);

}

}, 2500);

})();

Depois da execução desse trecho, você observará o rastreamento de pilha surgir após um breve atraso. Dessa vez, você também verá que a parte mais baixa de uma pilha não é um Global Code, mas sim uma Anonymous function (função anônima). Na verdade, não será a mesma função anônima, mas uma função de retorno de chamada passada para setTimeout. Já que você perderá o contexto de ligação com o retorno de chamada, não será possível determinar o que está causando o retorno de chamada para ser chamado. Se levar em consideração um cenário em que um retorno de chamada é registrado para lidar com o evento click de muitos botões diferentes, você não será capaz de dizer à qual retorno de chamada o registro se refere. Dito isso, essa é apenas uma pequena limitação, porque, na maioria dos casos, o topo da pilha provavelmente realçará as áreas com problemas.

Explorando a demonstração de ''test drive'' :

Captura de tela da demonstração de ''test drive'' Explore Error.stack.

Veja esta demonstração de ''test drive'' usando o IE10 no Windows 8 Consumer Preview. Você pode executar um código no contexto de um eval, e se um erro ocorrer, você poderá inspecioná-lo. Se você estiver executando o código dentro do IE10, você também poderá realçar as linhas do seu código enquanto você passar o mouse sobre as linhas de erro no rastreamento da pilha. Você pode digitar códigos na área de códigos ou selecioná-los a partir de vários exemplos na lista. Você também pode definir o valor Error.stackTraceLimit ao executar exemplos de códigos.

Para acessar materiais de referência, você poderá consultar a documentação da MSDN em Error.stack e também stackTraceLimit.

—Rob Paveza, gerente de programa, Chakra Runtime