Ya casi hemos terminado el 2013 y toca la típica review y objetivos para el año que viene.
Este año ha sido intenso, algunas de las razones son las siguientes:
No creo ni en la suerte ni en el destino pero mi intuición me dice que el año que viene va a ser movidito. Sólo espero sacar los productos que tengo pendientes y ver como evolucionan, mejorar mi puto inglés de una vez por todas (objetivo recurrente durante los últimos 6 años), pasarmelo lo mejor posible y quizá cerrar alguna que otra herida del pasado.
Qué bonito es trabajar desde casa, leer el libro de 37signals y asentir, sacar pecho “yo trabajo desde casa, soy el puto amo” y tantas otras cosas. Pero como todo, generalizar es la receta perfecta para darte la hostia. Cada persona, empresa y situación es diferente.
Estas son algunas de las cosas que he sufrido y sufro durante el tiempo que he trabajado en remoto:
lo de trabajar asíncrono está muy bien, pero algunas cosas requieren sincronismo. Si a un usuario (que paga) no le funciona algo y necesitas de alguien o alguien de ti necesitas ser síncrono y rápido. Algunos servicios tardan días en darte respuesta, pero en general no me gusta hacer esperar si alguien paga.
te pierdes la mitad de la historia y tienes que recomponerla. Muchas veces las cosas se hablan, pero claro, no estás en la oficina y entonces la gente te empieza a hablar de cosas. Te toca entonces buscar las piezas que no encajan. Y esto es bastante difícil y require esfuerzo y tiempo (ese que ganas en el viaje de metro que no haces).
el músculo de trabajar con gente se pierde. Sí ganas el trabajar con gente en remoto, ahí cada uno valore lo que es más interesante para él.
el músculo de hablar con gente, no necesariamente de trabajo, se pierde igualmente. Es algo que he constatado con más trabajadores remotos, a menudo evitas hablar con la gente, pierdes el hilo de las conversaciones muy fácilmente, etc.
si no eres un poco autodisciplinado terminas por no salir de casa, trabajar cuando no debes, etc. El momento de irte de la oficina es un punto de inflexión difícil de reproducir en casa.
no ves el jeto de tu compañero de trabajo, no sabes si hoy está triste, ha venido tajado, está hasta las narices, concentrado, presionado, alegre, risueño o atontado. Y este es posiblemente el mayor problema.
Ahora que javascript está tan de moda, es tan potente y nos permite hacer lo que código nativo nos permitía hacer en un 486 los desarrolladores estamos empezando a hacer cosas algo más interesantes que poner texto y enlaces como juegos o animaciones. Lo cierto es que a pesar de que javascript sea aún lento para hacer cosas con mucha carga matemática, tenemos a nuestra disposición tarjetas gráficas muy potentes y gracias a las mejoras que últimamente han incluído en los navegadores, podemos usarlas directamente (con WebGL) o indirectamente (CSS3, SVG, canvas)
El objetivo de este artículo no es explicar como renderizar rápido ni de como aprovechar la GPU si no de como hacer una animación.
Lo primero que se nos ocurre es el típico setInterval. Seguro que conoces la función, pero básicamente lo que hace es llamar a una función que le especificas cada cierto tiempo. Por ejemplo, la animación sería tal que así:
setInterval(function () {
update()
render();
}, 20)
Esto llamará a la función cada 20ms, esto es 50 frames por segundo (FPS). Para la mayoría de casos cumple perfectamente. Pero qué pasa si la función update tarda 30ms en terminar? Realmente no sé que es lo que hace el navegador, supongo que cada uno tendrá su política, pero cabe pensar que con el tiempo se empezarán a aculumar eventos de llamada a esa función ya que no es capaz de terminar en menos de 20ms.
Aunque el navegador fuese muy listo y gestionase eso perfectamente no podemos hacer la animación correctamente porque no sabemos el tiempo que ha pasado de un frame a otro.
Para evitar que se nos acumulen llamadas por que la función es lenta vamos a pedir renderizar un frame solo cuando hayamos terminado:
var logic = function () {
update()
render();
setTimeout(logic, 20);
};
setTimeout(logic, 0);
Vale, ahora ya no se acumulan eventos y si la función update o render tarda más que esos 20ms no pasará nada, sólo actualizaré la animación cuando el frame anterior haya terminado. Mejor que antes pero aún estamos animando como unos tristes.
Imaginemos que estamos controlando una animación de una imagen moviendose por la pantalla. Queremos que en 1 segundo se mueva 500px:
var img = new Image();
img.src = 'image.png';
img.style['position'] = 'absolute';
var pos = 0;
img.style['left'] = pos + 'px';
var logic = function () {
// update
pos += 500*0.02;
img.style['left'] = pos + 'px';
// browser will render the img on style change
//render();
if(pos < 500) {
setTimeout(logic, 20);
}
};
setTimeout(logic, 0);
En un segundo, a 50FPS habremos dibujado 50 frames con lo cual pos será 500 al pasar un segundo. No está mal, pero imaginemos que tenemos una máquina lenta, tan lenta que no es capaz de actualizar a 50FPS. En ese caso tendremos que la imagen llegará a la posición 500px, pero en más de un segundo. No estamos controlando el tiempo.
Para ello podemos usar el tiempo transcurrido desde el último frame. Vamos, lo que se lleva aplicando en videojuegos simples toda la vida:
var t0 = new Date().getTime();
var logic = function () {
var t1 = new Date().getTime();
var dt = t1 - t0;
t0 = t1;
// update
pos += 500*dt;
img.style['left'] = pos + 'px';
// browser will render the img on style change
//render();
if(pos < 500) {
setTimeout(logic, 20);
}
};
setTimeout(logic, 0);
Ya está, ahora aunque la máquina sea lenta controlamos la animación correctamente
Ahora podemos controlar un poco mejor la animación gracias a requestAnimationFrame. Para variar es una función que no es standard y cada navegador la implementa con el nombre que le da la real gana.
En resumen, esta función llama a la función que nostros queramos cuando el navegador vaya a renderizar. Eso es bueno, por que si nosotros llamamos 1000 veces por segundo a esta función como mucho llamará a la animación el máximo que pueda actualizar. Asumamos que esto es bueno, aunque si estás haciendo algo medio serio la actualización de la lógica puede ir parcialmente desacoplada del renderizado (luego veremos un ejemplo).
Así que la cosa quedaría tal que así:
var logic = function () {
var dt = //calculate dt
uddate(dt);
render()
requestAnimationFrame(logic);
}
requestAnimationFrame(logic);
Perfecto, además podemos llamar a requestAnimationFrame desde cualquier lado para que comience la animación.
Normalmente (la ley del copy & paste así lo dice) se define requestAnimationFrame tal que así:
var requestAnimationFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(a){setTimeout(a,20);};
aunque en mi opinión debería ser algo así:
function createRequestAnimationFrame(fn) {
var rendered = true;
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(a) {
if(rendered) {
rendered = false;
setTimeout(function() {
fn();
rendered = true;
}, 20);
}
};
}
logic = function() {
update();
myAnimationRequestFrame();
}
myAnimationRequestFrame = createRequestAnimationFrame(logic);
myAnimationRequestFrame();
element.onclick = function() {
// logic
myAnimationRequestFrame();
}
De este modo aunque llamemos 1000 veces a myAnimationRequestFrame sólo se llamará a la animación cuando toque
Qué pasa si el navegador es muy lento. Bueno, lo ideal es quitar la animación, pero claro, no sabemos a priori si la máquina es lenta (bueno, puedes comprobar si es Firefox). Para evitar que todo se vaya al traste quizá lo mejor sea limitar la animación para que no se vaya de madre.
var logic = function () {
var dt = //calculate dt
dt = Math.min(0.05, dt);
update(dt);
render()
requestAnimationFrame(logic);
}
requestAnimationFrame(logic);
De esta forma si la animación es muy lenta la ralentizamos pero así estamos seguros de que la lógica no falla. También nos protege del caso en el que el usuario cambie de tab (requestAnimationFrame no asegura que se llame al callback si la pestaña donde se ejecuta el código no está activa), al volver a activarse el dt será muy grande y podría desestabilizar en caso de no controlar el dt. Imagino que habrá algún API para controlar si la pestaña es activa…
Imaginemos que la función update hace algo más complejo que sumar, por ejemplo una integración numérica. En ese caso si el dt es muy grande la integración se puede ir al traste, necesitamos dt suficientemente pequeños. Ese caso quedaría resuelto también.
Por ejemplo, queremos que en el ejemplo anterior la imagen vaya hasta 500 pero suavemente:
var smooth = 0.1;
var distance_to_target = 500 - pos;
pos += distance_to_target*smooth*dt;
Si dt es muy grande lo que pasará es que esa función empezará a oscilar haciendo un efecto muelle en vez de llegar suavemente o incluso oscilará hasta el infinito si dt es muy grande (a que os suena de de cuando érais jóvenes y estudiabais? si eres teleco/industrial y no te suena vuelve a la carrera).
Mejor que explicarlo aquí, id a este artículo del mítico Javier Arévalo y grabadlo en vuestra mente:
No voy a explicar qué es CORS, la página de la wikipedia y cientos de web a golpe de búsqueda te lo explicarán. A mi gusta verlo como el gran bugfix del desarrollo web. Este post trata de explicar algunas de las dificultades que nos hemos encontrado en el uso de CORS después de dos años usándolo en producción.
Hasta ahora hemos estado usando JSONP y otros hacks para consumir servicios de dominios diferentes al que se ejecuta nuestra web app. Aunque tenga una siglas más o menos aparentes, detrás de ellas se esconde un hack horrible, que nunca debería haber existido. Alquien pensó que ya era hora de cambiar eso, que debería haber una tecnología que permitiese coger datos de dominios usando AJAX que no son el nuestro de forma segura. Y ahí entro CORS.
Nosotros en cartodb y otros proyectos de vizzuality usamos CORS cosas muy variopintas:
- poder manipular los píxeles de las imágenes (para hacer análisis raster en mapas)
- poder hacer POST y con enviar más datos de los que permite una petición GET
- consumir datos de nuesta API desde aplicaciones web sin necesidad de un backend
Decir que en ningún caso hemos usado autentificación, desconozco lo completo que será el tema, pero imagino que tendrá sus cosillas (hay tokens CSRF por ejemplo) de por medio.
Cuando digo que tenemos en producción quiero decir que hemos estado más de 9 meses basando nuestra arquitectura pública de mapas en CORS, quiero decir, ya hemos llegado a la fase de “esto functiona” a “vamos puliendo los detalles”. Cuando digo producción, digo millones de usuarios y en sitios con mucho tráfico y usuarios diferentes, por ejemplo, wikileaks, blog de twitter, lemonde, the guardian y otros con mucho más tráfico que no recuerdo.
Los problemas que nos hemos encontrado:
- INTERNET EXPLORER. Sí, las versiones anteriores no lo soportan y dependiendo de como le de [1] IE10 a veces reporta que tampoco lo soporta. Así que tendrás que tener un fallback ( leer la siguiente)
- Vas a necesitar un fallback: no puedes basar tu arquitectura en CORS, aunque los navegadores que soporten sean compatibles, hay muchos otros problemas (leer siguientes), así que vas a tener que tragar con JSONP o proxy en tu servidor.
- Algunos proxies eliminan las cabeceras de CORS. Cuando tu navegador web hace un OPTIONS antes de hacer la petición de verdad (necesita saber si puede de verdad hacerlo) si eliminan las cabeceras el navegador devolverá un error.
- El número de peticiones se multiplica. Hay que hacer un OPTIONS antes de hacer la petición real. No es que sea mucha carga, pero es una petición más que tus servidores tienen que tragar (no es cacheable según el standard y efectivamente los navegadores no las cachean), hay cierto lag y es un fastidio cuando estás desarrollando.
- Ahora ese problema ya no existe, pero durante este tiempo tanto chrome como FF han cambiado la forma de funcionar.
Para terminar, nosotros basamos todo nuestro API de mapas en CORS y teníamos como fallback JSONP. Hace cosa de 1 mes hemos pasado de usar JSONP como herramienta principal y CORS como fallback. Falta aún un tiempito para poder basar todo el sistema en CORS, pero desde luego es un gran avance en el desarrollo web.
Ya hay mucha gente incluyendo las cabeceras de CORS en sus servicios permitiendo hacer algunas cosas interesantes, por ejemplo, los tiles de los mapas de google los incluyen o algunos servicios de autentificación, por ejemplo openstreemap, permitiendo hacer oauth desde javascrip
[1] si está en modo compatibilidad IE10 no funciona CORS, pero incluso a veces en modo normal, tampoco.
During the Torque development (a small javascript library to create animations on maps) we decided to use a markdown template to write there the reference API documentation:
https://gist.github.com/javisantana/6413167
some goodies about it:
- plain text
- easy to read without format
- plenty of tools to transform to html (or whatever)
- easy to parse if the project grows and it need to be transformed to something better
- nice starting point. Useful when you don’t know how/where to start
While I was coding I always had a vim window open with the documentation so I can update it at the same time I was changing the interface in the code. It’s the only way I’ve found to keep the API documentation up to date.
At the end this is another example of “stone soup" story. In this case putting this file in a doc folder and opening it every time you start to code remind you need to update it.