Resulta descorazonador revisar tu propio código …

27 Nov

perl… escrito tiempo atrás y darse cuenta de todas sus carencias. Una deuda técnica que estos días estoy pagando a lo bestia.

Hace algunos años …

… , cuando no estaba más sólo que la una, mi compañero de trabajo y yo escribimos un servidor de trabajos de impresión para intentar resolver un problema cada vez mayor: los listados de la gestión comercial de la empresa.

Empezaban a ser una pesadilla de mantener porque el lenguaje en el que está escrito dicho programa de gestión (al que cariñosamente llamé Adriano) obliga a que los informes impresos se creen como si de una impresora matricial se tratase. Es decir, que en cada listado hay que detallar columna a columna y línea a línea todo lo que va a ir escrito, evitando que se desborde en las líneas.

Sí, tiene algún tipo de control de saltos de página, pero siempre terminábamos teniendo uno paralelo dada su poca flexibilidad, lo que significaba luchar contra los datos, el lenguaje de programación y los límites físicos de la pagina. Los efectos tipográficos consistían en enviar secuencias en lenguaje IBM Propinter o PCL directamente a la impresora y los desbordes de línea y página aparecían contínuamente. Jo, qué peleas, sobre todo cuando cambiábamos el modelo de la impresora y había que resucitar un arcano script en Perl que cambiaba pseudocódigos por secuencias de control reales.

Como el susodicho lenguaje de cuarta generación maneja textos con tanta sutileza como un panzer en un trigal, al final le teníamos rodeado de scripts auxiliares en Perl y siempre, pero siempre, siempre, encontrábamos alguna excepción que rompía el resultado. Un asco.

Con todo esto en mente (y teniendo en cuenta que no podíamos actualizar el sistema por si el montaje moría del todo) pensamos en que sería muy cómodo mover la generación de impresos a un programa Perl moderno, en otra máquina y limitarnos desde Adriano a enviarle datos en formato XML convenientemente auxiliados por una capa de funciones de manera que algo como

put stream standard
column 54,
"Sumas.....",
column 64,
(detalle.a_mes1 + detalle.b_mes1) using f1 clipped && " " &&
(detalle.a_mes2 + detalle.b_mes2) using f1 clipped && " " &&
diferencia(detalle.a_mes1 + detalle.b_mes1, detalle.a_mes2 +
detalle.b_mes2),
skip 2

quedase en algo más cuco y resultón

call impresor_abrir_registro( 'detail' )

call impresor_par( 'consignatario', f_lin.destinatario )
call impresor_par( 'portes', evalcond(f_lin.portes,'=','d',
msgtext(160), msgtext(150)))
call impresor_par( 'fecha', f_cab.fecha )
call impresor_par( 'agencia', agencia )
call impresor_par( 'bultos', num_bultos left )
call impresor_par( 'kilos', f_lin.kilos left )

call impresor_cerrar_registro()

Al menos ya no teníamos que preocuparnos de cortar, dar forma y escribir cada valor en cada columna; nos limitábamos  a generar un flujo de registros y a enviárselo al impresor.

Como la idea molaba nos pusimos a ello con toda nuestra buena intención y en un plazo no demasiado largo teníamos resultados que, si bien no eran tan aparentes y resultones -porque no pudimos emplear más que texto plano-, sí que relajaban mucho la tensión a la hora de crear nuevos listados o realizar cambios en la gestión comercial.

Decidimos utilizar la función form de Perl6 que teníamos disponible en el módulo Perl6::Forms y la cosa funcionaba bien porque podíamos escribir el listado en un archivo de texto y luego tratarlo con Perl. Perdíamos la posiblidad de emplear negritas, itálicas y demás, y parte de la lógica del listado -como los acumulados y los contadores- debía trasladarse al servidor de impresión pero merecía la pena.

Esta es una muestra de cómo se define un registro de detalle ahora:

<detail>
template = <<EOF
* {<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<} Bultos {>>>>>}
{VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV} Kilos {>>.>>>,<0}
{VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV} Portes {>>>>><<<<<}
{VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV}
{VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV}
Observaciones:
{<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<}
{VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV}
-----------------------------------------------------------------------------
EOF

fields = direccion, bultos, kilos, portes, observaciones
accum = bultos, kilos

<form>
bullet = "* "
</form>
</detail>

Hoy, años después de haber creado aquél programa, y ante la tarea de añadirle algunos listados y retocar otros me he dado cuenta de que no lo hicimos bien. El concepto sí, la implementación no.

¿ Qué fallos le encuentro ?

  • No tiene unos valores predefinidos sanos. Está demasiado anclado a una instalación concreta y en algunos aspectos cogido con pinzas. Normalizar el paquete Debian (porque el que tenía no funcionaba) ha consistido en deshechar todo lo anterior y comenzar de cero con el directorio debian/. Digo yo que no debería haber sido tan trágico, ¿ no ? 
  • Los mensajes de error que genera son para programadores Perl machotes. El operador medio sufre espamos musculares cuando intenta leerme por teléfono una traza de ejecución resultante de un error. Incluso a mí me cuesta entender cuál es el jodido problema final.
  • La documentación del módulo está incompleta. Ahora recuerdo que las malditas prisas nos obligaron a postergar tanto su completado que leerla es una auténtica prueba de fé. Fé en que las clases de las que derivan estén documentadas o sepan al menos lo que hacen. Y así hasta llegar a extremos ridículos.
  • El código tiene partes tan enrevesadas que resulta muy difícil de seguir debido a que quiere cubrir demasiados objetivos. El comienzo es bueno, pero según se le añaden características (como la copia del resultado vía SSH o SMB) se nota que entraron a hachazos en el diseño general. Mal, muy mal.
  • Los test. Prácticamente no existen. ni unitarios ni regresivos ni nada que no sea la verificación sintáctica. Las malditas prisas (de nuevo) y los resultados rápidamente visibles  provocaron que los olvidásemos y ahora es necesario realizar cirugía con trozos de código canibalizados para poner en marcha algún tipo de prueba. He tardado día y medio en darme cuenta de dónde podía cambiar una validación de valores en los campos sin destruirlo todo. Y casi no lo consigo.

¿ Qué aprendo de ello ?

Ahora que trabajo sólo me enfrento siempre a los jefes con mis ideas conservadoras sobre la creación de código por los retrasos que eso supone. Sigo pensando que es un tiempo bien empleado pero …

El resultado que persigo es disponer de un paquete de software que pueda instalar (suelo emplear paquetes Debian) y que tras recuperar la configuración y los datos pueda ponerlo en marcha sin más ajustes ni traumas. Pretendo que cualquier programa que se emplee más de una vez en la empresa se instale del mismo modo que un editor de textos o un navegador. Y por ahora lo estoy consiguiendo a pesar de la presión del día a día.

Lo que hago ahora es lo siguiente:

  • Empleo siempre un repositorio Git para todo proyecto.
  • Antes de escribir código escribo tests para probarlo:
    • Al principio de los principios todo casca, obviamente, pero según voy añadiéndolo verifico los resultados hasta que se cumplen todos.
    • Comienzo los test por los de más alto nivel para forzarme así a perfilar el interfaz y comprobar si es cómodo o no.
    • Los alimento con basura para saber qué puede llegar a dar por válido y no llevarme sorpresas luego.
  • Guardo los cambios en el repositorio lo más atómicos posible. No llegaré nunca al extremo que tengo entendido emplean en el núcleo de Linux, donde pueden deshechar algunos e incluir otros, pero al menos intento tener la capacidad de eliminar características completas de un plumazo.
  • Documento todo. Añado un test de cobertura de código para los módulos Perl y no lo doy por válido hasta que no termino por cubrir cada uno de los métodos y funciones públicas.
  • Los valores de la configuración son siempre los mínimos para que arranque sin problemas o para detectar cuándo no puede arrancar y avisar de ello. Prefiero ésto a enfrentarme con programas erráticos cuando no encuentran las cosas donde creen que están.
  • Creo un paquete Debian para el software intentando emplear todas las herramientas que existen de manera que no tenga que instalar a mano la documentación o dar de alta un servicio en el sistema cuando éste pueda hacerlo por sí mismo. El registro de cambios del paquete refleja a mayor nivel lo que todo el software empaquetado significa. Para cambios menores ya existe el del repositorio.
  • Aunque al principio intenté crear páginas web para cada proyecto abandoné pronto la idea por la duplicación de esfuerzos. Un programa como gitweb (o similar) y una buena documentación escrita bastan para realizar consultas. Además, como todos los módulos y programas Perl incluyen documentación en formato POD, y ésta se instala como páginas de manual en el paquete Debian, la parte de desarrollo queda cubierta. Me falta, eso sí, mejorar esta información empleando el formato HTML y programas como dwww para poder revisar el código. Estoy preparando un artículo sobre ello.

Y poco más que decir al respecto por ahora. Tengo pensando cambiar la implementación del servidor de impresión por un Perl más moderno y legible; a pesar de que utilizando herramientas como Moose los programas crecen bastante en recursos y tiempo de ejecución, el ahorro de esfuerzo en el mantenimiento merece la pena.