Archive for fevereiro \21\UTC 2012

Bug de ordenação de hashs no Chrome

fevereiro 21st, 2012

Aloha,

A algumas semanas venho trabalhando bastante com JavaScript (tanto para projetos pessoais como profissionais) e esbarrei em um problema intrigante … que depois de alguma pesquisa vi que é algo polêmico.

Trabalhando em algumas melhorias para o Sooner, uma extensão para Chrome para trabalhar com o ReadItLater, uma das issues requeridas pelo pessoal era que ao inserir uma nova página ao serviço, a listagem mantivesse a ordenação colocando o último item adicionado sempre no topo. O ReadItLater, que vou abreviar para RIL,  trabalha com uma API que trafega dados de duas formas: JSON ou XML.

Obviamente que a escolha foi o JSON devido à enorme facilidade de manipulação. Depois de adicionar uma nova página e chamar o serviço de recuperação das páginas, o RIL me retorna as páginas como no exemplo (extremamente simplificado) abaixo:

{
   "list":{
      "2":{
         "url":"http://url.com",
         "time_updated": "20120220180000",  
      },
      "1":{
         "url":"http://google.com",
         "time_updated": "20120219180000",
      }
   }
}

Neste exemplo, a coleção list contém um hash com dois objetos cujos índices são números com uma ordenação definida pelo RIL de acordo com o campo time_update: ele sempre retorna os registros ordenados pela data de atualização em ordem decrescente.

Ok! Nada de interessante até aí. Vou fazer uma iteração na lista com um for … in e renderiza-los normalmente. Usei o seguinte código:

1
2
3
4
for (var pageIndex in ril.list) {
var page = ril.list[pageIndex];
document.write(page.url);
}

Para minha surpresa o resultado foi:

  • http://google.com
  • http://url.com

Ou seja, o Chrome ao executar a iteração nos objetos converteu os indices para numéricos e re-ordenou a lista, ignorando a ordem em que eles estavam. Para fazer o teste de São Tomé, abra o Javascript Console do seu Chrome (no Mac é Command + Option + J ou pelo menu View > Developer > JavaScript Console), copie e cole o seguinte código:

1
2
var lista = {"2": "2", "1":"1", "a":"a"};
for (var index in lista) { console.log(index) };

O resultado esperado seria esse:

  • 2
  • 1
  • a

Mas o resultado será esse:

  • 1
  • 2
  • a

Agora, abra o console do Safari ou do Firefox, coloque o mesmo código e faça o teste. A iteração dá certo! :D

Seria esse um mole do Chrome ou eu que estava fazendo algo errado?

O problema polêmico

Depois de pesquisar um pouco me defrontei com duas issues (#164 do  V8 JavaScript Engine que roda dentro do Chrome e #37404 do projeto Chromium) que discorrem bastante sobre o problema, com pessoas defendendo e argumentando de forma fervorosa vários pontos das RFCs e padrões do ECMA Script 262 (usado como base  das engines JavaScript na maioria dos browsers modernos) fazendo contrapontos com a questão da necessidade de manter uma compatibilidade pelo bem da web como um todo. Basicamente, o que é discutido numa cronologia mais didática é:

  • A (especificação) ECMA-262 não especifica uma ordem de enumeração. O Chrome respeita a ordem de um hash exceto se existirem indices numéricos, onde ele tenta tenta transformar o indice num integer e ordena as iterações de acordo com esse resultado.
  • De acordo com a especificação do Javascript, “A for…in loop iterates over the properties of an object in an arbitrary order” (https://developer.mozilla.org/en/JavaScript/Reference/Statements/For…in).
  • De acordo com a RFC do JSON (lembrado bem pelo @jeffersongirao), ”An object is an unordered collection of zero or more name/value pairs” (http://www.ietf.org/rfc/rfc4627.txt)
  • Ao mesmo tempo, de acordo com o ECMA-262 (12.6.4) sobre for…in ”The mechanics and order of enumerating the properties (step 6.a in the first  algorithm, step 7.a in the second) is not specified.“ Ou seja, a implementação da enumeração não é especificada e por isso dependente de quem o implementa. O pessoal do Chrome então resolveu implementar ao pé da letra (de modo evasivo ou não) enquanto o pessoal dos outros navegadores decidiu fazer de outra forma.
  • Há de fato uma interpretação que cada navegador decidiu fazer de um jeito que acha bacana e certo, mas todos concordam que manter a compatibilidade seria uma boa idéia.
  • No caso do Chrome, todos enxergam isso como um “bug” pois a maioria dos outros browser modernos tem ido na direção de ditar o que seria o modo standard de iterar num hash mantendo sua ordem
  • Esse “bug” tem trazido muita dor de cabeça para o pessoal pois força a manter implementações alternativas ou corrigir comportamentos que devem utilizar essa forma de iterações em hashs ordenados
  • Até a presente data, o “problema” não foi resolvido nem existe posicionamento do pessoal do Chrome para corrigi-lo. Talvez numa revisão do ECMA ou versão nova.

Recomendo a quem quiser ler e tirar sua própria opinião, ler esses dois tópicos (issue #164 e issue #37404): é uma discussão muito bacana e velha (desde 2008). Inclusive o John Resig, criador do jQuery, fala sobre esse e outros bugs num post de 2008.

Importante notar que num recente post do pessoal do Chromium sobre o Harmony, uma versão nova/revisada do ECMA Script que está sendo feita em conjunto com o comitê do ECMA desde 2008, várias novas features foram apresentadas mas uma em especial ainda está indefinida. Advinha qual é? :P

Como resolver

Bom, com um pepino desses para descarcar, existem algumas alternativas para contornar o problema:

  1. Quando trabalhar com hashs com indices numéricos, adicione um _ (underline) às chaves para manter a ordenação, como abaixo:

     var lista = {"_2": "2", "_1":"1", "a":"a"};

    Isso evitará que o Chrome faça a conversão para inteiro e assim manterá a ordenação no melhor estilo gambi design patterns.

  2. Trabalhe com arrays de objetos ao invés de hashs com indices numéricos. Se você conseguir ter controle na geração do dicionário de dados e precise iterar mantendo a ordem dos itens, transforme seu hash com indices num array de objetos  como abaixo:

    var lista = [{"2": "2"},{"1":"1"},{"a":"a"}];

  3.  Se não puder alterar a forma de trabalho de sua array, como por exemplo um resultado que vem de um webservice de terceiros, tente trabalhar de uma forma alternativa como trabalhar os dados em XML. :P
  4. Se não puder fazer nenhum das alternativas acima, senta e chora.

Resumo da ópera

Infelizmente, não existe outra forma se não burlar o bug ou refatorar seu programa para que evite trabalhar com hashs com indices numéricos caso você precise trabalhar com eles numa ordem pré-definida.

Vale a pena acompanhar essa nova proposta do ECMA e torcer para que eles definam de uma vez a forma padrão e mais ainda para que seja mantida essa forma que foi de certa forma colocada como standard pelos browsers.

Assim como o W3C vem dando cabeçada atrás de cabeçada para liberar de uma vez novas especificações da HTML e CSS, acho importantíssimo e crítico que o próprio mercado possa “dar a real” e consiga colocar o que interessa para os desenvolvedores como base de aprovação para o novo ECMA.

Simbora. :)

Sooner 0.8 lançado

fevereiro 5th, 2012

Aloha pessoal,

Depois de um hiato bem grande, voltemos à programação normal. :)

A algum tempo lancei uma extensão pro Google Chrome chamada Sooner para ver e adicionar páginas à minha conta do ReadItLater. O ReadItLater é um serviço bem conhecido e antigo para guardar urls de todo o tipo e “ler depois”, como o próprio nome diz, e oferece uma API para criação de aplicativos que usem o serviço. O grande problema era que não havia uma forma rápida e direta de adicionar, ler e remover páginas em nenhum navegador. No Chrome existiam opções que eram bacanas mas não agradavam ou na usabilidade ou no funcionamento.

Com isso, e a curiosidade de fazer uma extensão pro Chrome pra aprender e brincar um pouco, nasceu o Sooner sem muita pretensão até que algum tempo atrás vi que várias pessoas eram feedbacks legais e tinha sido incluído na páginas e extensions do serviço. :)

Para baixar, basta acessar o link https://chrome.google.com/webstore/detail/mifafnghbieophofjinbniahjpiodpnm e pedir para instalar. Depois é só logar com sua conta do RIL (se não tiver ainda uma conta cadastre em http://readitlaterlist.com/signup) e pronto. :)

As novidades

Em relação à versão 0.7, foram adicionadas features que o pessoal havia pedido.

  • link para recarregar a lista, pois o RIL pode ser usado através de vários aplicativos e recarregar a lista pode ser necessário em algum momento
  • campo de busca, para buscar páginas da lista através de uma palavra ou termo. A busca está bem simples e buscando pelo titulo mas possivelmente no futuro procure na URL também
  • correção de um bug na listagem, pois o Chrome (isso vai render até um post) sempre re-ordenava os itens que vinham do RIL.
  • um novo design do tipo, para deixar os itens de adicionar, busca e recarregar lista em lugares de uso mais simplificado e direto.
Uma lista de novas features vem por aí, mas algumas já foram escolhidas como:
  • lista ordenável por título, link, páginas mais novas ou velhas
  • criação automática de conta através da página de opções
  • versão “text only” dos links, que é um serviço do RIL bem bacana.
Se curtir, dá uma instalada lá e experimenta! Se não, dá um macaquinho para trás. hehehe
Simbora.
Get Adobe Flash playerPlugin by wpburn.com wordpress themes