viernes, 17 de mayo de 2013

Medir el tiempo de ejecución

Podemos usar la consola de las herramientas para desarrolladores para ver el tiempo que tarda en ejecutarse una parte del código y comprobar su rendimiento. En Google Chrome esto se hace con 2 instrucciones:

  • console.time(nombre del timer): se coloca antes del código que queremos medir. Inicia un temporizador con el nombre que hayamos elegido, que será una cadena de texto.
  • console.timeEnd(nombre del timer): se coloca después del código que queremos medir. Termina el temporizador con el nombre asociado, que será uno de los que hayamos iniciado antes con console.time(). Además escribe a la consola el resultado.

Uso

console.time('nombre');
// código a cronometrar

console.timeEnd('nombre');

Ejemplo

Por ejemplo, lo podemos usar para medir el tiempo que tarda en pintar 10000 lineas aleatorias de colores al azar, como vimos en un post anterior:
<!DOCTYPE html>
<html>
 <head>
    <script type="text/javascript" >
        var canvas;
        var ctx; //contexto del canvas.
        
        function setup(){
            // accedemos al canvas
            canvas = document.getElementById('my_canvas');

            // si se puede acceder al contexto del canvas
            if (canvas.getContext){ 
                // accedemos al contexto del canvas 
                // y llamamos a nuestra función de dibujo.
                ctx = canvas.getContext('2d');
                draw();
            }
            else { // el canvas no está soportado
                alert('Este navegador no es compatible con canvas');
            }
        }
        
        // nuestra función de dibujo
        function draw(){                
            // lo ajustamos al tamaño de la ventana del navegador
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
            // dibujamos. En este caso, 10000 lineas aleatorias
            // y medimos lo que tarda
            console.time('tiempo que tarda en pintar');
            
            pintaLineasAleatorias(canvas.width, canvas.height, 10000);
            
            // hasta que termina
            console.timeEnd('tiempo que tarda en pintar');
        }
        
        function pintaLineasAleatorias(width, height, n)
        {
            var x1, y1, x2, y2;
   
            for (var i=0; i< n; i++)
     {
         x1 = Math.floor((Math.random()*(width + 1)));
         x2 = Math.floor((Math.random()*(width + 1)));
         y1 = Math.floor((Math.random()*(height +1)));
  y2 = Math.floor((Math.random()*(height +1)));
  ctx.strokeStyle = 'rgb('
                    + Math.floor(Math.random()*256)
                    + ',' + Math.floor(Math.random()*256)
                    + ',' + Math.floor(Math.random()*256)
                    + ')';
  ctx.beginPath();
  ctx.moveTo(x1,y1);
  ctx.lineTo(x2,y2);
                ctx.closePath();
  ctx.stroke();
      }
        }
    </script>
 </head>
 <body style="margin:0; padding:0; width:100%; height:100%;" onload="setup()" >
  <canvas id="my_canvas" style="display:block;"></canvas>
 </body>
</html>
Si abrimos el archivo html anterior en el Google Chrome y luego abrimos la consola de javascript (Ctrl+Mayús+J) veremos el tiempo que ha tardado en dibujar las 10000 lineas.

martes, 14 de mayo de 2013

Números aleatorios

La capacidad de generar números aleatorios es muy interesante en gran cantidad de juegos, con fines tan variados como simular la tirada de un dado, generar un mapa aleatoriamente, dotar de un comportamiento errático a algunos enemigos o generar eventos aleatorios.
Para generar números aleatorios nos vamos a servir del objeto Math, que sirve para realizar varios cálculos matemáticos, incluidos cálculos trigonométricos. Para este fin son necesarias 2 funciones del objeto Math:
  • Math.random(): devuelve un número aleatorio entre 0 y 1.
  • Math.floor(): redondea hacia abajo, hacia el "suelo".
Para generar un número aleatorio arbitrario usaremos una combinación de ambas, por ejemplo, para generar un número aleatorio entre 0 y 255:
Math.floor( Math.random()*(255 + 1) );
Podríamos haber usado Math.round() lugar de Math.floor() para conseguir el mismo resultado, pero aunque ambos sirven al mismo fin, Math.floor() produce un reparto de los resultados más equitativo, mientras que Math.round() produciría una desviación.

Canvas que ocupa toda la ventana del navegador

Para tener un canvas que ocupe toda la ventana del navegador debemos hacer las siguientes cosas:

  • Para que el body del HTML ocupe toda la ventana del navegador, sin dejar ningún borde, añadimos el siguiente atributo a la etiqueta body:
style="margin:0; padding:0; width:100%; height:100%;"
  • Para que el canvas no tenga barras de scroll hay que añadir el siguiente atributo al canvas:
style="display:block;"
  • Para que el canvas ocupe toda la ventana, cada vez que vayamos a volver a dibujar hay que redimensionarlo, con las siguientes instrucciones:
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
Un ejemplo de uso:
<!DOCTYPE html>
<html>
 <head>
    <script type="text/javascript" >
        var canvas;
        var ctx; //contexto del canvas.
        
        function setup(){
            // accedemos al canvas
            canvas = document.getElementById('my_canvas');

            // si se puede acceder al contexto del canvas
            if (canvas.getContext){ 
                // accedemos al contexto del canvas 
                // y llamamos a nuestra función de dibujo.
                ctx = canvas.getContext('2d');
                draw();
            }
            else { // el canvas no está soportado
                alert('Este navegador no es compatible con canvas');
            }
        }
        
        // nuestra función de dibujo
        function draw(){                
            // lo ajustamos al tamaño de la ventana del navegador
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
            // dibujamos. En este caso, 10000 lineas aleatorias
            pintaLineasAleatorias(canvas.width, canvas.height, 10000)
        }
        
        function pintaLineasAleatorias(width, height, n)
        {
            var x1, y1, x2, y2;
   
            for (var i=0; i< n; i++)
     {
         x1 = Math.floor((Math.random()*(width + 1)));
         x2 = Math.floor((Math.random()*(width + 1)));
         y1 = Math.floor((Math.random()*(height +1)));
  y2 = Math.floor((Math.random()*(height +1)));
  ctx.strokeStyle = 'rgb('
                    + Math.floor(Math.random()*256)
                    + ',' + Math.floor(Math.random()*256)
                    + ',' + Math.floor(Math.random()*256)
                    + ')';
  ctx.beginPath();
  ctx.moveTo(x1,y1);
  ctx.lineTo(x2,y2);
                ctx.closePath();
  ctx.stroke();
      }
        }
    </script>
 </head>
 <body style="margin:0; padding:0; width:100%; height:100%;" onload="setup()" >
  <canvas id="my_canvas" style="display:block;"></canvas>
 </body>
</html>


lunes, 13 de mayo de 2013

Depurar javascript

La depuración es el proceso de detectar y corregir errores y fallos en un programa. Esto es importante en javascript porque suele fallar silenciosamente, sin dar pistas de lo que puede ir mal.
Hay varias cosas que podemos hacer para evitar y corregir posibles errores:

  • Usar un detector de errores de sintaxis.
  • Incluir sentencias de depuración.
  • Usar un depurador.

Usar un detector de errores de sintaxis

En un post anterior ya hablamos de JSHint o JSLint. Cualquiera de los dos nos facilitará enormemente descubrir posibles errores de sintaxis que tan fácilmente pasan inadvertidos en javascript. Por ello, el primer paso debería ser pasar el script por uno de ellos.

Incluir sentencias de depuración

Normalmente detectamos que hay algún problema porque no ocurrió algo que esperábamos o bien alguno de los resultados del programa es diferente al esperado y sin embargo, al analizarlo con JSHint no se detecta ningún error. Esto puede ser debido a que el programa es correcto sintácticamente pero no hemos programado realmente lo que queríamos programar y el ciclo de ejecución del programa no lo lleva por los caminos que habíamos supuesto. En ese punto hay que intentar descubrir qué es lo que falló. ¿Ha pasado el programa por los puntos que esperabamos? ¿Tienen las variables los valores que suponíamos? Una técnica para detectar estos y otros problemas, sin llegar a recurrir a un depurador propiamente dicho consiste en incluir sentencias de depuración.
Las sentencias de depuración son instrucciones que se añaden al script con el objeto de ayudarnos a encontrar el punto de fallo. Un ejemplo típico es mostrar un cuadro de alerta en un punto del programa, para comprobar que la ejecución ha pasado por ahí, o bien escribir algún mensaje en el documento html.
Normalmente, las instrucciones de depuración servirán para depurar y una vez comprobado el programa y satisfechos con el resultado, se eliminarán del script, comentandolas o bien borrándolas.

Cuadro de alerta

  • alert(mensaje): muestra un cuadro de alerta con el mensaje especificado, que debe ser una cadena de texto, entre comillas simples o dobles. Por ejemplo:
if (a > 5)
{
    // hacer cosas...
    alert("a > 5");
}
else if (a < 7)
{
    // hacer cosas...
    alert("a < 5 y a < 7");
}
else
{
    // hacer cosas....
    alert("El programa ha pasado por aquí.");
}
En este ejemplo, si la variable a tiene un valor numérico, debería cumplir una de las 2 primeras condiciones (si no es mayor que 5, entonces es menor que 7, si o si). Sin embargo, si la variable a está declarada pero no tiene un valor definido, o tiene un valor que no es numérico, puede no ser mayor que 5 ni menor que 7 y, por lo tanto el programa entrará en el último else, cosa que detectaremos por el cuadro de diálogo que muestra "El programa ha pasado por aquí.".

OJO!!: como la instrucción alert provoca la aparición de un cuadro de diálogo, no conviene usarla dentro de ciclos con muchas iteraciones porque cuando se abre un cuadro de alerta la ejecución del script se interrumpe hasta que se vuelve a cerrar y luego hay que cerrarlos!!.

Escribir al documento HTML

  • document.write(mensaje): es una alternativa sencilla para mostrar información en el navegador.
OJO!!: document.write() sobreescribe TODO el DOM. Por muy compleja que sea la página web que tengamos, después de hacer un document.write() es como si sólo tuviesemos el texto del mensaje en el documento HTML. Por ejemplo, en un documento HTML con canvas y otros elementos, si hacemos:
document.write("el valor de a es: " + a);
Tendremos una página web en la que canvas y demás elementos han desaparecido y sólo queda el mensaje (suponiendo que a=2):

Escribir a un elemento HTML

Escribir a todo el documento HTML es conveniente a veces por su sencillez: es un método de depuración rápido, para comprobar algún valor de alguna variable sin liarse a abrir el depurador ni más complicaciones. Sin embargo, a veces es más conveniente escribir a un elemento HTML de la página web. El elemento puede ser uno que ya exista en la página web y otras veces lo crearemos expresamente para la ocasión, modificando el archivo HTML o bien mediante javascript.

  • Es muy fácil escribir al título de la página, cambiando el atributo title del documento. Por ejemplo, supongamos que una hipotética variable b = 8:
document.title = "la variable b vale: " + b;



  • Escribir a un elemento existente, o que creamos en el HTML.
El elemento debería tener un atributo id, para acceder fácilmente con document.getElementById(). Si no lo tiene, se lo podemos añadir en el HTML.
Por ejemplo, podríamos crear un párrafo y escribir al mismo cambiando su atributo innerHTML:

Creamos el elemento, con id="depurar" en el HTML, dentro del body:
<p id="depurar"></p>
y accedemos al mismo y escribimos la información para depuración que queramos desde javascript:
document.getElementById("depurar").innerHTML = "la variable b vale: " + b;
para conseguir lo mismo que en el ejemplo anterior, pero en un elemento párrafo en vez del título.


  • Escribir a un elemento creado mediante javascript.
Supongamos que vamos a crear el elemento párrafo del ejemplo anterior, pero mediante javascript, para no tocar el archivo HTML original. De esta forma es más sencillo eliminar el código de depuración cuando ya no sea necesario.
    var parrafo=document.createElement("p");
    document.body.appendChild(parrafo);
    parrafo.innerHTML = "la variable b vale: " + b;
En el código anterior se crea un elemento en el documento, de tipo párrafo (especificador "p" de la función createElement). Ese elemento párrafo se asigna a una variable parrafo del javascript para poder acceder al mismo, sin tener que buscarlo con getElementById, ya que no tiene id.
Una vez creado, lo añadimos al body del documento. Finalmente escribimos nuestro mensaje de depuración modificando el atributo innerHTML de la variable parrafo.


Escribir a la consola

  • console.log(mensaje): escribe el mensaje a la consola javascript del navegador Chrome. Otros navegadores funcionan de otra manera. Veremos la consola javascript al hablar del depurador en el siguiente apartado.
En Chrome se puede ver la consola javascript yendo a "Personaliza y configura Google Chrome" -> Herramientas -> Consola Javascript o bien pulsando Ctrl + Mayús + J.

Usar un depurador

Parece la opción obvia cuando hay algún problema, pero antes de recurrir al depurador es bueno pensar un poco en cuál puede ser la causa del problema y a veces es más fácil y rápido usar instrucciones de depuración en lugar de ir directamente al depurador.
El depurador nos permite ejecutar el script poniendo puntos de parada, ejecutar el script paso a paso y ver el valor que toman en cada momento las variables. Es una herramienta potente pero hay que usarla con cabeza.
En este post nos vamos a centrar en el depurador de Google Chrome.
En el ejemplo usaremos los dos archivos siguientes, que deben estar en la misma carpeta, para que el camino desde el HTML hasta el javascript sea simplemente "mi_javascript.js":

pagina.html
<!DOCTYPE html>
<html>
 <head>
    <script type="text/javascript" src="mi_javascript.js" >
    </script>
 </head>
 <body onload="mi_funcion()">
  <canvas id="my_canvas" width="200px" height="100px"
style="border:1px solid #000000;"></canvas>
 </body>
</html>
mi_javascript.js
function mi_funcion() {
    var a = 2;
             
    if (a > 5)
    {
        // hacer cosas...
        alert("a > 5");
    }
    else if (a < 7)
    {
        // hacer cosas...
        alert("a < 5 y a < 7");
    }
    else
    {
        // hacer cosas....
        alert("El programa ha pasado por aquí.");
    }
    document.write("el valor de a es: " + a);
    console.log(a);
}

Si abrimos el archivo pagina.html con el navegador Chrome, veremos lo siguiente:

Arriba a la izquierda está el canvas con borde, y también hay un cuadro de alerta con el texto "a < 5 y a < 7". La ejecución del script está bloqueada hasta que no cerremos el cuadro de alerta haciendo click en el botón "Aceptar". Después de desaparecer el cuadro de alerta, se escribe al documento HTML y se sobreescribe todo el DOM con "el valor de a es: 2":

Se aprecia que por haber sobreescrito todo el DOM, el canvas ha dejado de existir.

Las herramientas para desarrolladores de Google Chrome se sacan pinchando en "Personaliza y configura Google Chrome" -> Herramientas -> Herramientas para desarrolladores o bien pulsando Ctrl+Mayús+I.

La pantalla del navegador queda dividida en 2 zonas por la barra de herramientas para desarrolladores: la parte superior aloja la página web y la inferior la pantalla seleccionada en la barra de herramientas. De éstas, de momento, y a un nivel más básico, nos interesan Elements, Sources y Console.

Elements

Si pulsamos sobre el botón "Elements" aparece el contenido del HTML (puede ser necesario desplegarlo pulsando sobre el triángulo negro de la izquierda:

Podemos apreciar que el documento HTML ha quedado reducido al head, vacío y el elemento body, que sólo contiene el mensaje que hemos escrito desde el javascript.

Console

Si pinchamos en el botón Console, veremos la consola de javascript, en la que aparece escrito un "2", que es el valor que hemos escrito con la instrucción console.log(a).
En la consola se puede escribir javascript, que se ejecuta al instante. Por ejemplo, si escribimos a; y pulsamos Enter, que es lo que haríamos para ver el valor de la variable a, obtendremos un error: Reference Error: a is not defined. Esto es debido a que la función "mi_funcion()" ya ha terminado y la variable a, que se declara dentro de ella, ya no está en el ámbito (scope).


Elements de nuevo

Pulsemos sobre "Elements" y luego hagamos click derecho con el ratón sobre el área de la página y seleccionemos la opción "Cargar de nuevo" para recargar la página. También se puede recargar la página pulsando F5, pero en este ejemplo deberemos hacer click sobre la página antes para devolverle el foco y que se refresque correctamente.

Aparece el mensaje de alerta y vemos el elemento canvas en la web. También vemos la estructura de la página web en las herramientas para desarrolladores: tenemos el canvas dentro del body.
En cuanto cerremos el cuadro de alerta, el script continúa su ejecución y se ejecuta la instrucción document.write(), con lo que se sobreescribe todo el DOM y podemos ver que la estructura de la página web es lo más básica posible, habiendose sustituido todo el contenido del body por el texto "el valor de a es: 2".
Una de las funcionalidades más interesantes de la pantalla Elements es que se puede pasar el ratón por los elementos de la página o por la lista de elementos que aparecen en la parte inferior y ver cómo se resaltan en la parte superior de la pantalla, mostrando el espacio que ocupa el elemento. También es muy útil seleccionar algún elemento y ver sus propiedades en la parte derecha de la ventana Elements.

Sources

Pinchamos en Sources y luego en el botón Show navigator . En el navegador podremos elegir el archivo html y el archivo javascript. Lo interesante será ver el archivo javascript. Desde aquí podremos hacer varias cosas para depurar el javascript.

  • Poner puntos de parada (breakpoints): pinchando con el ratón a la izquierda de una linea del script, en la columna en la que aparecen los números de linea, se puede marcar la linea con una flecha azul, que indica un punto de parada. El punto de parada también aparece a la derecha, en la sección breakpoints.



  • Ejecutar el script: para eso pulsamos F5 para recargar la página. Si tenemos puesto algún breakpoint, el script se detendrá en el mismo y podremos ver dónde se ha detenido y el valor de las variables que estén en el ámbito (scope), como por ejemplo, la variable a.

  • Continuar la ejecución paso a paso.
Los siguienes botones  nos permiten continuar la ejecución del script, hasta el siguiente punto de parada, continuar paso a paso sin entrar en funciones, continuar paso a paso entrando en funciones o continuar hasta salir de una función y por último, desactivar todos los breakpoints.
En el ejemplo actual, si le damos un par de veces al 2º botón (continuar sin entrar en funciones), vemos que la ejecución llega a entrar en el 2º else:
Este es un ejemplo de cómo se puede seguir la pista al camino de ejecución de un script y ver si la ejecución toma las bifurcaciones esperadas.

El objetivo de este post es dar una pequeña introducción a las herramientas de depuración, pero para aprender todas sus posibilidades y a manejarse adecuadamente con ellas es necesario recurrir a otras fuentes, como por ejemplo:





sábado, 4 de mayo de 2013

JSHint: Detección de errores en javascript

Una particularidad frustrante que tiene javascript es la combinación de 2 características de su funcionamiento habitual:
  • es muy sensible al caso: no es lo mismo array que Array, ni onload que onLoad.
  • falla silenciosamente: muchas veces la única pista que tenemos de que algo va mal con nuestro script es que no hace lo que queríamos, pero nada más: no hay mensajes de error, el navegador no se queja... a veces no funciona nada de nada pero no tenemos ninguna pista de donde puede estar el fallo, a veces tan simple como una coma o un punto y coma que no están donde debieran.
Hay una herramienta que nos va a ayudar a detectar y corregir todo tipo de errores de sintaxis que tan dificiles son de detectar, sobre todo al principio. Existen dos variantes muy similares JSLint y JSHint. Personalmente prefiero JSHint, pero para gustos los colores.

Versión web

En www.jshint.com , lo único que hay que hacer es pegar nuestro código javascript en el recuadro de texto y darle al botón "Lint".

Plugin para Notepad++

Se puede descargar de la página del proyecto en sourceforge: http://sourceforge.net/projects/jslintnpp/
Para instalarlo sólo hay que descargarse el archivo zip de la página del proyecto, descomprimirlo y copiar el archivo JSLintNpp.dll en la carpeta plugins de Notepad++. Después de hacerlo hay que cerrar y volver a abrir el Notepad++ antes de poder usarlo. En Windows el camino de la instalación por defecto es C:\Archivos de programa\Notepad++\plugins.

Después de instalarlo, conviene configurar las opciones:
En Notepad++: Plugins-> JSLint-> JSLint Options

Aquí podemos elegir si queremos que sea JSLint o JSHint el programa que se encargue de analizar nuestros archivos y también podemos configurar una serie de opciones para cada uno de ellos. Yo prefiero JSHint y suelo tener marcadas por defecto: Assume  browser y assume development.



Para usarlo, lo único que debemos hacer es tener un archivo javascript abierto y elegir JSLint->JSLint Current File (Ctrl+Shift+F5).