Isto é um bug no Javascript?

17 de agosto de 2009  |  Javascript  | 
2279116910_4d699d91cb

Foto de Dustin Diaz

Javascript é uma linguagem controversa. Muitos programadores adoram trabalhar com ela, mas com certeza o número de programadores que a odeiam é maior ainda. Não importa se você está no primeiro ou no segundo grupo, se você é um desenvolvedor web simplesmente não pode evitá-la.

Eu particularmente sempre estou em cima do muro quando o assunto é Javascript, já passei por momentos bem agradaveis criando coisas com esta linguagem, mas também já me irritei com algumas caracteristicas ruins da sua implementação em muitas outras ocasiões.

O fato é que para se dar bem com Javascript é importante conhecer um pouco mais da linguagem do que o usual. Mas a maioria dos programadores só estão interessados em conhecer o suficiente da linguagem para realizar pequenas tarefas e criar alguns scripts simples (Ctrl+C e Ctrl+V). Assim, quando alguma coisa imprevista acontece na execução do código, logo jogamos a culpa no pobre Javascript.

Em uma dessas madrugadas em claro, eu comecei a enumerar alguns trechos de código que parecem bugs, mas não são. Segue uma explicação sobre cada um deles e algumas dicas para evitar resultados inesperados. E antes que atirem pedras em mim, os subtítulos não devem ser levados a sério.

Javascript não sabe somar

>>> 0.1 + 0.2
0.30000000000000004

Números em Javascript são representados internamente com 64 bits e cada número pode ter no máximo 17 dígitos significativos. Isto tem algumas consequências interessantes como demonstrado no exemplo acima. É preciso ser bem cuidadoso com certos cálculos, principalmente quando a precisão é algo muito importante para o aplicativo.

Javascript não sabe somar – parte 2

>> "3" + 4 + 5
=> 345

>> 3 + 4 + "5"
=> 75

No Javascript, o operador de soma (+) é usado tanto para somar valores numéricos como também para concatenar strings. Precisamos estar atentos a seguinte regra: durante uma operação de soma, quando um dos valores é do tipo string, então tudo é convertido para este mesmo tipo e uma concatenação é realizada.

No primeiro exemplo, como iniciamos a soma com um string, todos os valores seguintes são imediatamente convertidos para este mesmo formato e depois são concatenados. Já no segundo exemplo, temos inicialmente uma soma numéria (3 + 4 = 7) que é realizada normalmente, mas como a próxima soma a ser realizada não será com outro valor número, mas sim com o string "5", então o Javascript realiza a conversão do primeiro resultado e em seguida realiza uma concatenação com os valores, devolvendo no final o string "75".

Javascript não sabe converter string para números

>>> parseInt("010")
8

>>> parseInt("010", 10)
10

O método parseInt é muito utilizado e tem por objetivo converter strings em números inteiros. Basicamente o método aceita dois parâmetros: o primeiro é um string contendo o valor a ser convertido e o segundo é um inteiro que representa a base (decimal, binário, hexadecimal, etc.) na qual o string em questão está sendo representado.

O problema aqui é que alguns livros e sites costumam explicar que o valor padrão da base (para quando ele não é informado) é 10, o que não é necessariamente uma verdade. Quando não informamos a base (radix) ou a definimos com o valor 0, então o método utiliza a seguinte regra:

  • Se o string que estamos convertendo iniciar com "0x", então o método assumirá a conversão com a base 16 (hexadecimal).
  • Se o string começar com "0" (como no primeiro exemplo) a base utilizada será 8 (octal).
  • Se o string iniciar com qualquer outro valor numérico, então a base padrão será 10 (decimal).
  • E se o primeiro caractere do string não puder ser convertido para um número, então parseInt retornará NaN. (“not a number”).

Desta forma, a regra é sempre informar a base na qual a conversão deve ser realizada, exatamente como foi feito no segundo exemplo.

Javascript não sabe fazer comparações

>> 1 == true
=> true

Podemos realizar comparações em Javascript com os símbolos <, >, <= e >=. Eles funcionam tanto para numerais como para strings. Mas quando o assunto é a comparações de igualdade, precisamos prestar atenção a alguns fatores.

O operador de igualdade duplo realiza uma coerção de tipos caso você esteja comparando dois valores de tipos diferentes, como acontece no exemplo acima. Embora aparentemente este seja um resultado normal, em outros casos podemos ter resultados inesperados, como na seguinte comparação:

>> null == false
=> false

A linguagem também conta com um operador de igualdade tripo, que ignora a coerção de tipos:

>> 1 === true
=> false

Eu particularmente tenho como regra sempre utilizar o operador triplo ao realizar comparações de valores, isto tem me ajudado a evitar alguns erros de interpretação ao analisar o código e eventuais bugs decorrentes desta característica da linguagem.

Javascript também não sabe contar

>> var a = ["dog", "cat", "hen"];
>> a[100] = "fox";
>> a.length
=> 101

A propriedade length de objetos do tipo array em Javascript sempre retornará um número acima da maior posição do array e não necessariamente a quantidade de itens no objeto, conforme você pôde ver no exemplo acima.

Se você tentar recuperar o valor em uma posição não existente no array ele devolverá undefined.

Eu amo Javascript

Javascript possui muitas características realmente boas, e da mesma forma também possui algumas implementações realmente ruins (pretendo falar sobre isto em outro artigo). Por isto é importante gastar um pouco mais de tempo estudando mais a fundo o comportamento da linguagem. Depois de entender como algumas coisas realmente funcionam, descobrimos que nem tudo é bug.

Estas são apenas algumas poucas curiosidades que consegui coletar em alguns minutos brincando com a linguagem, você conhece mais alguma?


13 Comentários


  1. Salve,

    Eu gosto de JS, apesar de não ser fluente nele e ter problemas de vez em quando, especialmente porque é parecido com o Ruby sob vários aspectos. Eu recomendo o livro JavaScript: The Good Parts do Douglas Crockford [1], é bem legal e ajuda a separar o joio do trigo, tem uma apresentação que ele fez antes de escrever o livro no Yahoo![2], vale assistir. Além disso, um aviso para quem está começando (obviamente não é o seu caso): nunca brinque com JS sem um framework decente por trás, tipo jQuery(meu favorito), prototype ou derivados. Ninguém merece arrancar os cabelos tentando entender como cada browser entende coisas como getElementById.

    [1] http://oreilly.com/catalog/9780596517748/
    [2] http://video.yahoo.com/watch/630959/2974197

    Abraço

  2. Legal esse artigo, ele explica alguns pontos interessantes mas que não estão limitados somente ao Javascript. Na verdade, esses comportamentos são os mesmos em praticamente todas as linguagens.

    Por exemplo, em “Javascript não sabe somar”, na verdade o problema que ocorre ali ocorre em todas as linguagens porque é o clássico problema de representação de numeros reais em computadores.

    A grandeza de um número real é maior do que a dos naturais, porque além de existir infinito números positivos e negativos, existem infinitos números entre cada número ou fração. Pela maneira que é representado os números, somente números de base 2 podem ser totalmente representados, no caso de números fracionários os números terminados em 5 (ex: 0.5 ou 0.235). Todos os outros números são uma dízima periódica.

    Todo mundo já ouviu falar, pelo menos, que calculo de ponto flutuante não é preciso em computadores e deve ser evitado quando os calculos devem ser precisos, como calculos cientificos ou financeiros.

    O mesmo problema que você teve no javascript existe em todas outras. Por exemplo, em ruby:

    >> 0.1 + 0.2
    => 0.3

    A string retornada pelo #inspect exibe correto, mas:

    >> 0.1 + 0.2 == 0.3
    => false

    E se forçarmos o output para exibir todos os 17 elementos da fração:

    >> "%.17f" % (0.1 + 0.2)
    => "0.30000000000000004"

    Também em ruby, se formos transformar uma string em numero:

    >> Integer("010")
    => 8

    Mas se usarmos o .to_i, ele desconsidera os zeros iniciais e usa base 10:

    >> "010".to_i
    => 10

    Em javascript true e 1 são compativeis, assim como em varias outras linguagens (por questões históricas herdadas de C), mas por exemplo, em Ruby, nil != false:

    >> nil == false
    => false

    E existem outros comparadores:

    * obj.eql?(other) é um sinônimo de obj == other;
    * obj.equal?(other) compara se os objetos são os mesmos;
    * obj === other que define regras diferentes de comparação que é o usado pelo case/when

    E por ultimo, todas linguagens que utilizam indexação de array começando em 0 (zero), tem o mesmo comportamento:

    >> arr = []; arr[100] = “foo”; arr.length
    => 101

    Abraço
    Cheers

  3. Carlos,

    Ruby também! testa isso:

    shell> irb
    irb> (1.8 + 0.1) == 1.9 # false

    Vai dar “false”

    porém, se você usar:

    irb> (1.8 + 0.1)1.9 == Float::EPSILON # true

    Vai dar “true”

    Referencia: Ruby Cookbook, páginas 43 e 44

  4. Rodrigo, boa!

    Eu já tinha começado a preparar um artigo sobre estes mesmos casos em Ruby, mas agora não preciso mais… ;)

  5. Putz Ruby é pior que JS.

    Essas incosistencias tão basicas são terriveis para qualquer linguagem.

  6. Boa, cara.
    Realmente, esta maravilhosa linguagem é, muitas vezes, discriminada da raiz. Muitos nao sabem o verdadeiro poder do JS, e ja vi gente dizer “eu programo em jQuery”, mas dai dizem q nao sabem JS. Outros tantos, dizem que programam em Ajax…
    Ainda ha profissionais que dizem que javascript é usado para “validar formularios” e menospresam todas as possibilidades para incremento na qualidade da interface, estabilidade e interação do seu sistema web com o usuário.

    Parabens pelo post.

  7. Para nos lembrar o quão poderoso é o JavaScript vale lembrar do Gmail. A interface dele é bem complexa e não existe outra tecnologia que chegaria em um resultado parecido.

  8. Olá Carlos,

    Achei tão interessante o seu post que resolvi referenciá-lo no meu blog. Espero que não tenha problema :)

    Grande abraço e parabéns!

    Isto é um bug no Javascript? – http://blog.lppjunior.com/isto-e-um-bug-no-javascript/

  9. Muito legal o post Carlos, a algum tempo postei um post no meu blog sobre JS “String sao objetos ou sao tipos primitivos?” = >http://edipolf.wordpress.com/2008/07/08/strings-sao-objetos-ou-sao-tipos-primitivosjavascript/ não tem muito haver com o post seu, mas se alguem tiver interessado,,, Obrigado

  10. uts, ótimo post. Parabéns pelas explicações.

  11. Sempre que vejo um comentário depreciativo quanto ao JavaScript acho graça. Normalmente parte de quem não domina a liguagem e seu foco não é interface.

    Ouvi a frase: “…é, a programação de script é algo mais fácil, não é? Num tem regras difíceis como no back-end”. Daí pensei…depende da complexidade da interface, dependendo pode até ser mais complicada sim, mas neguinho acha que JavaScript é só para validação de campos, fazer o quê? O dia que realmente adentrarem ao mundo client-side verão a verdade.

  12. Carlos, muito bom, vou enviar para meus colegas, estavamos discutindo sobre isso ontem. Ótimo artigo!

  13. Rapaz tu falou do problema no qual estou passando e não disse a possivel solução no primeiro exemplo que o js não sabe somar?

Trackbacks

  1. Isto é um bug no Javascript? » Luiz Paulo - tecnologia web
  2. Verdade seja dita sobre Javascript | George Guimarães on Tech
  3. Twitter Trackbacks for Nome do Jogo » Blog Archive » Isto é um bug no Javascript? [nomedojogo.com] on Topsy.com
  4. Retrospectiva 2009 | Nome do Jogo

Deixe um comentário