Text

Qué puede aprender un programador de una puerta automática y de un mueble de IKEA

La puerta automática

¿No os ha pasado que caminando por la calle cerca de la pared se abra una puerta automática de una tienda sin querer entrar en ella? Es evidente que aquí el sistema ha dado un falso positivo. La puerta automática está pensada para abrirnos paso cuando queremos entrar, y en este caso se ha abierto y no queríamos entrar. ¿Cómo mejorar este sistema? Podríamos colocar cámaras y que un sistema de análiis de imágenes a través de computer vision calculase la trayectoria de un objeto en movimiento. Pero además también querríamos evitar que se abriesen las puertas si lo que pretende entrar es un animal y no una persona. Podríamos en tal caso instalar un sistema que a través de algún mecanismo de machine learning (redes neuronales, algoritmos genéticos, etc.) reconociera qué es y qué no es una persona. Una vez desarrollado podríamos comercializarlo y vender las bondades del nuevo sistema y hacernos de oro.

Seguro que no hay nadie en su sano juicio que quiera hacer una cosa así. El sistema es mucho más complejo y absurdamente caro frente a la solución de siempre, que a pesar de no ser “tan buena” es suficientemente efectiva. Pues este patrón lo encontramos día a día cuando programamos código. No debemos obcecarnos en soluciones 100% perfectas. Muchas veces buscar una solución óptima es infinitamente más complicada que una solución simple que acierta en más del 90% de las ocasiones y nos basta con eso. Debemos aprender a valorar esas soluciones.

He visto cómo muchos programadores instalaban motores de indexación basados en Lucene teniendo sólo un puñado de registros en una base de datos MySQL donde puedes usar FULL TEXT tranquilamente, o complejas arquitecturas de sistemas para un puñado de visitas o usar frameworks frontend con tropecientos plugins para una landing page.

Y a veces el sistema más sencillo cuesta implementarlo un chasquido de dedos, como la puerta automática más simple: pones una plataforma que cuando se presiona (pisas) acciona la puerta. Por ejemplo en vez de liarte a instalar, configurar (o incluso implementar!) un motor de recomendaciones super cool, puedes hacerte uno “espartano” en 40 líneas de código. O por ejemplo, cómo implementarías un sistema que reconociese si un texto es una crítica positiva o negativa hacia un producto? Uff, tendríamos que implementar un algoritmo de reconocimiento del lenguaje natural y un algoritmo basado en inteligencia colectiva y… O bien puedes tener una lista de palabras positivas y otro de negativas y simplemente sumar y restar.

En ocasiones también una solución se acepta comunmente como la mejor y resulta que volviendo a la raíz del problema podemos encontrar una opción que incluso marca una diferencia comparativa en nuestro producto. Esta empresa eligió la fuerza bruta para parsear logs, y hacer eso les permite dar mejores resultados.

El mueble de IKEA

Cuando vi este mueble de IKEA, os parecerá lo más mundano del mundo, pero para mi fue un “blown my mind”.

¿Dónde están los tiradores? ¡No tiene! Utilizando menos material consiguen la misma funcionalidad. Además no se te engancha el tirador con la ropa al pasar cerca, no tienes que atornillarlos para montarlos con el riesgo de cargarte la madera, no hay que atornillarlos si se aflojan, no… ¡simplemente no hay y no hacen falta! Y cuando vi por primera vez las instrucciones de IKEA… otro “blown my mind”.

¿En qué idioma están? ¡En ninguno! y realmente… ¡en todos!

Dos ejemplos de libro de cómo hacer más con menos.

Hacer más con menos, o hacer casi lo mismo con mucho menos, o menos es más

¿Es posible hacer esto en la informática? Algunos ejemplos.

  • Cuando borras algo de la memoria del ordenador o del sistema de ficheros probablemente lo primero que se nos ocurre sería iterar sobre todos los bytes y setearlos a cero o a un número aleatorio para destruir esa información. Pero resulta que casi siempre lo que queremos es dejar espacio para otro dato, no necesitamos destruir el dato anterior de forma que sea irrecuperable. Es muchísimo más eficiente simplemente marcar ese espacio de memoria o almacenamiento como sucio o disponible y ya sobreescribiremos si necesitamos ese espacio.
  • Las aplicaciones en iOS: al principio ni se podían cerrar, simplemente ibas a otra aplicación y la anterior se cerraba. Pero realmente en ese flujo el usuario no ha realizado una acción explícita para cerrar la app. ¿Para qué hace falta un botón de cerrar? Simplemente dejas de usar la aplicación y punto.
  • Páginas web que no requieren login y simplemente te dan URLs únicas que te guardas en favoritos o compartes.
  • Interfaces que en vez de preguntarle cosas al usuario y darle cientos de opciones son inteligentes y conocen tus intenciones a través del contexto.
  • En vez de poner en una web un editor de texto enriquecido con decenas de opciones puedes usar markdown y punto.
  • Sistemas con aplicaciones siempre a pantalla completa. No hay media docena de acciones posibles para redimensionar ventanas, simplemente las aplicaciones se ejecutan a pantalla completa y punto.
  • Aplicaciones que no tienen un botón de guardar. Guardan por defecto siempre y si quieres puedes deshacer.

Aplicándolo a un producto

También muchas veces que he querido idear un producto empiezo por un target muy grande y me voy dando cuenta que reduciendo el target puedes enfocar el producto mejor: más sencillo, más óptimo y más útil. Por ejemplo en vez de una app para buscar cualquier tipo de local (bares, restaurantes, gimnasios, peluquerías,…), hacer una app sólo para un sector. En vez de hacer un servicio web para cualquier profesional, enfocarlo sólo a freelancers, o sólo a diseñadores, etc. O incluso dentro del mismo sector (moda por ejemplo) hacer una app sólo para un segmento de ese sector (pequeñas tiendas vs grandes cadenas, sólo complementos, etc). Cada segmento tiene sus necesidades particulares y si pretendes abarcarlas todas terminarás con un producto con centenares de funcionalidades de las cuales cada usuario sólo usará una mínima parte. En definitiva haciendo menos cosas muchas veces tienes un producto mejor. Si sabes elegir qué hacer y qué dejar de hacer claro :)

Text

Las guerras santas en los mundos de REST (II)

Hace tiempo ya escribí sobre mi opinión sobre los fundamentalismos en REST. Y en mi experiencia sigo viendo ejemplos día tras día donde esos dogmas no encajan, y la gente me sigue preguntando sobre recomendaciones a la hora de diseñar un API REST. Así que… vamos a la carga con una segunda parte de este tema.

REST hace muchísimo hincapié en:

  1. Ceñirte a los verbos HTTP. Esto es: GET, POST, PUT,… para que tu API sea semántica.
  2. Que tus URLs sean identificadores, esto es: nombres; y no verbos. Una URL no puede contener un verbo.

Esto encaja estupendamente si tus verbos encajan en las necesidades de tu API. Es decir, si tu API se parece mucho a un CRUD pues lo tienes chupado:

  • POST /foo para crear un foo
  • GET /foo para hacer un listado de foos
  • GET /foo/:id para leer un objeto
  • PATCH /foo/:id para actualizar un foo
  • DELETE /foo/:id para eliminar un foo

Ya está, el CRUD de toda la vida. Pero, ¿y si queremos que nuestra API sea más expresiva? Y si yo quiero hacer un POST /foo/:id/like para hacer un like? O un POST /foo/:id/checkin para hacer checkin? O un POST /foo/:id/report para reportar un foo? Por qué no podemos usar más verbos? Por qué diferenciamos un API HTTP de un API de un lenguaje de programación normal? Es como si nos limitásemos a nosotros mismos en el número de palabras que podemos usar en los métodos de nuestras clases. Imaginaos:

    class Foo {

        get(id)

        post(params)

        patch(id, params)

        delete(id)

    }

Y así todas las clases de vuestra API. Tiene sentido? Por qué no puedo tener verbos like, checkin, report,…?

En el artículo anterior hablaba de que los fundamentalistas promulgaban crear objetos Like, Checkin, Report,… para estos casos. Esto es, cuando haces un “like” no haces una operación sobre un “foo”, sino que haces un POST /like pasando el identificador del objeto principal. Y la semántica del API endpoint es que estamos creando un objeto “like” en la base de datos. Pero no siempre esto encaja. Quizá un API endpoint manda un mensaje por SMS/email, y no guardo nada en la base de datos. O quizá estoy usando una base de datos como Redis en la que tengo colecciones, y hacer un like no me genera un identificador porque guardo información que ya tengo en un Set y listo.

Además en un API que sea un mero CRUD no hay problemas, pero a nada que tengamos ciertas reglas de negocio la pureza en el diseño se va…

  • POST: a veces una operación no crea sólo un objeto en la base de datos. A veces se crean más objetos, aunque haya uno principal. Y de esos “subproductos” no crearemos endpoints para la operación de creación.
  • PUT/PATCH: Por reglas de negocio muchas veces el cliente no especifica ni si quiera los parámetros. Ej: estoy trackeando llamadas telefónicas en un call-center y tengo un método finish(); no necesito parámetros, la duration de la llamada es calculada en el servidor. Y por reglas de negocio no quiero que el cliente pueda cambiar ciertos campos. Y por reglas de negocio necesito verbos para saber qué cambiar: accept(), reject(), addNotes(), uploadPictures(), porque se tienen que ejecutar en un orden y tienen diferentes necesidades de autorización. Muchísimas veces en un API el flujo es una máquina de estados y nombro a los estados y creo endpoints para cada paso de un estado a otro: una llamada telefónica tiene funcionalidad para create(), accept(), finish(), report(),… y tiene que ser en ese orden. No quiero crear un API endpoint único PUT/PATCH. Quiero tener varios con URLs diferentes para que quede claro para todo el equipo qué hace cada API endpoint y qué posibles estados hay, etc.
  • DELETE: en la mayor parte de las ocasiones nunca quiero borrar nada, como mucho marcar como borrado.

Además si me apuras hasta quitaría los identificadores de las URLs. Un API HTTP no tiene que ser SEO-friendly. Y no me gusta especialmente tener que concatenar Strings para generar URLs. Me parece más legible al programar tener un hashmap/diccionario de parámetros y listo. Ejemplo en objective-C:

// poniendo el identificador en la URL
NSString *path = [NSString stringWithFormat:@”/foo/%@/messages”, foo.identifier];

// pasando todo como parámetros
NSString *path = @”/foo/messages”;

NSDictionary *params = @{ @”id”: foo.identifier };

Y finalmente sobre los códigos HTTP. Mucha gente pierde mucho tiempo buscando cuál es el código HTTP perfecto para una situación. Sinceramente yo me despreocupo por varias razones:

  • No son suficientes para nuestras necesidades. No puedo expresar situaciones muy concretas, sólo generalidades. Es como si en un lenguaje de programación sólo tuvieras un número limitado de códigos de error y no pudieras hacer excepciones propias.
  • Por ejemplo no existe un modo de expresar un error externo. Si estoy usando una pasarela de pago y esta falla, qué devuelvo, un error interno? Pero si yo no he producido el error! No ha sido un error interno.
  • Es curioso que cuando un POST tiene éxito devolvemos un 201 (creado) y nos sentimos orgullosos porque hemos usado el código de error perfecto para la situación. Pero luego resulta que un GET, un PATCH y UN DELETE devuelven lo mismo en caso de éxito? (200)
  • El código 404 para un API siempre me ha resultado chocante. Deberíamos usarlo sólo si el API endpoint no existe o también si lo que no existe es un registro en la base de datos (el identificador del objeto)?
  • Al final siempre necesito especificar más información de un error en la respuesta. Siempre añado un campo message o similar. Y entonces ser específico en el código HTTP se vuelve redundante.
  • Al final casi siempre acabas usando 200 (éxito genérico), 201 (creado), 403 (fallo de autenticación), 400 (genérico para request inválida), 404 (no encontrado), 500 (genérico para error interno). Y en el cliente miras si statusCode >= 400 y entonces miras la response para encontrar más info del error: { message: ‘Invalid email or passwor’ }. Y listo.

En resumen está bien saber lo que opina la gente, pero esto no es un jardín zen. Hay que ser pragmáticos. Con utilizar las cuestiones básicas es suficiente. A veces hay quien se empecina en alcanzar el nirvana en el diseño de un API cuando hay cuestiones más importantes como:

  • Qué hace el API
  • Tiene todas las funcionalidades que necesitamos
  • Devuelve la información que necesitamos
  • La información está correctamente formateada y es consistente (fechas, números, nombres de campos,…).
  • Es rápida

Como resumen: las prácticas habituales de REST están pensadas para APIs CRUD y bases de datos relacionales. Y ni si quiera hay nada “escrito” para hacer operaciones BATCH, etc. Así que no temáis en romper las prácticas habituales si vuestras necesidades van más allá.

Espero que os haya gustado :)

Tags: REST
Text

Viviendo en USA: diferencias culturales en el día a día

Llevo viviendo unas semanas en Vermont, un estado al noreste de Estados Unidos y fronterizo con Canadá. Es un estado peculiar, poco poblado y muy frío (a 23 de Marzo todavía sigue nevando!). Ya había estado en USA anteriormente, concretamente en Nueva York y California, pero esta es la primera vez que estoy viviendo aquí y no meramente de paso. Por eso desde que he llegado he decidido ir anotando las cosas que me han ido chocando del día a día en este país y en concreto en este estado. Sin ningún orden concreto, allá van:

  • Tanto si te invitan a comer a casa de alguien como en muchos restaurantes, te puedes llevar las sobras (leftovers) a casa en un tupper que ellos llaman doggy bag.
  • El alquiler del apartamento, la luz, etc. lo normal es pagarlo… ¡con cheques! Usan cheques muy a menudo. Cuando te abres una cuenta bancaria te dan un buen fajo de cheques. Y para pagar las facturas de luz, alquiler, etc. sueles dejar un cheque dentro de un sobre, y el sobre lo hechas a una dropbox para tal fin (por ejemplo en el ayuntamiento hay una para pagar la luz).
  • Por contra las cosas pequeñas puedes pagarlas casi siempre con tarjeta. Hasta un par de cafés. Algunos establecimientos no tienen importe mínimo para pagar con tarjeta y otros suelen poner un mínimo de entre $3 y $10.
  • Si no tienes cuenta bancaria también puedes emitir cheques en una oficina postal. Vas, les dices la cantidad, y pagas con tarjeta o con efectivo y con una comisión muy pequeña tienes un cheque de la cantidad que pediste.
  • En cuanto a las tarjetas allí las tarjetas europeas, aunque sean de débito para sus sistemas la tarjeta es de crédito. Si en una pantallita tienes que elegir entre crédito o débito, aunque tu tarjeta sea de débito en Europa tienes que elegir “crédito” o no podrás pagar.
  • Aquí no funcionan todavía las tarjetas con chip y casi nunca te piden que te identifiques para pagar con tarjeta. Normalmente firmas el tiquet y a veces si el importe es bajo ni si quiera tienes que firmar.
  • En los bares el camarero te preguntará si quieres pagar en el momento o más tarde por si pides más consumiciones. Si decides pagar luego te pedirá tu tarjeta y se la guardará! Al final le pedirás cerrar la cuenta y entonces buscará tu tarjeta y te cobrará. Muy seguro todo…
  • Al menos en Vermont es frecuente que en las cafeterías tengas que recoger la vajilla que hayas usado. Hay un sitio donde dejar vasos y cubiertos usados.
  • En casi todas las cafeterías cuando pides un café con leche te dan un café sin leche… Y como en starbucks en un rincón tienes todo lo que te quieras echar (incluida la leche). Y en algunos sitios si pides un refresco te dan un vaso vacío y vas al sitio que toque a llenarlo.
  • El café es muy malo, pero tienen una variedad de tés inmensa. En muchos establecimientos puedes pedir hasta teteras grandes de té, y echar allí el día entero. También es común ir con un termo y que te lo llenen de café o té para llevar.
  • Es muy frecuente ver a gente con bebidas en la mano en cualquier parte, y en menor medida comida. En cualquier tienda, en los medios de transporte, en cualquier parte, hay gente con un termo o un vaso desechable. Quizá tiene que ver que este es un sitio muy frío y la gente no para de beber bebidas calientes para sobrellevar el frío.
  • En cualquier tienda los dependientes te saludan y despiden efusivamente. Te preguntan cómo estás y si pueden ayudarte en algo.
  • Los camareros reciben un salario mínimo y creo que no tienen ni seguro médico ni nada, y se entiende que complementan su sueldo con las propinas. Las propinas en teoría no son obligatorias pero en la práctica tienes que dejar entre un 15% y un 20%. A veces en el tiquet te dejan el cálculo hecho en dos o tres porcentajes :)
  • Para ganarse mejores propinas los camareros suelen preguntarte constantemente si todo está bien (puede llegar a ser bastante cansino) y cuando estás a punto de terminar tu bebida te preguntan rápidamente si quieres otra.
  • Aquí el transporte público es para estudiantes o gente sin recursos. Si no tienes coche, aunque sea viejísimo y medio destrozado, eres un bicho raro.
  • En los autobuses la gente se baja con la calma, y tanto por la puerta de delante como la puerta de atrás, y la gente suele despedirse del conductor con un “Thank you!”.
  • Debido a la nieve la gente se descalza en la entrada de casa para no manchar el suelo, y deja el calzado de calle en un boot tray.
  • Hay gente que para caminar por la calle lleva incluso escarpines.
  • Las casas se llenan en invierno de churretones de hielo que pueden llegar a ser peligrosos si se rompen y se te caen encima. A veces ves zonas de la calle precintadas para que no pases por allí.
  • Debido a que puede haber en ocasiones más de un metro de nieve en algunos sitios (algunos caminos, aceras y bocas de agua para bomberos, etc.) tienen un palo de plástico vertical, para saber dónde están cuando la nieve los cubre.
  • Está mal visto taparse la boca con la mano al toser o estornudar. Se ve como algo anti-higiénico porque luego podrías darle la mano a alguien :) Lo normal aquí es taparse con la manga o la parte anterior del codo.
  • A los niños pequeños los padres les dejan libre albedrío. Si te invitan a una casa con niños probablemente los niños estén de un lado para otro cual monetes en el bosque. Incluso en los bares te puedes encontrar niños danzando por todas partes como si estuvieran en su casa.
  • La gente es suuuuper amable por lo general en este estado. Son capaces de acompañarte de un piso a otro de un edificio si estás buscando algo, decirte dónde encontrar algo en otra tienda si en la suya no lo tienen, disculparse veinte veces por teléfono si tu pedido de walmart se ha retrasado,…
  • Supongo que dada la cercanía a Canadá aquí todo el mundo que reconoce que somos extranjeros se piensan inmediatamente que venimos de Canadá. Se sorprenden cuando les decimos de dónde venimos.
  • Hay gente que ha debido evolucionar o vienen de la era glacial y a pesar de haber hasta -28ºC en la calle son capaces de ir en pantalones cortos, pantalones de pijama o zapatillas de verano.
  • Hay lugares en los que sirven cóckteles calientes. Por ejemplo te puedes tomar un Matte Latte, un Mocha Locca o una sidra caliente con naranja y canela (hot mulled cider) entre muchos otros.
  • La variedad de cervezas aquí es impresionante. No exagero cuando digo que puedes encontrar 100 tipos de cervezas en un supermercado.
  • En Vermont (no en otras partes de USA por lo que he podido ver) consideran la pizza un plato americano y las pizzerías tienen ambientación americana por lo general y en los restaurantes italianos encontrarás pasta, pero no pizza.
  • No es frecuente tener lavadora en tu propia casa. Lo normal es tener lavadoras que funcionan con monedas (de 25 cents, llamadas quarters) en el sótano (como en Big Bang Theory). Y en algunas casas ni si quiera tienes en el sótano y necesitas ir a una lavandería a pie de calle.
  • Las casas suelen alquilarse sin muebles. Es raro encontrar apartamentos en alquiler amueblados.
  • Como las antiguas pesetas aquí el tamaño de las monedas puede no corresponderse con su valor. Así la moneda de 10 cents es mucho más pequeña que la de 5 cents. Los billetes tienen todos el mismo tamaño.
  • Cuando conoces a gente y les caes bien pueden despedirse diciendo que les gustaría quedar un día a cenar contigo. Pero si no lo dicen con mucha insistencia es simplemente una forma cordial de despedirse y no significa que vayan a llamarte para cenar un día con ellos.
  • Un amigo en España me enseñó las cenas de traje: yo traje tortilla, yo traje croquetas, yo traje albóndigas,… Vamos, que cada uno lleva una cosa. Aquí eso se llama potluck. Nos invitaron a uno y estaba todo muy bueno.
  • Sólo hemos visto una vez galletas maría. Normalmente hay galletas muy buenas, pero blandas, o demasiado dulces. Y por otro lado tienes crackers: galletas muy crujientes y muy saladas.
  • Los postres, bizcochos, etc. son muy densos. Están muy buenos por lo general, pero te llenarán el estómago enseguida. No conocen el hojaldre :)
  • Al menos en Vermont hay muchísima comida orgánica. Y en general mucho aprecio a lo natural y a lo agrícola.
  • El supermercado que solemos frecuentar es una cooperativa que trabaja en colaboración con los granjeros locales. Todos los productos locales están etiquetados adecuadamente para indicarlo y además la organización te hace descuentos si haces horas de trabajo a la comunidad.
  • En España llevamos horarios de trabajo peculiares, pero los de aquí son incluso más extraños. Por ejemplo los supermercados abren todos los días hasta las 11 de la noche. Y los fines de semana también abren. Y teniendo en cuenta que la gente suele salir los fines de semana desde las 7 de la tarde, perfectamente alguno habrá que después de tomarse dos pintas y antes de irse a casa pasará por el supermercado a comprar una leche con galletas.
  • Algunos días ha nevado tanto que se suspenden las clases en las escuelas. Y para recuperar esos días perdidos están los makeup days  al final del semestre.
  • Las personas suelen expresar exageradamente sus sentimientos. Cuando algo les gusta es maravilloso o lo mejor del mundo. Basta ver los programas de televisión para ver cómo aplauden o gritan en muchas ocasiones.
  • Los bomberos y policía también son muy exagerados. Parecen naves espaciales con tantas luces y sonidos.
  • Hay bastantes incendios. Supongo que parte de la culpa es porque las casas son de madera normalmente y los sistemas eléctricos muy antiguos.
  • La gente hace ruidos al comer y no suelen cerrar la boca.
  • Hay bastante gente por la calle que se habla sola o hace cosas extrañas. En general en todas las ciudades que he visitado creo que tienen un nivel de enfermos mentales demasiado elevado.
  • El tamaño de las cosas también suele ser exagerado. Las neveras, los microondas donde te puede caber un pavo entero, las lavadoras, las porciones del papel de cocina, las sillas, las sillas de ruedas, los vasos de refresco, las hamburguesas, las camionetas,…
  • La distribución de los mandos de la vitrocerámica tiene un fallo de usabilidad grave. Los mandos suelen estar en la pared, así que tienes que pasar el brazo sobre los fogones para controlarlos.
  • En los mercados que he visitado suele haber más establecimientos de comida preparada que establecimientos con comida sin cocinar.
  • Las señales de tráfico suelen tener mucho texto, o simplemente ser todo texto, incluso para indicarte un simple giro a la dcha.
  • Hay bastantes tiendas de segunda mano de ropa y muebles.
  • En las tiendas es común ver dependientes de mediana edad o mayores. Eso me sorprendió gratamente. Incluso en una tienda de Gap por ejemplo es normal que te atienda una señora mayor.
  • Las farmacias son auténticos supermercados. En una supuesta farmacia te pueden vender un fregasuelos, cerveza, cosméticos o tarjetas de felicitación de San Valentín.
  • La calidad de las cosas baratas suele ser muy cuestionable. Por ejemplo los muebles baratos tienen peor calidad que los de Ikea, y no son más baratos.
  • Las conexiones a Internet son caras. En AT&T pagas $40/mes por 200 míseros megas de datos. Supongo que el tipo de construcciones en EEUU (casas de pocas alturas) hace que la población esté muy dispersa y la infraestructura sea muy cara.
  • Por otro lado la ropa es muy barata. Unos vaqueros de marca puedes encontrarlos hasta por $15. Y es curioso que siempre hay ofertas y rebajas en todas las tiendas en cualquier momento del año.
  • Aquí reciclan bastante, pero en vez de haber contenedores dispersos por la ciudad el ayuntamiento te da una caja azul que tienes que llenar y dejar en la acera para que un camión la recoja.
  • Las personas aquí necesitan un mayor espacio vital y no les gusta demasiado el contacto físico. Normalmente tienes que dejar más de medio metro de distancia con la gente si es posible. Y en una ocasión intenté ayudar a una señora que se había caído al suelo por el hielo y no quiso que la tocara.

Como conclusión, decir que la adaptación es fácil. No es como irse a vivir a un país de oriente medio o de Asia, pero me ha sorprendido la cantidad de detalles diferentes en el día a día. No obstante y a pesar del frío estamos pasando una estupenda experiencia en este país :)

Text

El Santo Grial de los programadores es controlar el tiempo

En el artículo anterior comentaba que la programación y el software tienen evoluciones incrementales. Que no hay revoluciones.

Pues bien, sí pienso que hay una cosa que cambiaría drásticamente cómo se construye software actualmente y tiene que ver con el tiempo. En dos aspectos: el tiempo en las comunicaciones y el tiempo en nuestros runtimes.

El tiempo en las comunicaciones

El software se ejecuta sobre un hardware y este tiene limitaciones por las leyes de la física. Una de ellas es la velocidad de la luz. Por muy rápido que sea un impulso eléctrico u óptico nunca será instantáneo. Ahí está la velocidad de la luz y las impurezas de los materiales para impedírnoslo. Así que no hay nada instantáneo, siempre hay una latencia. En una máquina la latencia es importante, pero no suele ser algo crítico. Sin embargo la latencia en las comunicaciones es un problema importante. Sin más ni menos creo que es el problema más complicado o imposible de resolver en la informática hoy en día. Puedes tener miles de cores en miles de CPUs, miles de GB de RAM y millones de GB de almacenamiento, es sólo cuestión de dinero, pero es posible conseguirlo. ¿Por qué las aplicaciones tienen problemas para escalar entonces? Si es posible y cada vez más barato tener miles de gigas de todo! Pues sencillamente: por la maldita latencia. Es el origen del problema, aunque casi nunca se le eche la culpa.

Cuando necesitas escalar puedes hacerlo verticalmente (mejorando tu máquina) u horizontalmente (añadiendo más máquinas). Escalar verticalmente tiene su límite, así que tarde o temprano necesitas escalar horizontalmente. Entonces, da igual lo que tengas que escalar, si tienes que compartir estado entre máquinas (base de datos, ficheros,…, vamos prácticamente siempre) las máquinas tienen que comunicarse. Y la comunicación no es instantánea. Así que por ejemplo si guardas un dato y quieres replicarlo, la replicación no es automática, y mientras se replica el dato puede venir otra operación de escritura. Y no sólo eso; si un mismo dato se intenta modificar en dos servidores con una diferencia de tiempo muy pequeña? Cómo saber con qué versión quedarse si ni si quiera podemos saber si los relojes de los servidores están perfectamente sincronizados? Ya ves, empiezan los problemas. La comunicación no es instantánea así que la comunicación es asíncrona y mientras una tarea se realiza otras operaciones incompatibles pueden estar sucediendo lo cual nos lleva a desarrollar algoritmos para gestionar conflictos, sincronizaciones, replicaciones, etc. Sin ir más lejos Google utiliza relojes atómicos sincronizados via GPS para tener una sincronización de hora casi perfecta entre servidores y poder hacer las operaciones de sincronización / replicación lo más precisas posible.

Y si lo piensas, especialmente con bases de datos, todos los problemas vienen derivados de cómo gestionar todo en un mundo asíncrono. Se implementan algoritmos de replicación, pero no hay una silver-bullet. Has oído hablar del CAP theorem? De NoSQL? Todo viene porque es imposible tener un “estado concreto en un momento dado” sino que cada parte del sistema puede tener un estado diferente en cada momento, porque no hay una sincronización instantánea. Y en ningún caso funciona “añadir más máquinas”, porque sólo haces crecer el problema: más máquinas = más comunicación entre máquinas = más datos que sincronizar. Recomiendo esta lectura: Clocks Are Bad, Or, Welcome to the Wonderful World of Distributed Systems. Para mi fue un “blown my mind”.

Y el problema es que la física está en nuestra contra: la velocidad de la luz y los materiales nos impiden reducir la latencia hasta un punto que fuera marginal, que no importase. Veremos durante muchos años que el ancho de banda en las comunicaciones crecerá una barbaridad, pero la latencia seguirá ahí, siendo un problema, sobre todo para sistemas escalables. Ni si quiera el entrelazamiento cuántico nos da una esperanza:

No obstante, no parece que se pueda transmitir información clásica a velocidad superior a la de la luz mediante el entrelazamiento porque no se puede transmitir ninguna información útil a más velocidad que la de la luz. Sólo es posible la transmisión de información usando un conjunto de estados entrelazados en conjugación con un canal de información clásico, también llamado teleportación cuántica. Mas, por necesitar de ese canal clásico, la información útil no podrá superar la velocidad de la luz.

El runtime

El otro factor-tiempo que sí podemos abordar es el de dominar el tiempo en nuestro runtime. Estamos acostumbrados a programar de la siguiente forma: programar, ejecutar, como mucho depurar, reescribir y vuelta a empezar. Mi sueño es tener que darle sólo una vez al botón de “ejecutar” y a partir de ahí poder hacerlo TODO en tiempo de ejecución. He visto este concepto nombrado de varias maneras: live coding, interactive programming,… Y todo lo que he visto se basa en poder hacer hot swapping del código en tiempo de ejecución. Es decir, pausar la ejecución, cambiar código y “seguir p’alante”. ¡Que no está nada mal! Pero a mi me encantaría otra cosa: poder ir hacia atrás en el tiempo. Poder no sólo cambiar el código, sino volver a un estado anterior de la ejecución del programa. Y poder ir hacia adelante y hacia atrás sin problema. Y que además el código se ejecutase en un entorno “virtual” en el que TODO pudiera volver hacia atrás o hacia adelante en el tiempo. Es decir, el reloj del sistema, si he borrado un fichero y vuelvo hacia atrás que el fichero vuelva a aparecer, etc.

Imaginaoslo: poder probar una porción de código una y otra vez sin tener que preparar el contexto de ejecución una y otra vez y pudiendo ir hacia adelante y hacia detrás para saber exactamente qué hace el código y por qué.

El salto cualitativo y cuantitativo que podríamos dar en el software que hacemos sería brutal. No puedo explicarlo con palabras. Mejor os dejo este vídeo con el que lo vais a entender. Si os da pereza basta con que veáis desde el minuto 29 al 35 para ver la diferencia entre trabajar de manera no interactiva e imaginarse cómo podría ser trabajar de manera interactiva. Pero os aconsejo verla entera. Esta charla para mi fue un “blown my mind” en toda regla.

Bret Victor - Inventing on Principle from CUSEC on Vimeo.

Text

Programadores en la Edad de Piedra

En los últimos meses he oído afirmaciones del siguiente estilo:

  • Un lenguaje de programación no te da más productividad que otro
  • La dificultad de comprender y mantener cierto código no depende del lenguaje sino sólo de la experiencia del programador en el lenguaje.
  • Los lenguajes de programación y en general el sector está estancado y no evoluciona.

Estoy bastante desacuerdo con las tres.

Sobre el primer punto primero hay que aclarar qué se entiende por productividad. Podemos definir la productividad como la cantidad de trabajo realizado por unidad de tiempo. Para un programador considero que hay tres cosas te dan más o menos productividad

  1. El lenguaje de programación. Te da más o menos productividad dependiendo de su verbosity, facilidad para hacer tareas complejas como concurrencia o gestión de memoria, estructuras de datos incluidas en el lenguaje, sintaxis, etc.
  2. Las herramientas: tanto librerías disponibles para tu lenguaje / plataforma como el entorno de desarrollo, el compilador, sistema de dependencias, etc.
  3. El runtime: máquina virtual, intérprete y el dispositivo en el que tengas que ejecutar el código (tu ordenador, un servidor, un dispositivo móvil, un simulador,…).

Por ejemplo en mi caso que trabajo tanto en backend, como en frontend, como haciendo aplicaciones móviles (sobre todo iOS pero también algo en Android), tengo la experiencia que desarrollar para móviles es mucho más improductivo porque la compilación, ejecución y pruebas son mucho más lentas. Además los lenguajes son más verbosos (Java y Objective-C) en comparación con JavaScript por ejemplo. Desde mi experiencia con varios entornos no puedo entender que alguien diga que todos los entornos son igualmente productivos. Para mi está claro que hay entornos que te hacen perder más tiempo que otros y por tanto todo se ralentiza.

Hay quién dirá que el problema es el runtime o las herramientas, y no el lenguaje de programación. Bien, pues yo incluyo el lenguaje de programación sí o sí. No entiendo cómo alguien puede decir que todos los lenguajes de programación son igual de productivos. Entonces por qué creamos lenguajes de alto nivel? Por qué no programamos siempre directamente en ensamblador? Si todos los lenguajes son igual de productivos qué sentido tiene programar en lenguajes menos eficientes? Y por qué los lenguajes de programación evolucionan añadiendo nuevas características a su sintaxis? Pues para mejorar la vida al programador y por tanto reducir el tiempo invertido o malgastado en hacer diversas tareas y por tanto… en mejorar la productividad!

Bueno, dejando atrás ensamblador… creo que no hay mejor ejemplo que Objective-C y su runtime para ver cómo un lenguaje de programación evoluciona ayudando al programador a hacer más cosas en menos tiempo == mejorar su productividad.

Objective-C es un superconjunto estricto de C y por tanto hereda muchos de sus inconvenientes, pero también lo hace muy eficiente por ser de muy bajo nivel y permite usar cualquier proyecto en C desde Objective-C. Este lenguaje es super-verbose. Hace no mucho tiempo para crear una clase con un atributo y sus métodos get/set requería 4 pasos:

  1. Definir la propiedad
  2. Definir los métodos get/set en el fichero .h
  3. Implementar los métodos get/set en el fichero .m
  4. Liberar la memoria de la propiedad en el método dealloc.

WAT????!!! Efectivamente, si ahora no te queda claro eso de que unos lenguajes de programación son más productivos que otros creo que no perteneces a este mundo.

Una de las cosas que a muchos programadores les chocará sin duda es el cuarto punto. Liberar memoria??? Sí, Objective-C no tiene una máquina virtual detrás y por tanto la gestión de memoria no es automática. Eso ya es un gran feature para perder muchas horas en algunas ocasiones buscando leaks, objetos zombies (doble-free de memoria), etc. Esto me permite (creo) zanjar el tema de la productividad, y pasar a otro tema, el de la evolución de los lenguajes. Pero continúo con Objective-C porque resulta que de golpe y porrazo los ingenieros de Apple se sacaron de la manga una cosa denominada ARC que para mi es un “blown-your-mind”. Resulta que ahora el 99% del tiempo ya no tienes que gestionar la memoria, porque… atención… el compilador se encarga de esa tarea. Sí, el compilador, no una pesada máquina virtual. Y el 1% del tiempo restante sólo es en caso de relaciones cíclicas en las que en un lado de la relación tienes que indicar que la relación es débil, y nada más.

Y Objective-C también ha mejorado mucho en los últimos años. Han añadido bloques (closures), está Grand Central Dispatch para la gestión de la concurrencia que me parece de lo mejorcito en todos los lenguajes de programación que he tocado, etc. Así que discrepo totalmente que todos los lenguajes de programación sean igual de productivos, porque un mismo lenguaje de programación (Objective-C) puede evolucionar y hacerte más productivo, así que más motivo para que entre lenguajes de programación pueda haber grandes diferencias.

Pero bueno, en general discrepo mucho con que la industria del software esté estancada en el tema de lenguajes de programación. En los últimos años hemos pasado de hacer aplicaciones desktop pesadas (ej: Office, CRMs de escritorio, etc) a aplicaciones desktop multinúcleo donde la concurrencia cada vez es más importante y han surgido varios paradigmas para solventarla. Y también hemos empezado a hacer aplicaciones móviles que requieren mayor optimización, y a hacer aplicaciones servidor que requieren miles de conexiones concurrentes. También estamos usando bases de datos distribuidas (multitud de bases de datos han surgido), etc. Y los lenguajes de programación y entornos se han ido adaptando a estas circunstancias: programación asíncrona, programación basada en eventos, nuevas librerías de concurrencia, etc. También estamos viendo nuevos lenguajes, transpilers, LLVM, nuevas herramientas de debugging,..

Creo simplemente que el que crea que va a surgir un lenguaje de programación que rompa todos los esquemas de la noche a la mañana está equivocado. Las evoluciones no son dramáticas, sino más bien incrementales. Pero eso no implica que no haya evolución. Creo que el que no la ve es porque no se ha tomado el tiempo en investigarla y es él el que no ha evolucionado en las herramientas que usa, simplemente.

Text

Comandos útiles de npm


Pequeña introducción y algunos tips & tricks de npm aprendidos con el tiempo :)

npm init

Lo más básico. Deberíamos empezar un proyecto por aquí. Te pregunta una serie de cosas y te genera el fichero package.json para tu proyecto.

npm install <paquete> —save

Te instala un paquete y además añade la dependencia en tu package.json.

Por si alguna vez se nos olvida poner “—save” también podemos utilizar posteriormente require-analyzer que se encarga de poner al día el fichero package.json según lo que tengamos instalado en node_modules.

npm install <paquete> —save-dev

Como el anterior pero guarda la dependencia en devDependencies (dependencia en desarrollo) en vez de simplemente en dependencies.

npm install —production

Útil en el servidor para instalar las dependencias omitiendo las dependencias de desarrollo.

npm shrinkwrap

Genera un fichero npm-shrinkwrap.json que es un fichero que describe las dependencias de tu proyecto con los números de versión exactos que tengas instalados. Normalmente se intenta ser flexible con las dependencias al instalarlas (ej: un módulo afirma ser compatible con otro en sus versiones “1.1.x”), pero a la hora de instalar los paquetes en el servidor no queremos sorpresas. Esto lo conseguimos con el fichero npm-shrinkwrap.json. Si existe, el comando “npm install” se basará en este fichero en vez de en el package.json. Así de sencillo y útil.

npm outdated

Nos muestra qué paquetes que tenemos instalados tienen actualizaciones disponibles. Pero ojo, nos mostrará sólo aquellas actualizaciones que cumplan el rango de versiones definido en nuestro package.json. Así si por ejemplo dependemos de express “3.0.x” nunca nos indicará que haya una actualización a express “3.1.0” por ejemplo. Esto es interesante porque en principio debería evitar actualizaciones no compatibles hacia atrás con nuestro código.

Pero también nos interesaría saber si nos estamos quedando obsoletos. Para solucionar esto podemos usar npmedge que busca justamente las actualizaciones que no entren en el rango definido en el package.json. 

npm star <paquete>

Marca como favorito el paquete en npmjs.org. No tiene más utilidad que darle kudos a los desarrolladores del paquete.

Estos y algunos tricks más en NPM tricks

Tags: npm javascript
Text

Dos mentiras del mundo estartapil

Préstamos públicos como inversión privada

En ocasiones he visto anuncios en Loogic o incluso en Techcrunch de que ciertas startups recibían XXX miles de euros de inversión. Pero sin embargo una gran cantidad de ese dinero realmente eran préstamos públicos y no inversión privada. Vamos, que gran parte del dinero era dinero que las startups tenían que devolver, no dinero a fondo perdido.

Hay una modalidad de ayudas de distintas entidades públicas que se llaman préstamos participativos. Los más populares son ENISA y NEOTEC. Consisten en que te dan un préstamo, que tendrás que devolver, pero suele tener condiciones bastante ventajosas. Otra cuestión importante es que la startup debe presentar un plan y un presupuesto, y la entidad pública cubre una parte del presupuesto (un 70 - 85% normalmente) pero el resto lo tienen que aportar los socios. Es por tanto frecuente que para cubrir ese porcentaje restante se recurra a inversores privados (por ejemplo business angels). Pero lo que a veces también ocurre es que se comunica a los medios el presupuesto total como inversión privada, cuando realmente sólo ha sido un 15 - 30% de lo anunciado.

Adquisiciones de mentira: los acquihire

Otra mentira muy extendida se da sobre todo en empresas de EEUU. Anuncian que han sido compradas por una empresa grande, pero realmente no es una adquisición, sino que lo que ocurre es que la empresa grande contrata a los empleados de la otra compañía, pero puede ser que ni si quiera adquieran la tecnología ni los productos. Como ejemplo de esto tenemos Sparrow (supuestamente comprados por Google) o Acrylic (supuestamente comprados por Facebook).

Veamos por ejemplo Sparrow. El producto salvo por un par de pequeñas actualizaciones ha quedado estancado. No sale el logo de Google en el producto por ningún lado, no se ha “mergeado” con la app nativa de GMail en absoluto. Si vas a la Apple Store pone que Sparrow pertenece a “Sparrow by Google” y no a “Google Inc.” porque no está subida ni traspasada a la cuenta de Google en la Apple Store. Sin embargo cuando una empresa compra realmente a otra, pongamos el ejemplo de cuando Twitter compró Tweetie, inmediatamente cambia el logo, el branding, la imagen corporativa,… O por ejemplo en la última adquisición de Facebook, vemos cómo lo anuncian en su blog. ¿Hemos visto algún anuncio cuando “compraron” Acrylic? ¿Google dijo algo cuando “compró” Sparrow?

Estas adquisiciones se suelen llamar acqui-hire porque es una “adquisición de los empleados” pero no de la compañía. Los socios e inversores no reciben una suma millonaria por la compra de la compañía, sino que los empleados reciben un nuevo sueldo de la empresa “compradora”, pero se anuncia a bombo y platillo como una compra y no se dice nunca por cuánto (porque no hay compra real). Simplemente se “invita” a que la gente especule con la cantidad (que no existe). Aquí lo explican muy bien (aunque bastante duramente): Acqui-hire Is Just Another Way to Spell Failure

Tags: startup
Text

4 errores a evitar en JavaScript

Todos los lenguajes y herramientas tienen sus WAT? Pero más allá de casos extremos del lenguaje voy a exponer 4 problemas típicos en los que puedes caer si programas en JavaScript

1.- Variable Hoisting

Dado el siguiente código, ¿qué crees que sale por consola?

var someVariable = 'value1'
someFunction()

function someFunction() {
    console.log('someVariable', someVariable)
    var someVariable = 'value2'
}

¿Saldrá “value1” o saldrá “value2”? Pues no, sale “undefined”. En JavaScript resulta que el intérprete inicializa a “undefined” todas las variables que se declaran (ponemos “var”) en un scope al inicio de dicho scope. En nuestro caso si quitamos el “var” dentro de “someFunction” por consola aparecería “value1”, pero como la hemos declarado como variable dentro de ese scope (usando “var”) el intérprete la inicializa a “undefined” al inicio de ese scope (la función “someFunction”). Así que lo mejor es que declaremos las variables antes de usarlas (obvio, no?) y tener cuidado con los scopes.

Más información en JavaScript hoisting explained.

2.- Ojo al crear objetos con literales

Dado este código, ¿qué aparece por consola?

var key = 'foo', value = 'bar'
var obj = { key: value }
console.log('value', obj[key])

Tendría que salir “bar” no? Pues no, sale “undefined” porque “key” en la segunda línea es realmente un literal, y no el nombre de una variable. Es un literal aunque no vaya entrecomillado (que podría ir, de hecho en JSON es obligatorio). La solución es hacer lo siguiente: “var obj = {}; obj[key] = value”. Lo malo es que esto nos impide algunas declaraciones “inline” de objetos. Por ejemplo nos impide poder hacer:

var variable = getVariableName();
someFunction({variable: value})

Esto no funciona por lo mismo que antes, porque en la construcción del objeto “variable” es un literal.

3.- Ojo con las propiedades “ocultas” de un objeto

Supongamos que tenemos este código que simplemente dado un array de palabras (strings) cuenta cuántas veces aparece cada una:

var words = ['to', 'be', 'or', 'not', 'to', 'be']
var result = countWords(words)
console.log('result', result)

function countWords() {
    var count = {}
    for (var i = 0; i < words.length; i++) {
        var word = words[i]
		if (count[word]) {
			count[word]++
		} else {
			count[word] = 1
		}
	}
	return count
}

Resultado:

result { to: 2, be: 2, or: 1, not: 1 }

Perfecto, no? Bien, ahora vamos a poner una palabra más en el array de “words”. Añadimos “constructor”. Ejecutamos y el resultado es…

result { to: 2, be: 2, or: 1, not: 1, constructor: NaN }

WAT? ¿Qué ha pasado con “constructor”? Bien, pues la cuestión es que la declaración “var count = {}” no crea el objeto totalmente vacío, sino que hay una propiedad “oculta” de nombre “constructor” que apunta a la función que creó el objeto. En este caso el literal “{}” es como haber hecho “new Object()” y count.constructor apunta a dicha función Object. Bueno, estrictamente la propiedad “constructor” la posee el prototipo de “count”. Para más información podemos consultar la documentación de Mozilla al respecto. Bueno, ¿pero cómo solucionamos el asunto? Para solucionar nuestra pequeña función podemos hacer dos cosas.

  • O bien crear el objeto “count” con “var count = Object.create(null)” para crear el objeto sin prototipo (por eso pasamos null)
  • O bien cambiar el “if (count[word])” por “if (count.hasOwnProperty(word))” que chequea si el objeto posee esa propiedad sin tener en cuenta el prototipo del objeto.

4.- Bucles y closures

Es muy típico al empezar a programar en JavaScript equivocarnos en algo como esto:

for (var i = 0; i < 10; i++) {
    setTimeout(function() {
		console.log('i', i)
	}, 100)
}

Pensamos que por pantalla va a salir la secuencia 0, 1, 2,… y sin embargo sale el número 10 diez veces. WAT? El ejemplo usa la función setTimeout pero podría ser cualquier función asíncrona, o podríamos estar añadiendo un handler a un objeto del DOM, etc. El tema está en que el bucle itera todos los valores y cuando la función se ejecuta, el bucle ya ha terminado y la variable “i” tiene siempre el valor máximo para todas las ejecuciones de la función. ¿Cómo lo solucionamos? La solución más utilizada es generar una función intermedia que, como closure que es, guarde el valor en cada momento con otro nombre de variable:

function createLoopHandler(x) {
    return function() {
		console.log('i', x)
	}
}

for (var i = 0; i < 10; i++) {
	setTimeout(createLoopHandler(i), 100)
}

He hecho el ejemplo más verbose para que sea más legible, pero podráimos haber usado funciones “inline” (sin tener que declarar una función externa “createLoopHandler”. Bien, pues esto es lo más típico, pero también hay otra manera interesante que es usando la sentencia “with”.

var n = 10
for (var i = 0; i < 10; i++) {
    with({x:i}) {
		setTimeout(function() {
			console.log('i', x)
		}, 100)
	}
}

Esta última solución es muy clara y sencilla, pero habrá que tener cuidado porque la sentencia “with” tiene otras implicaciones. Más información sobre "with" en la documentación de Mozilla.

Actualizado: si estamos recorriendo una colección también podemos usar un iterador, como Array.forEach (que no está soportado en todos los navegadores) o jQuery.each

Tags: javascript
Text

JSONP y las curiosas respuestas JSON de Google

Hoy en día nos puede surgir la necesidad de usar desde una web un API / webservice externo. Tenemos el problema de que no podemos hacer llamadas AJAX a otros dominios. Bueno, sí con CORS, pero para eso el navegador lo tiene que soportar y el servidor destino también. Así que de momento no es suficiente siempre.

Pues JSONP surge de esa necesidad. Es un hack en toda regla, pero que funciona. No podemos hacer peticiones con XMLHttpRequest a otros dominios pero sí podemos generar dinámicamente tags <script> que apunten a URLs con otros dominios. Si el servidor a través de una URL nos devuelve un JavaScript como el siguiente: callback_function({“foo”:1234}) y previamente implementamos “callback_function” ya podemos hacer llamadas a webservices externos! Simplemente tenemos que implementar la función de callback, generar un tag <script> con dicha URL y esperar que la función sea llamada con los datos como argumento. Bien, pues eso es en esencia JSONP. Aunque tiene algunos problemas:

  • Sólo podemos hacer peticiones GET. Pero normalmente el servidor soporta algún parámetro para indicar qué método HTTP usar. Este parámetro suele llamarse “_method” y a la técnica se le suele referir como “method_override”.
  • No podemos responder con códigos HTTP de error o de redirección, ya que el navegador directamente ignorará el contenido de la respuesta y la función de callback no será llamada. Así que deberíamos incluir el código de error en la respuesta y devolver normalmente un código HTTP 200.

Estos problemas y la necesidad de poder definir en un parámetro de la URL el nombre de la función de callback implican que hacer que nuestros webservices sean JSONP-friendly requiera ciertos cambios. Más información en este estupendo artículo: Stripe.js and JSONP.

Defenderse de navegadores con bugs

A veces queremos lo contrario, no queremos que nuestra web sea llamada desde otros dominios. En principio no tendríamos que hacer nada ya que por AJAX no van a poder hacer peticiones desde fuera, y por JSONP si no generamos una llamada a una función y solamente devolvemos JSON, ese JSON se descargará, pero el contenido no se podrá gestionar por ninguna función de callback porque no la hay.

O no… Resulta que algunos navegadores cometen errores. Por ejemplo en versiones antiguas de Firefox se pueden sobreescribir la función Array, Object, Error,… y por tanto inyectar código que sería ejecutado al parsear cualquier estructura JSON, sin necesidad de llamar a funciones de callback. Así que aunque no implementemos nada para que otros consuman nuestros “webservices” los navegadores pueden contener errores y nuestros usuarios ser atacados por estos bugs.

Para evitar esto, algunos servicios como los de Google devuelven respuestas JSON que no son JSON del todo. Añaden “while(1);” o “&&&BLAH&&&” para impedir que el JSON se procese tal cual y sólo una llamada AJAX y no una llamada por tag <script> pueda procesar el resultado. Y como las llamadas AJAX están restringidas (mientras no usemos CORS), problema resuelto. La única pega es que nos añade un poco de complejidad tanto en servidor como en cliente. En servidor tendremos que devolver la estructura JSON modificada con ese prefijo que evita parsearse o procesarse correctamente, y en el cliente tendremos que descargar el contenido como un string, manipularlo para quitar el prefijo y luego parsearlo con eval() o con alguna librería.

Más información:

Text

Los programadores no necesitamos vanas promesas

Nuestra industria está plagada de modas. Todavía recuerdo la programación orientada a aspectos, la inyección de dependencias, la sobreutilización de XML, la arquitectura orientada a servicios, programación funcional,… Algunas de estas modas quedan en nada, y de otras quedan algunos posos que es la esencia útil después de quitarles polvo y paja.

Pues bien, de un tiempo a esta parte se oye un murmullo en forma de… promesas sobre todo en el mundo de JavaScript. Han surgido al menos uno, dos y tres frameworks, una especificación y otra especificación y tropecientos artículos de considerable extensión a favor y en contra. Incluso algunos básicamente pregonan que la gente no se está enterando de qué va esto. Qué locura. Con tanta agitación me he querido enterar bien y después de leer y leer y de informarme todo lo posible llego a la conclusión de que las promesas no me aportan nada nuevo y me hacen cambiar mis APIs y ¡hasta me hacen escribir más código!

¿Pero de qué va esto de las promesas?

Para explicarlo me voy a saltar a otro lenguaje donde creo que sí son útiles. Me voy a Java un momento pero casi cualquier otro lenguaje valdría. Supongamos que queremos descargar contenido de dos páginas web que devuelven texto plano y lo queremos concatenar. Lo común en un lenguaje como Java donde las APIs suelen ser síncronas sería hacer algo así (voy a obviar todo el tiempo la gestión de errores por brevedad):

String a = descargarContenidoWeb("...");
String b = descargarContenidoWeb("...");

// hacer algo con a y b
String c = a + b;

Esto está bien, pero es mejorable porque como las llamadas son bloqueantes (esperan) la segunda descarga sólo empieza cuando la primera ha terminado. Veamos cómo sería usando Future (la forma estándar de usar promesas en Java). Reimplementaríamos el método descargarConteniedoWeb internamente para que devolviese una promesa y luego lo usaríamos así:

Future<String> a = descargarContenidoWeb("...");
Future<String> b = descargarContenidoWeb("...");

// hacer algo con a y b
String c = a.get() + b.get();

Nada más llamar a descargarContenidoWeb el contenido se empieza a descargar, pero la ejecución no se bloquea y la segunda descarga también se empieza a ejecutar en segundo plano a la vez que la primera. Como el contenido se está descargando y todavía no podemos viajar al futuro, no se devuelve un String con el contenido, sino una promesa. Pero tarde o temprano queremos obtener finalmente el contenido. Para eso está el método get() de las promesas. Este método chequea si la descarga ha terminado, y si no ha terminado bloquea hasta que termine. Con el get() estamos bloqueando, pero las descargas ya se han lanzado a la vez en segundo plano. Sencillo y útil, no?

Ahora volvamos a JavaScript. En JavaScript no hay APIs bloqueantes (salvo muy puntuales excepciones). Las funciones que pudieran ser bloqueantes en vez de devolver una variable reciben un callback como parámetro que será llamado cuando la operación termine. Esto lo habremos visto muchas veces si hemos usado JavaScript en el frontend (con jQuery por ejemplo) para hacer AJAX o para escuchar eventos HTML, etc. Así pues una llamada a la función descargarContenidoWeb tendría que ser una cosa así

descargarContenidoWeb('...', function(err, contenido) {
	// ...
})
console.log('Esto ese ejecuta mientras se descarga el contenido')

Como la función no bloquea podemos llamar a la función dos veces consecutivas sin anidación, para descargar los contenidos en paralelo.

descargarContenidoWeb('...', function(err, a) { ... })
descargarContenidoWeb('...', function(err, b) { ... })

Listo, ya estamos descargando los contenidos en paralelo. Pero… ahora tenemos dos funciones de callback y necesitamos hacer algo cuando terminen ambas descargas. ¿Cuándo sabemos que las dos han terminado? ¿Dónde ponemos ese código? ¿Cómo nos las arreglamos? Bien, veamos una forma rápida de gestionarlo

var _a = null, _b = null
function finalizar() {
    if (_a && _b) {
        var c = _a + _b
}
}
descargarContenidoWeb(function(err, a) {
_a = a; finalizar()
})
descargarContenidoWeb(function(err, b) {
_b = b; finalizar()
})

Mmmm… Solucionado, pero poco elegante y poco legible. ¿Se puede mejorar? Sí, con un callback handler como async (se puede usar tanto en nodejs como en el navegador; en nodejs es el segundo módulo más popular). Con async nuestro problema podría solucionarse así:

var urls = ['...', '...']
async.map(urls, descargarContenidoWeb, function(err, results) {
    var c = results[0] + results[1]
})

La función map() recibe un array de elementos y una función que los procesará. Finalmente devuelve un array de resultados. Así de sencillo. 

¿Y qué hay de las promesas? Bien, pues con promesas en vez de usar los callbacks de toda la vida tendríamos que reimplementar la función descargarContenidoWeb para que devolviese una promesa. Después usarla con Q sería una cosa así:

var a = descargarContenidoWeb('...')
var b = descargarContenidoWeb('...')
Q.allResolved([a, b]).then(function (promises) {
    var c = a.valueOf() + b.valueOf()
})

¿Queda bien, no? Sí, pero no veo ninguna mejora, incluso ocupa más código, y hemos tenido que reimplementar la función y añadir un framework externo que tendrá que usar tanto quien implementa la función como quien la usa, y el nivel de anidamiento es el mismo, y…

…Y además librerías como async hacen muchísimas más cosas sin tener que reimplementar nada. En nuestro ejemplo imaginemos que tenemos mil URLs en vez de dos. Nos interesaría no descargar las mil de vez, sino ir descargando como mucho un número máximo de items a la vez. Pues bien con async es tan sencillo como usar mapLimit() en vez de map(). Simplemente tiene un argumento más en el que le decimos el máximo de items a procesar a la vez. ¡Voilá! Yo estoy encantado, de verdad que no he encontrado ninguna otra combinación de lenguaje o framework donde fuera tan sencillo hacer cosas en segundo plano de forma tan simple y flexible. Os invito a echar un vistazo a todas las funciones y ejemplos del módulo async.

¡Es más! Si tenemos un nivel medio de JavaScript en apenas 25 líneas de código podemos hacer nuestro micro-callback-handler (lo he llamado collector) sin necesidad de usar una librería externa como async. El código queda así:

var c  = collector()

descargarContenidoWeb('...', c.bind('a'))
descargarContenidoWeb('...', c.bind('b'))

c.collect(function(errors, results) {
    var c = results.a + results.b
})

Lo único que hace bind() es crear una función de callback que guardará el resultado con el nombre que le damos y finalmente hay un callback para recolectar todos los resultados. Super sencillo y de nuevo una forma simple y legible de hacer lo mismo y sin necesidad de modificar la implementación interna de la función.

Mis conclusiones

  • Las promesas son útiles en un lenguaje con APIs síncronas (ej: Java) porque ayudan a hacer cosas en paralelo, pero en un lenguaje con APIs no bloqueantes pierden utilidad porque ejecutar cosas en paralelo es el comportamiento por defecto. Con la ayuda de un callback handler además podemos hacer virguerías.
  • Un callback handler permite hacer todo lo que podemos hacer con las promesas y más. Y además sin tener que cambiar las implementaciones de nuestras funciones para usar promesas.
  • Las promesas no resuelven el callback-hell o pirámide de la muerte. La pirámide de la muerte se resuelve muy fácilmente: dividiendo el código en funciones.

Finalmente he dejado unos ejemplos comparando el uso de una librería de promesas versus la librería async. Se compara el uso de una función aislada y la ejecución en paralelo de dos funciones.

Y después de esta chapa… ¿Creéis que las promesas en JavaScript son una moda más? ¿Creéis que son útiles? ¿Merecen la pena? ¿Me estoy dejando algo?