El efecto mariposa o sobre la gestión de un error en PyQGIS y QGIS

6 minuto de lectura

Como se suele decir “hasta de los errores se aprende”. Esta entrada me sirve para recopilar, organizar y exponer un conjunto de lecciones aprendidas durante el proceso de corrección de un error. Un error informático, en concreto de programación en un complemento de QGIS usando PyQGIS, la API para Python de este potente GIS.

La solución final no fue compleja, solo hubo que introducir un paso intermedio en un modelo de procesamiento de QGIS. Pero tirando de nuevo de tópico, aquí “lo importante fue el camino y no el destino (la solución)”. En este artículo se recopilan algunas de estas experiencias aprendidas durante ese y otros ‘caminos’.

Fuente: Wikipedia

Documentación de código y naming

Es clave poder contar con los documentos que se hayan generado antes y después de la programación: casos de usos, diagramas ER, modelos, diagramas de flujo, control de cambios… Si no existen, por la razón que sea, por ejemplo por ser un desarrollo heredado, hay que ir generándolo a medida que vamos conociendo el código.

Que el código esté bien documentado es fundamental. Dedicarle tiempo y mimo añadir los docstring de las clases y funciones nos va a ser muy útil cuando tengamos que consultarlo después de algún tiempo. Aunque si nos ponemos puristas, un buen código se describe a sí mismo, dependiendo de nuestra experiencia es interesar ir dejando comentarios concretos que ayuden a comprender mejor la aplicación.

Otro gran recurso será en la elección correcta de los nombres de variables y funciones. Hay toneladas de literatura sobre esto, véase por ejemplo el libro The Clean Coder de Robert C. Martin, pero pongo un sencillo ejemplo. Si tengo una variable que representa a una capa puntual de acometidas y la llamo layer, no pasará nada, siempre que trabaje solo con una capa. Pero ¿qué corre si tengo 2 o n capas?,¿las llamaremos layer_2, layer_3 o layer_n?¿cómo sabré a qué corresponde layer_20?

Una (mala) solución sería tirar de comentarios dentro del texto explicando que layer_20 corresponde con una capa de transformadores y así sucesivamente. Pero si al declarar la variable directamente usamos el nombre layer_transformers tendremos este tema resuelto.

La máxima debe ser usar nombres intencionados y descriptivos, evitar abreviaciones, prefijos o el uso de secuencias de números en variables. Además de esto es importante no perder de vista las convicciones sobre los nombre definidas en la PEP8 de Python

Entornos y versiones

Tener control sobre las versiones de los paquetes, librerías y programas que vamos a usar para nuestro desarrollo es fundamental.

Cuando trabajamos exclusivamente con librerías, nos protegemos de estos cambios creando entornos virtuales donde definimos las versiones a usar. Pero al usar un programa como QGIS, esta rutina no nos asegura que todo vaya a funcionar correctamente. Este SIG de código abierto cuenta con muchas más funcionalidades que no dependen exclusivamente de su API. Un ejemplo claro es la incorporación de geoprocesos de SAGA o GRASS.

Ahí va otro ejemplo, que puede traernos algún que otro problema. Partimos de un complemento de QGIS que integra la ejecución de modelos de procesamiento. El usuario final trabaja con una versión LTR de QGIS (3.10) dentro de un Amazon WorkSpace. Es lo que se llama su entorno de producción. Como versión de pruebas, tenemos otro workSpace pero en esta ocasión con QGIS 3.16. Y para rematar, nuestro equipo de trabajo en local tiene la última versión LTR de QGIS 3.22.

Esta multitud de versiones nos puede dar verdaderos dolores de cabeza ya que para cada nuevo lanzamiento, siempre se incorporan mejoras y correcciones que pueden afectar a nuestro código. Aunque toda la documentación sobre los cambios es recopilada en la página del proyecto QGIS tendemos a quedarnos en los apartados de novedades. Pero como desarrolladores es igual de relevante ir al final del registro de cambios y estar atentos al último apartado de Notable Fixes.

Esto produce que un cambio, entendido como cambio el uso de alguna clase de PyQGIS o incluso de algún geoproceso, no funcione correctamente en versiones estables más antiguas. Debemos siempre programar en la misma versión que va a usar el usuario final.

Si en algún momento se produce la necesidad de cambiar de versión, por ejemplo para implementar una nueva nueva funcionalidad, debemos asegurarnos que todo nuestro desarrollo previo se va poder ejecutar en la versión a migrar y que el costo (tiempo, recurso, dinero…) de este cambio nos es beneficioso.

Implantar un sistema de logs y avisos

Cuando estamos trabajando en nuestro propio equipo y se produce algún tipo de error siempre tenemos a mano la información que nos ofrece el terminal donde estamos ejecutando el código, el editor (Vscode) o framework (PyCharm) que manejemos o el mismo QGIS mediante los paneles de mensajes.

Pero ¿cómo saber qué ha pasado el equipo del cliente-usuario donde se ha producido el error? Si no tenemos implementadas herramientas internas (logs) y externas (ventanas de información) tendremos que dedicar mucho más tiempo del deseado en que el cliente nos explique no solo lo que ha pasado, sino también qué es lo que ha hecho (partiendo del hecho de que seguramente él no ha tocado nada).

QGIS nos ofrece clases para poder desarrollar ventanas emergentes que comuniquen al usuario cómo va evolucionando un determinado procesos, si se ha concluído con éxito o si algo ha fallado.

Es fundamental también el registro de estos eventos usando archivos de registros o logs. En este historial queda constancia temporal de los procesos realizados inclusos de su tipo o categoría (info, warnings, errors…) Su utilidad está más que clara. En esta entrada pasada hay algo más sobre este tema.

Ojo a la personalización de errores

Añado este apartado porque un mal uso en la creación de errores de salida puede causarnos mucho daño.

Lo habitual en Python es gestionar las excepciones mediantes sentencias try…exceptque nos devuelven los errores producidos. Pero debemos tener cuidado si añadimos mensajes personalizados porque pueden llevarnos a equívocos. Es mucho mejor recoger los errores que nos devuelve al sistema, tanto sin son los propios de Python (ValueError, RuntimeError, TypeError…) como los definidos para cada librería de terceros que usemos.

Último ejemplo. Dentro de complemento de QGIS, había definido una sentencia try/except en la que se ejecutaba un modelo de procesamiento de QGIS. Además de los mensajes de error que nos devuelve PyQGIS, se había añadido un aviso personalizado que indicaba que el modelo no había sido localizado. Este mensaje estaba pensado para saber que el perfil del usuario de QGIS que estaba ejecutando el programa no era el definido por defecto y que por lo tanto, no localizaba la carpeta donde se almacenaban los modelos del QGIS.

Aunque inicialmente el mensaje tenía su utilidad, la información que ofrecía centró la búsqueda del fallo en una dirección contraria a lo que de verdad estaba ocurriendo. El modelo fallaba porque al añadir un campo nuevo (status) en una capa que formaba parte del proceso de unión de varias capas, el tipo del nuevo campo no era el correcto (boolean) con el campo status de otra capa (tipo string), y por lo tanto el modelo se detenía.

Lo que se se soluciono simplemente añadiendo un paso más el flujo del modelo, tuvo un efecto mariposa que consumió mucho más tiempo buscando una posible solución en una línea incorrecta por un ‘bienintencionado’ pero incorrecto mensaje de error.

Comentar