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.