Monday Rant: de los programadores

No me digas que el programa está haciendo algo raro. Es tu programa. No se ha escrito solo. Únicamente hace lo que le has pedido que haga. Que no siempre es lo que pretendes que haga. Tampoco me digas que no sabes lo que está pasando en un momento dado. Es tu obligación saber depurar tu propio código. De hecho, deberías ejecutarlo con el depurador al menos una vez. Es tu código. No es dificil de leer. Si sabes programar. Continue reading

Un usuario. En la biblioteca. Con un tablet.

En Internet hay miles de artículos sobre optimización de código y mejoras de escalabilidad para que tu super servicio soporte más de 10.000 peticiones por segundo. Y miles de programadores, en este momento, están leyendo esos artículos. Solía decir que de esos miles de ávidos lectores, en un momento dado, sólo dos realmente necesitan la información. Es decir, está bien optimizar pero cuando sabes que lo vas a necesitar, no por practicar otro deporte de riesgo. Sigo pensando así. Mi webapp, mi site, mi servicio de alquiler de mascotas por horas, no va a necesitar grandes alharacas en el terreno de la escalabilidad.

Hasta que descubres un nuevo problema de escalabilidad: el Señor Verde. En la biblioteca. Con un tablet. Traduciendo para los que no conocen el Cluedo: un único usuario con una tableta. Y su aplicación de CRM. O de cualquier otro sistema que tenga que machacar datos. Muchos datos. Un único usuario, no 10.000.

Independientemente de si la estructura de datos es de nueva planta o es una adaptación de un megadatawarehouse mantenido por tres ingenieros nucleares y dos consultores de la NASA lo que está claro es que, en algún momento, esa estructura va a dar problemas. De complejidad y, por lo tanto, de velocidad. La velocidad. El talón de Aquiles de cualquier tableta. Y el talón, el femur, la rabadilla y el brazo izquierdo de Aquiles de cualquier webapp en una tableta.

SQLite puede ejecutar los mismos queries que tu app contra un servidor Oracle. En una aplicación de escritorio la diferencia de velocidad no llega a afectar a la experiencia del usuario. Es en una tableta donde esta diferencia si se nota. No en el uso normal, buscando, mostrando resultados (donde si se mira el profiler con atención se ve que aún hay mucho por optimizar en el dibujado antes de meterse con los datos), sino en algunos momentos delicados. Por ejemplo, preparando una vista que muestra información de distintas subentidades. O en el, cada vez más requerido, dashboard o panel de control. Un sitio donde se tiende a obtener KPIs agrupando datos dispares. Ejecutando queries complejos. A veces muy complejos. Que puede que no tarden más de 1500ms en ejecutarse pero… uno detrás de otro + ejecutados en un entorno síncrono + uniendo el resultado de subqueries para obtener una cifra x cada vez que se redibuja el dashboard = lentitud percibida por el usuario.

Uno de los remedios típicos en web ha sido desnormalizar los datos, tunearlos para que los resultados sean lo más rápido posibles, a costa de aumentar el espacio que ocupan los datos. En un entorno donde el espacio es “barato” y la velocidad “cara” es normal que, por ejemplo, las relaciones de un usuario de un site social no se calculen a la hora de mostrarlas, sino que estén precalculadas y que lo que se muestra es el resultado de dicho cálculo.

¿Cómo podemos aprovecharnos de este truco en una aplicación (mixta o nativa) que usa SQLite o algún otro motor de datos en una tableta? Exactamente de la misma manera. Siguiendo con el ejemplo del CRM y del dashboard que muestra, por ejemplo, la desviación a la hora de conseguir un objetivo el sistema tradicional nos dicta que hemos de ejecutar uno o varios queries hasta obtener el número de elementos que se desvían del objetivo previsto en tal o cual grado. Siguiendo con ese sistema tradicional se ejecutan dichos queries contra todas las tablas implicadas en el cálculo de los KPIs. Para ponerlo claro: quiero el total de todos los pedidos que contengan líneas de productos que están en una tabla de promociones que se basan en la fecha y la situación geográfica del comprador que no lleguen a un mínimo de unidades y lo quiero agrupado por medio: presencial, teléfono o fax. Este sencillo query se ejecuta cada vez que se actualiza el dashboard como resultado de un cambio en los datos.

La opción no-tradicional puede ser tener una o más tablas específicas para el cálculo del resultado final. A la hora de crear o modificar un registro que afecta a la desviación, se calcula el valor para cada línea de pedido y, con el cálculo de estas, para el pedido. Se asigna, por ejemplo, un valor tipo y un valor si/no. Cuando se actualiza el dashboard ya no se ejecutan los queries para calcular el total de pedidos, sino que el query es sencillo y rápido. El “peso” del cálculo se ha llevado a un punto de la ejecución del programa, la grabación, donde el usuario puede esperar medio segundo más. Sólo se calculan las filas necesarias en la estructura de datos, no se ejecuta un query que busca determinadas líneas de los pedidos y luego cruza con los tipos de los pedidos.

Es un breve ejemplo de cómo luchar contra el problema definitivo de optimización y escalabilidad: Un usuario. En la biblioteca. Con un tablet.

Back to the basics

My latest weekend project (weep for short) has two objectives: first and more important, help me forget the hideous code I’ve been forced to clean for the last two weeks (and counting). Secondly, as any weep worth of its name, force me to learn something new. And what am I learning, would you ask? Apart from a new tool, I’m learning to un-jQuery myself.

In more simply worlds, I’m coding JavaScript without any lib. Do you remember the reasons for the existence of jQuery? Do you remember the browser wars of the nineties? Do you even remember the nineties? That was the time when, if you were doing some web development, you needed to write everything twice. One for each DOM. So, when jQuery and other libs, such as Prototype, arised we were released from the chains of rewriting and retesting for that SOB browser everyone has grown to hate. Continue reading

Refactoring: utiliza los métodos adecuados

El método que tenemos hoy en la mesa de autopsias pretende asegurar que siempre haya una opción seleccionada en un grupo de checkboxes, en caso de que el usuario decida deseleccionar todas. La razón de forzar que haya siempre una opción marcada es evitar que al código que usará la selección del usuario no le de un pasmo porque no comprueba (aunque debería) dicha selección.

 

Check_Selection: function () {
    var checks = $(“#group1 input");
    var checked = [];
    var nonechecked = true;
    
    for (var i = 0; i < checks.length; i++)
    {
        if (checks.eq(i).attr("checked"))
        {
            checked.push(checks.eq(i));
            nonechecked = false;
        }
    }
    if (nonechecked)
    {
        checks.eq(0).attr("checked", true).checkboxradio("refresh");
    }
}

Primero, como maniático que se es, un par de cambios “estéticos”. El método, efectívamente, comprueba si hay una selección, pero por aquello de que un método haga sólo una cosa y su nombre describa lo que hace, es más apropiado llamarlo forceOneSelection.

“Ajunto” las variables al inicio del método y cambio los nombres según la norma de estilo de la casa (que se ha escrito, como todos sabemos, para ser ignorada). Revisando las variables vemos que se crea un array, checked, en el que se añaden elementos. Pero luego no se usa. Un despiste lo tiene cualquiera, así que variable fuera.

El WTF comienza más adelante. Se comprueba si un checkbox está seleccionado usando el atributo “checked”. Usar attr() para estas cosas en jQuery es la manera de NO hacerlo desde la versión 1.6. Y este código usa la versión 1.7. La forma correcta sería usar prop(). Pero hay una forma aún más correcta. Preguntar por el pseudo-selector :checked.

Ya que estamos rodeados de jQuery, cambio el for por un $.each. Eso ya es manía de cada uno. El método quedaría así.

 

forceOneSelection: function () {
    var $Checks = $("#group1 input"),
        boolNoneChecked = true;
    
    $Checks.each(function (i, objCheck) {
        if ($(objCheck).is(':checked')) {
            boolNoneChecked = false;
            return false;
        }
    });
    if (boolNoneChecked) {
        $Checks.eq(0).prop('checked', true).checkboxradio('refresh');
    }
}

 

Por último, una vez el código funciona (y nunca antes), se optimiza. En lugar de pedir todos los inputs y preguntar uno a uno si están seleccionados, le pedimos amablemente a jQuery que nos devuelva sólo aquellos que están seleccionados.

 

forceOneSelection: function () {
    var $Checks = $("#group1 input:checked");

    if (!$Checks.length) {
        $Checks.eq(0).prop('checked', true).checkboxradio('refresh');
    }
}

try/catch me… if you can

Todo lenguaje que se precie tiene un mecanismo para capturar excepciones. Ya sabes, esos errores que hacen nuestra vida más emocionante en los momentos más inoportunos. Y todo programador digno de tal nombre sabe usar la gestión de excepciones a su favor. Algunos de maneras… imaginativas. Para valores muy extremos de “imaginativas”.

La gestión (adecuada) de excepciones hace nuestra vida mejor, sin ninguna duda. Sin embargo, lo que me encontré hace tiempo en un proyecto desarrollado en JavaScript por un programador de C# con más entusiasmo que conocimientos ocasionó más de un dolor de cabeza a la joven programadora encargada de su mantenimiento.

¿Los síntomas del problema? Aparte de los mencionados dolores de cabeza, el programa se colgaba. Sin dar errores. Ni aviso de quince días. Ni tan siquiera un carraspeo avergonzado. Vale, estoy exagerando. El programa mostraba este mensaje.

before

El método IsBlocked(), donde se muestra esta alerta, es el típico método promiscuo que se lanza a una orgía de llamadas asíncronas a otros métodos que llaman a otros métodos que hacen otras llamadas asíncronas hasta que, de repente, nos salta un alert en pantalla.

Uno de esos métodos ha dado un error, que ha sido interceptado por el catch y “escalado” hacia arriba, catch tras catch hasta llegar a la línea 1572 del archivo donde muestra el mensaje. Lo que sigue a continuación suele ser una divertida sesión de depuración aderezada con recordatorios a la madre del programador original.

Una correcta gestión de excepciones no sólo es deseable, sino que ayuda a mantener la salud mental. El problema surge cuando la gestión de excepciones consiste en una serie de métodos que envían el error hacia arriba esperando que alguien lo recoja. Si nadie lo hace o si, como en este caso, la respuesta es que ha habido un error en un método que no es que realmente falla nos espera un mundo de dolor e imprecaciones.

Pero ¿por qué salta una excepción y qué significa? En un mundo ideal los programas nunca tienen errores, los servidores responden a las peticiones con los datos que se esperan y los arrays están correctamente dimensionados, entre otras muchas partes móviles que pueden griparse en un momento dado.

Una excepción es, literalmente, que ha pasado algo que el programa no sabe como manejar. La gestión de excepciones a base de try/catch(“intenta hacer esto”/“pilla el error”) captura esas situaciones “excepcionales” y le dice al programa que algo ha pasado (ese parámetro que se pasa a la parte catch()). Si el “pilla el error” correspondiente al “intento de hacer algo” no hace nada, la excepción se pasa al “pillador” de la función o método desde el que se llamo al código que ha fallado. Y así sucesivamente hasta que se hace algo con esa excepción.

Si el sistema está bien organizado es una gran ayuda. Si, como pasa muchas veces, no hay nadie para “pillar” las excepciones, el resultado es un error que ha sido enmascarado. O directamente ignorado. El programa no hace lo que debería y nadie sabe la causa, porque no se reciben errores en el depurador.

Porque esa es otra, el depurador (en el navegador, en este caso) no va a devolver una excepción que ya está siendo, en teoría, gestionada por el código. En lugar de mostrarte un mensaje de ha pasado esto en tal línea se calla, porque alguien le ha dicho que ya se encarga de esas cosas.

En el ejemplo que nos ocupa, se ejecutan siete métodos con su correspondiente catch (e) { }. Es decir, que no hacen nada más que mandar el error hacia arriba. Hasta que un método, lo más lejos posible del error original, dice que ha tenido un error. Pero no es el método que se queja quien ha fallado, sino otro siete niveles más abajo. Para que luego digan que programar es aburrido.

Las soluciones a estos despropósitos son múltiples. Una, implementar correctamente la gestión de excepciones. La segunda, colarse por en medio y, si la plataforma lo permite, añadir un sistema de excepciones “paralelo”. Cuando se está trabajando con código perpetrado más que programado es conveniente usar el sistema “paralelo”.

Implementar este sistema es trivial en JavaScript. Basta con añadir un handler que se ejecuta cada vez que sucede un error. Por defecto, el depurador sólo menciona los errores que no son capturados por la gestión de excepciones, pero, con este handler estamos forzando a responder a todos los errores, gestionados o no.

    window.onerror = function (strMessage, strURL, strLine) {
        alert(strMessage + '<br/>' + strURL + ' - ' + strLine);
        return true;
    };

El handler va a recibir el texto del mensaje, y el archivo y línea donde se ha generado el error. Mostrar un alert, o escribir el error a la consola, o hacer cualquier cosa con él ya es decisión del programador.

after

Volviendo al ejemplo, el error que hace saltar una excepción en la línea 1572 del archivo se produce, realmente, en la línea 2437 del mismo, varias llamadas asíncromas más tarde. Este mensaje si es realmente informativo y nos ahorra perseguir todas las llamadas hasta que una de ellas falle. La otra opción, por supuesto, es buscar en todo el código cada catch y “rellenarlo” con un mensaje o una alerta. Pero si hay que mantener o arreglar código perpetrado es más rápido tirar por la calle de en medio.