Impossible Programming

Impossible Programming
Facebook Twitter Flipboard E-mail

El término Impossible Programming no existe (aunque la vivamos a diario), pero es el nombre que se me ha ocurrido para presentar un sencillo ejercicio sobre Programación dirigida por eventos.

Hay algunos problemas, que si bien en si mismos no son complicados, la gran cantidad de elementos involucrados hacen que resolverlos con éxito, sea una empresa arriesgada. Por ejemplo, todos los aquí presentes sabemos sumar pero… ¿estaríamos seguros del resultado tras sumar dos números con 230 dígitos?.

Al menos con la suma sabemos como tenemos que proceder pero, ¿y si además de haber muchos elementos involucrados, no estamos seguros de tener una estrategia óptima?, ¿y si resulta que tras deshacer el nudo gordiano y dedicar mucho tiempo a poner orden (por ejemplo con cuidadosas pruebas unitarias y TDD) la estrategia no es correcta?, ¿y si nos cambian los requisitos, la información es errónea o las condiciones varían con el tiempo?, ¿y si (muy a nuestro pesar) efectivamente el problema supera nuestros conocimientos?, …

Bueno, bueno, una vez encomendados a San Isidoro de Sevilla (patrono oficial de los programadores), ¿hay algo más que podamos hacer?. Claro, existen un buen número de herramientas útiles que los programadores podemos usar cuando (seamos sinceros) no tenemos ni idea de por donde tirar. Hoy, te propongo jugar con la Programación dirigida por eventos, ¿estás preparado?.


Brevísima introducción a la Programación dirigida por eventos

Cuando escribimos un programa, habitualmente esperamos unos datos de entrada pactados de antemano (eg. una lista de nombres, los datos de registro de la centralita telefónica, etc…) y por eso podemos escribir un procedimiento que procese los datos de forma prefijada. Insisto, establecemos un procedimiento concreto de antemano, que describe como deben procesarse esos datos.

Sin embargo, si los datos nos llegan en cualquier orden y forma (ahora un nombre, luego un teléfono, después algo que hay que procesar, etc…), fijar un único procedimiento concreto será, normalmente, más complicado que si nos centramos en procesar individualmente cada bloque de datos por separado. El siguiente ejemplo puede aclarar la idea, en lugar de tener todo mezclado:

Puede quedar más claro si separamos las diferentes formas en las que nos llegan los datos:

En general, la programación dirigida por eventos está orientada a situaciones en las que la naturaleza del problema es, precisamente, un conjunto de eventos pero, podemos aprovechar la forma de organizar los eventos para simplificar un problema, veamos como.

El problema: la situación

Hoy tenemos un problema “empresarial” (no “matemático”), por eso deberás hacer un pequeño esfuerzo para no dormirte entre requisito y requisito.

Supongamos que una empresa dispone de diversas factorías para fabricar productos de repostería (toman las materias primas y producen ya embalados diversos tipos de dulces). Como son factorías muy viejas y que llevan funcionando mucho tiempo, se han convertido en auténticas máquinas de Rube Goldberg, como la de la imagen que encabeza el post.

La cuestión es que si bien su mecánico es bastante bueno y sabe hacer funcionar todos los sistemas, no hay nadie capaz de entender cómo y para qué sirven todos los mecanismos que componen las factorías, ni siquiera el mecánico tiene una idea general de cómo funciona todo, él simplemente va arreglando las cosas que dejan de funcionar.

Cada día se producen averías con mayor frecuencia y el Caos empieza a ser lo habitual en el funcionamiento de las factorías. Además, debido a que no es evidente el flujo de procesos, una máquina puede dejar de funcionar debido a que a mucha distancia, otra máquina ha dejado de tener materia prima y a la empresa le resulta muy difícil proveer un suministro constante de materia prima para maximizar la producción. De pronto se quedan sin harina, y deben salir corriendo a buscar más para que todo vuelva a funcionar.

En definitiva, he descrito el tipo de empresa en el que todos querríamos trabajar.

Así pues, en la empresa piensan que como los ordenadores ordenan cosas, a ellos igual les viene bien tener uno para que ponga orden en sus factorías (si este argumento te parece descabellado, debes saber que es “El argumento” del que es imposible sacar a los clientes; piensan que no son ellos los que deben poner orden ¡para eso están los ordenadores y los informáticos!) y te llaman a ti, que de fabricar pasteles sabes lo mismo que un caracol de tocar el Ukelele.

El problema: lo que hay que hacer

Obviamente todos sabemos que la mejor solución es construir nuevas y flamantes factorías que dispongan de la última tecnología pero claro, la empresa dice que “ya lo habíamos pensado pero como nuestro mecánico es muy bueno (que lo arregla todo), a ver si se podía poner orden en los recursos que debemos tener disponibles en cada momento”.

Así, los requisitos serían más o menos los siguientes:

  • Se pide diseñar un sistema que indique la cantidad de recursos necesarios para maximizar la producción de pasteles. Obviamente debe poder decirlo con la antelación suficiente (días o semanas).

  • El sistema debe servir para diferentes factorías, cada cual con sus particularidades.

  • El sistema debe ser capaz de adaptarse a situaciones inesperadas como caída, eliminación, adición y/o cambios en el comportamiento de los subsistemas de cada factoría.

Soluciones “formales”

Todo el mundo sabe que en un escenario como este las redes de Petri son una herramienta que nos permite analizar, comprender y optimizar el flujo de procesos, es probable que exista algún software que nos permita modelar el Caos de estas factorías pero, ¿y si no sabemos qué son esas redes?, ¿y si existen procesos estocásticos que dificultan el modelo?, ¿y si no sabemos usar ese software? (deberíamos aprender).

Todo el mundo sabe también, que la complejidad de los procesos puede ser rodeada usando la estadística (para obtener datos) y el cálculo de probabilidades (para predecir comportamientos). Podríamos realizar un cuidadoso estudio estadístico de, únicamente, el flujo de materias prima de entrada y la producción de salida, ignorando toda la complejidad interna de las factorías. Obtendríamos así (probablemente) un modelo ajustado sobre la probabilidad de que nos quedemos sin materia prima que nos permitiría minimizar la probabilidad de parada. Sin embargo, aplicar esta solución requiere tomar muchos datos y, además, con el tiempo habría que ir ajustando el modelo.

Solución “a la torera”

Como Joselito con su capote, vamos nosotros capear el problema, sino con inteligencia, esperemos que con gracia. Porque en realidad lo que vamos a hacer es una mezcla “informal” de las dos anteriores. Pues en lugar de modelar la red de Petri, únicamente vamos a simular nuestras factorías, y en lugar de minimizar la probabilidad de parada, sólo vamos a obtener los datos del funcionamiento de las partes (estadística).

Es decir, vamos a construir un simulador de factorías para poder predecir que recursos se van a agotar primero y en tan sólo 99 líneas de código. ¿Empezamos?.

Lo que vamos a hacer

La solución que proponemos a la empresa es, ya que no va a cambiar las factorías y que el funcionamiento va a seguir cambiando de un día para otro, la siguiente:

  • Nuestro sistema se podrá reconfigurar instantáneamente, bastará con cambiar el archivo de entrada. Así, si un nuevo subsistema cambia su comportamiento, es añadido o eliminado, ajustamos el archivo de configuración y listo.

  • Nuestro sistema podrá funcionar con independencia entre las diferentes factorías, un cambio en unas, no afectan en nada a las otras.

  • Nuestro sistema responderá instantáneamente a las entradas de material, así, podremos pedir un report que instantáneamente nos indique el estado que tendrán todos los subsistemas (por ejemplo, para poder ver que la máquina de amasado se bloqueará porque necesitará más harina).

El generador de simuladores

La primera estrategia consiste en abstraer el problema. En lugar de intentar modelar directamente las factorías, vamos a crear un generador de simuladores. Con ello conseguimos por un lado no tener que pensar en el tremendo lío que hay en cada factoría y por otro, que la configuración y reconfiguración pueda hacerse muy fácilmente (desde el archivo de configuración).

El simulador

La segunda estrategia consiste en identificar las partes de la factoría que sean posibles, si una parte es completamente incomprensible no pasa nada, la trataremos como un todo, pero el identificar las más partes posibles nos permitirá actuar fácilmente allí donde se produzca el problema.

La forma más fácil de ver como funciona el simulador es ver su archivo de configuración:

Vemos que hemos identificado las diferentes materias primas con el número de elementos que nos llegan en cada envío (eg. 30 manzanas por envío) y los procesos internos (eg. para hornear hacen falta cinco montajes ya listos y una unidad de energía). Si mañana se añade un proceso, sólo es añadir una línea, si desaparece, eliminarla, si la máquina de recortes empieza a fallar y desperdicia una de cada seis manzanas sólo es ajustar el número de manzanas necesarias (eg. en lugar de 15 poner 19), etc…

El control

Para controlar el simulador, además del archivo de configuración, querremos disponer de algunos comandos como levantar todo el sistema, detener el sistema, simular la caída de un subsistema, añadir recursos de algún tipo, de otro tipo, pedir un estatus a todos los sistemas, etc…

La implementación

Para implementar nuestro sistema vamos a utilizar Inter-process communication que aunque suena muy técnico, es tan sencillo como que unos procesos se envían “correos electrónicos” (es un símil) unos a otros. Como en mi ejemplo los procesos son sencillos scripts (en bash) utilizaré un sencillo comando msgtool que puedes encontrar en las referencias.

Bien, el código que nos permite generar los simuladores a partir del archivo de configuración es:

Que no hace otra cosa, más que generar automáticamente una serie de scripts que mandan y reciben mensajes simulando las relaciones existentes en las factorías. Después veremos algún script autogenerado por este código en Perl.

El despliegue

Para poner en marcha los simuladores, crearemos una carpeta para cada factoría con su archivo de configuración, por comodidad podemos poner nuestro script generador de simuladores y la utilidad msgtool. El proceso de generación es trivial, pongo un ejemplo completo con el listado de archivos (que es lo que ocupa espacio):

Para levantar todos los subsistemas de nuestro simulador, basta hacer:

Obviamente si solicitamos a los subsistemas que emitan su estado, estarán todos a cero:

Por comodidad, podemos lanzar el siguiente comando en otro terminal para tener actualizado en tiempo real el estado del simulador:

El simulador también nos ha generado automáticamente scripts para cada uno de los recursos que tenemos disponibles, así, podríamos lanzar por ejemplo la siguiente carga de recursos (eg. según llegan a los almacenes):

Vemos que con las manzanas y operarios que han entrado se han producido seis recortes y hay disponibles las guindas, pero no puede continuar el proceso, además, operarios tenemos tres parados y no hay más manzanas.

Si continuamos añadiendo recursos, vemos la evolución:

Así, podemos predecir fácilmente las necesidades de recursos que vamos a tener a corto plazo y pedirlas antes de que se agoten, sin necesidad de tener almacenadas grandes cantidades.

Los scripts autogenerados

Cuando hemos generado el simulador a partir del archivo de configuración, se nos ha listado los identificadores asociados a cada variable (manzana, horneado, …) algunos no se usarán para nada (eg. manzana) pero otros serán los identificadores de las colas de mensajes.

El script que levanta todo el sistema contiene lo siguiente:

Que como ves, únicamente pone en segundo plano cada uno de los subsistemas que hemos definido.

Cada uno de los subsistemas intermedios (masa, montaje, …) son muy parecidos, contabilizan los recursos que tienen disponibles y cuando son suficientes para producir el resultado, lo notifican al subsistema siguiente. Por ejemplo, el subsistema del montaje es:

Que como ves, únicamente consiste en esperar a que llegue un mensaje a su número de cola asociada y en caso de que sea alguna materia prima, incrementa la cantidad disponible. Si es un comando de sistema (poweroff o report) efectúa lo propio y en cualquier caso, mira a ver si puede producir el resultado con el material disponible, en cuyo caso, lo notifica a la cola de destino.

El subsistema final es un caso especial, pues cuando se termina una tarta, no hay nada que hacer, así, únicamente se notifica, pero es esencialmente igual que los demás.

El último script que genera el generador de simuladores es el sendToAllSystems, que sirve para notificar a todos los subsistemas alguna cosa como que se apaguen o que reporten su estado:

Conclusión

Por un lado, vemos que existen estrategias que nos permiten abstraer la dificultad intrínseca de los problemas. Obviamente no las podemos aplicar siempre, sólo en aquellos problemas que las admitan (como en este caso). Conocerlas y practicar con ellas nos permite enfrentarnos a situaciones que a priori pueden parecer intratables o que superan nuestros conocimientos.

Por otro lado, vemos que no hace falta dedicar mucho tiempo ni recursos para modelar adecuadamente un problema a priori complejo. Subestimar o ignorar herramientas como el shell hará que perdamos la oportunidad de utilizarlas en innumerables ocasiones.

Nota sobre la implementación

La Programación dirigida por eventos no impone mayor restricción que el que se pueda “disparar” un cómputo cuando se producen ciertas condiciones, es por ello que existen otros paradigmas en los que puede hacerse una traslación casi directa cuando la usamos en la forma presentada en el post. Por ejemplo, es trivial definir en POO una clase denominada Subsistema que disponga de algún método recibirRecurso y la referencia al subsistema receptor de su producto. También es trivial crear un sencillo programa que únicamente mantenga una lista completa de todas las variables, si introducen una materia prima, incrementa la variable y revisa hasta que no se pueda todas las transformaciones en subproductos.

Que la implementación utilizada sea distribuida (cada subsistema puede correr en máquinas distantes) no es relevante (aunque interesante) pero enfatiza el sentido de “disparo de evento” que se produce entre los subsistemas y muestra que es muy sencillo utilizar colas de mensajes (semáforos, mutex, …) en un shell.

Más información | Programación dirigida por eventos.
Más información | Inter-process communication, msgtool.
Más información | Isidore of Seville, Rube Goldberg device (o incredible machine), Redes de Petri.

Comentarios cerrados
Inicio