To throw or not to throw o para que sirven las excepciones

To throw or not to throw o para que sirven las excepciones
Sin comentarios Facebook Twitter Flipboard E-mail

En el mundo impuro en el que vivimos se producen situaciones inesperadas que escapan a nuestro control. Es por ello que los programas necesitan un mecanismo mediante el cual poder controlar dichos casos excepcionales, al más común de esos mecanismos se le llama Exception handling. Sin embargo, el uso de excepciones tiene consecuencias en la calidad de nuestro código haciéndolo más frágil. ¿Podemos hacer algo al respecto?

Acción fantasmal a distancia

El principal problema con las excepciones es que realizan una acción fantasmal a distancia esto es, una situación excepcional ocurrida en cierto proceso inmerso en cierto contexto eleva esa situación sin concierto previo de ello a todos los procesos superiores en sus contextos superiores. Por ejemplo, tu tienes cierto código cuyo proceso consiste en sumar los importes de unas facturas y algún proceso profundo eleva una excepción cuando los datos de una factura están corruptos ¿que haces?, ¿dejas que la excepción se eleve e impides que los usuarios trabajen hasta que todas las facturas en liza se "arreglen"? no, seguramente con la idea de descartar las que "estén mal" intentarías capturar dicha excepción en el cuerpo que itera las facturas pero ésto, por desgracia, sólo agrava el problema que nos ocupa (aunque quizás tu aplicación funciona bien unos meses o incluso años si tienes suerte y no te piden modificárla jamás).

La cuestión es, que las excepciones suponen una interrelación tácita entre procesos en sus propios contextos, por lo que es difícil razonar y ser consciente de las implicaciones cuando ocurren.

El uso de excepciones hoy en día

Es completamente utópico pensar que vamos a poder establecer un marco que sustituya las excepciones fantasmales sin embargo, descontextualizaré torticeramente el artículo de Eric Lippert sobre Vexing exceptions para mostrar que el marco actual es notablemente mejorable. Él argumenta que no es posible actuar controladamente (sin usar excepciones) frente a, por ejemplo, el acceso a un fichero, dado que por muchas verificaciones que realices antes (existe, es de cierto tamaño, tienes permisos, ...) siempre puede ocurrir algo que rompa tu proceso de lectura. Es obvio que algo puede romper el proceso de lectura ¡pero no hay porqué andar lanzando excepciones a diestro y siniestro! la operación de lectura que eleva una excepción si ya no puede leer el fichero ¡está asumiendo que siempre podrá leer el fichero abierto! y eso, para nosotros, es mucho suponer (o no, y en tal caso no nos tendríamos que preocupar porque tal excepción se eleve).

Mi opinión es que cuando lanzamos una excepción, debemos asumir que el proceso entero que depende (directa o indirectamente) del que elevó la excepción se ha roto también y sólo es recuperable cuando no existe tal dependencia. Por ejemplo, el correcto funcionamiento del sistema operativo no depende del éxito o fracaso de la ejecución de mi programa (¿o sí?: isolation, containers, ...), luego si yo elevo una excepción él puede capturarla y decir, simplemente "abnormal program termination".

Otro ejemplo de uso "legítimo" podría ser la digestión de mensajes, nuestro worker debe seguir digiriendo mensajes aunque el proceso de uno de ellos falle estrepitosamente (ej. un bug que provoca NullPointerException) ¡pero sólo si el proceso entre mensajes es independiente!

Por supuesto, las excepciones son fantásticas para mantener de forma cómoda las aserciones en nuestro código y nos permiten validar importantes invariantes que, de no cumplirse, nos revelarán un bug.

Excepciones y recursos

Si el problema de la ruptura del proceso, no obtener el resultado esperado y que la acción fantasmal nos impide percibir adecuadamente las interrelaciones en nuestro código no te parecen pocos problemas, el hecho de lanzar una excepción obliga a todos los procesos intermedios a liberar todos los recursos que hubieran acaparado. Por fortuna en este caso, las interrelaciones suelen estar más claras, pues el contexto dentro de un } finally { normalmente estará bien definido.

La acumulación de ficheros temporales, un bloqueo sobre un fichero no liberado, la corrupción del estado persistente y otros similares son síntomas de que el programa falló sin una adecuada liberación de recursos. Este tipo de errores los vemos con frecuencia en el software que utilizamos.

Alternativa a throw

Strf

Hemos visto casos en los que las excepciones son un útil mecanismo. Huelga decir que el "uso creativo" de las excepciones como la famosa alternativa a la validación anidada estaría desaconsejada en general. Pero entonces ¿que alternativas tenemos para gestionar lo excepcional entre nuestros procesos interrelacionados?.

De las que yo conozco la que a mí me parece mejor resuelve el problema es la applicative style programming (o applicative functors) pues de una forma elegante permite mantener simultáneamente, cual programación orientada a aspectos se tratara, la ortogonalidad dualidad que nuestros procesos mantienen entre las acciones requeridas y las acciones excepcionales.

Pero eso mejor, otro día.

Comentarios cerrados
Inicio