featured image

La programación orientada a ferrocarril (o Railway oriented Programming, RoP en inglés) es una nueva forma de orientas nuestro software para conseguir mejor legibilidad y entendimiento ante situaciones de error, ¿sabes cómo funciona?

En un artículo reciente sobre Elixir leí sobre un nuevo tipo de programación llamada Railway oriented Programming (Programación orientada a ferrocarril). El nombre parece un poco rimbombante pero la idea es bastante simple y clara. Si pensamos en el ferrocarril, los raíles que guían una locomotora, podemos considerar que la ejecución de una función tiene dos posibles caminos: el correcto y el incorrecto, el esperado y el imprevisto.

Cada lenguaje es un mundo. Realmente no estoy seguro pero por la cantidad de documentación de este patrón o paradigma en F# doy por hecho que este sistema nació en F#, un lenguaje funcional desarrollado por Microsoft para su plataforma .NET.

La idea de este sistema es considerar la ejecución como una locomotora. Debes conducir a la locomotora al punto final y en caso de tener un cambio de agujas (que suceda un error) tomar el camino de servicio para poder finalizar dando información del error. De esta forma el camino correcto queda claro y solo con los pasos específicos a dar para completar el recorrido. En caso de error siempre se deriva a la vía de servicio donde se encuentran todos los casos de error y finaliza la ejecución de la función concreta.

Construyendo las funciones

Este sistema requiere un poco de refinamiento. Es decir, las funciones deben retornar siempre de forma binaria los datos de dos formas: correcta o incorrecta. Los valores correctos deben ser identificables y aprovechables para el resto de la ejecución y los valores incorrectos igualmente identificables para poder actuar ante los errores en el camino específico para ellos.

En un lenguaje como Elixir podemos decir que el retorno debería ser estándar para fijarse en tuplas de dos elementos donde el primer elemento corresponde de forma fija a un átomo (ok o error) y el otro elemento al contenido en sí (el retorno esperado o la razón del error).

No obstante para evitar tener que escribir cada función o tener que sobrecargar las ya existentes en Elixir existe una librería que permite sobrecargar un operador y emplear unas palabras clave (funciones) para permitirnos envolver según qué funciones. Aún parece que no hay librería pero solo se necesita un módulo para trabajar de esta forma y lo puedes bajar de este Gist.

Tipos de carriles

Hay definidos cuatro operaciones try..catch, map, tee y la llamada normal a la función. La captura de excepciones puede verse con la misma representación que la llamada normal pero tee y map tienen otras representaciones diferentes.

El carril tee se supone que actúa cuando una función (como el típico print en la mayoría de lenguajes) realiza una acción pero no retorna nada o su retorno es desechable. Eso quiere decir que la salida de la ejecución lleva a un camino sin salida y debe mantenerse tanto los datos de entrada como el camino inicial:

El carril map siempre sigue el carril correcto. Son funciones muy conocidas que no pueden retornar un error. Se muestra así:

Podemos poner un ejemplo con el uso del módulo de Gist anterior. El código se encargaría de validar una solicitud, obtener un usuario, actualizar la base de datos, enviar un email y retornar la respuesta:

request
>>> validate_request
>>> (map get_user)
>>> (tee update_db)
>>> (try_catch send_email)
>>> return_http_message

Como puedes ver el código muestra justamente eso. Ni más ni menos. La solicitud son los datos de entrada que se encauzan para su validación, que se emplean para obtener el usuario, actualizan la base de datos, se envía el email con la información y se retorna finalmente la respuesta.

En este código la obtención del usuario en realidad es una acción de modificación de uno de los datos de entrada y tras la validación ese dato debe existir y su modificación es trivial por lo que se trata de un carril map.

La actualización en la base de datos no retorna datos necesarios para la respuesta por lo que se desechan y se continúa con los datos originales. Es un carril tee.

Enviar el email puede provocar un error inesperado por lo que lo rodeamos con try_catch y así aseguramos su retorno ya sea correcto o erróneo. Por último el último carril es normal y se encarga de retornar la información de la respuesta.

La función última además se encarga de unificar las respuestas y retornar un error o la información. En realidad este ya no es un carril porque es el último eslabón en el flujo creado.

Aplicaciones...

Realmente cuando más profundizamos más vemos que se pueden hacer cosas y más claros quedan los conceptos y se simplifica el desarrollo. En artículos anteriores hablé de [Altenwald Books][4] donde integré el método de pago. En principio este método de pago lo tenía dividido en unas cuantas funciones con diferentes condicionales y anidamientos. Ciertamente muy difícil de leer.

Cuando comencé a ver este tipo de programación me aventuré a refactorizar esa parte del código y tengo que admitir que al principio no fue fácil. Hay que tener muy claras cada una de las partes desarrolladas cómo funcionan y después cómo deberían funcionar en el nuevo esquema.

Finalmente, al reescribir el código resultó genial. Ahora solo hay un nivel de anidamiento, cada función tiene un nombre más coherente, el flujo es claro y los errores están enumerados y centralizados.

Conclusiones

La forma de programar orientada a ferrocarril es una forma de organizar mejor el código pensando en piezas encajables y tipos de piezas concretas. Crear estas piezas y asegurarnos de que cumplen con su misión es cosa de las pruebas unitarias, algunas pruebas de integración y finalmente un conocimiento a alto nivel de qué debe realizar la aplicación.

Cuantas más pequeñas y generales sean las funciones mejor será su reutilización y finalmente tendrás un código extensible, entendible y organizado, ¿qué más se puede pedir?

¿Te animas a organizar y desarrollar mejor tu código?, ¿aceptas el desafío de ver cómo se vería en el lenguaje que uses normalmente?, ¿necesitas más ayuda para entender exactamente cómo funciona en profundidad? ¡Déjanos tu comentario!