Algunas cosas que los científicos pueden aprender de los programadores

Naukas

Mi carrera científica tuvo un parón en 2012. O al menos entonces pensé que era un parón. Nada más licenciarme en física, en lugar de continuar directamente hacia un doctorado (como me hubiese gustado), las circunstancias me obligaron a buscar un empleo a tiempo completo. Tuve la suerte de encontrar uno, concretamente de ingeniero en una empresa de diseño de lentes.

Reconozco, con cierta vergüenza, que entonces pensaba que en el sector privado no podría aprender gran cosa en comparación con lo que podría aprender si hubiese continuado en la Universidad. Sin embargo hoy, retornado a la investigación desde 2015, utilizo a diario muchas de las herramientas que aprendí en mi etapa como ingeniero.

Dos destacan sobre el resto: los tests unitarios y el control de versiones. Son herramientas enormemente obvias y de dominio público. Tanto, que he dudado sobre la utilidad de publicar o no el presente artículo. Si al final me he decidido a hacerlo es debido al convencimiento, basado en mi experiencia personal, de que muchos profesionales del mundo científico desconocen por completo su existencia.

Si hay algo en el mundo real que se parece al concepto fantástico de “palabras mágicas” es el de comando en programación. Escribes las palabras correctas, y el ordenador hace lo que quieres. Se parece, pero no es igual. Para empezar, en programación no hay ninguna magia de por medio. Y quizá más importante, lo realmente relevante es la lógica detrás de los comandos y no los propios comandos. Sin embargo, hay un paralelismo con las “palabras mágicas” sobre el que quiero llamar la atención: basta un error tipográfico, o un comando mal utilizado, para que todo se vaya al traste. Sabido esto, sorprende que existan programas informáticos con miles e incluso millones de líneas, escritos entre varios programadores, con diferentes versiones a lo largo de los años y a menudo dependiendo de otros programas y submódulos, … y que a pesar de todo funcionen. ¿Cuál es su secreto?, ¿cómo hacen para no meter la pata?

Bien pensado, la pregunta tiene especial relevancia para los investigadores científicos. Para empezar, muchos de nosotros programamos en nuestro día a día. Pero es que además nos enfrentamos a un problema similar al de un proyecto de ingeniería cada vez que escribimos una publicación: al fin y al cabo, una publicación también es un proyecto complejo, que debe tener consistencia interna (como poco), a menudo con varios autores y con varias versiones a lo largo del tiempo.

Los programadores acumulan décadas de experiencia en este tipo de problemas. Sus métodos y herramientas son notoriamente públicos y, a pesar de ello, sorprendentemente desconocidos para una enorme fracción de los profesionales científicos. Quizá valga la pena aprender de ellos.

El propósito de este artículo es llamar la atención sobre la mera existencia de este tipo de herramientas. Usaré como ejemplo dos de ellas, aquellas que me resultaron más útiles. Concretamente los tests unitarios y el control de versiones.

Espero con ello que, como mínimo, la próxima vez que pierdas una tarde buscando un error en un trozo de código que ayer funcionaba perfectamente, o la próxima vez que abras una carpeta con doce versiones de tu tesis (y te sientas sucio), o tengas cualquier otro problema relacionado con la gestión de información, sepas que no estás sólo… que casi seguro alguien ha tenido el mismo problema antes que tú, y ha creado herramientas para evitarlo. Y como máximo, que quizá nunca más te vuelva a suceder algo así.

Ejemplo extraído de XKCD cómics

Tests unitarios

La idea detrás de los tests unitarios consiste en escribir mini programas que se aseguren de que todo funciona cómo esperamos.

Veamos un ejemplo: imagina que quieres escribir una (sencillísima) función para sumar dos números, algo como:

suma(x, y):

return (x + y)

Un test obvio sería, por ejemplo:

asegúrate de que suma(2, 3) = 5

Otros menos obvios serían, por ejemplo, probar a sumar un número negativo:

asegúrate de que suma(2, -3) = -1

O un número no entero:

asegúrate de que suma(1.1, 2.3) = 3.4

O probar a introducir valores extraños, como texto:

asegúrate de que suma(1, “abc”) = Error

Esta pequeña batería de tests se guarda para que las comprobaciones puedan ser repetidas cada vez que se quiera. Cualquier edición futura de la función deberá pasar, al menos, los mismos tests y quizá otros nuevos. Si en el futuro alguien, por error, modifica la función suma así:

suma(x, y):

return (x – y)

los tests nos indicarán inmediatamente que algo va mal con nuestra función suma.

Nuestro ejemplo parece (y es) bastante tonto, pero no olvidemos que el código está vivo. Lo que hoy empieza como una o dos funciones facilonas, mañana puede crecer, y en un año contener cincuenta funciones interrelacionadas entre sí. Un solo error en una de ellas puede producir un desastroso efecto dominó. Los tests, cuando están bien escritos, facilitan muchísimo la tarea de localizar errores.

Como ventaja adicional, leer los tests correspondientes a una función que no esté bien documentada puede ayudarnos a entender qué pretendía el autor (a menudo nosotros mismos hace seis meses) al escribirla.

Por otro lado, programar sabiendo que cada función que escribamos deberá ser comprobada por los tests nos forzará a programar de un modo más lógico y modular.

Por último, si compartes tu código con un potencial usuario (que probablemente seas tú mismo en un futuro), lo primero que hará este será comprobar que todos los tests se pasan con éxito también en su equipo. Si la respuesta es no, probablemente signifique que el programa no se ha instalado correctamente. Sea como sea, el nuevo usuario estará alertado, y encontrar el problema será relativamente sencillo.

Me interesa, ¿cómo empiezo?

Aunque la idea detrás de los tests unitarios es muy general, la forma específica de implementarlos de forma práctica dependerá del lenguaje de programación concreto que uses. ¿Cómo empezar a usarlos?… sí, lo has adivinado, googlea “unit testing” + .

Control de versiones

Los programas informáticos (y también los textos) evolucionan en el tiempo. Crecen, cambian, se actualizan, … La idea básica detrás del control de versiones es llevar un registro ordenado y comentado de todos los cambios que se han realizado sobre el código, con la posibilidad de, por ejemplo, comparar el mismo archivo en dos momentos distintos o volver a una versión antigua en caso de que “rompamos” algo en la actual.

A diferencia de, por ejemplo, las copias de nuestro código guardadas en servicios como Dropbox o Google Drive, el control de versiones implica registrar solamente aquellas actualizaciones que se consideran importantes, junto con una descripción de la lógica detrás del cambio o conjunto de cambios.

Como bola extra, servicios como github o bitbucket permiten publicar fácilmente en la red el conjunto de código + control de versiones. Esto es especialmente útil en situaciones como las siguientes:

  • Si estás escribiendo código con varios colaboradores.
  • Si has usado tu código en un artículo científico y quieres enlazarlo. Si quieres que los resultados sean reproducibles por parte de un lector interesado, es condición necesaria (pero no suficiente) enlazar exactamente a la misma versión que tú utilizaste en tu análisis.
  • Si quieres dar a conocer tu código.
Ilustración 1 Ejemplo de un registro de GitHub. Los «commits» aparecen ordenados cronológicamente, y cada uno corresponde a una modificación lo suficientemente importante como para merecer ser comentada. Haciendo click sobre los commits (Fuente aquí) se pueden ver en todo detalle los cambios efectuados.

Me interesa, ¿cómo empiezo?

El sistema de control de versiones más popular actualmente es git, y se puede obtener gratuitamente. El primer encontronazo puede ser un poco impactante si no estás acostumbrado a las aplicaciones de consola. El uso de interfaces gráficas puede ayudar bastante durante las primeras etapas. En particular, yo utilicé una llamada SourceTree hasta que me acostumbré a usar solamente la consola de comandos.

No quiero complicarme

La primera reacción de la mayoría de científicos que conozco al saber de estos métodos es considerarlos demasiado complejos y/o alejados de sus especialidades. Irónicamente, suelen desarrollar sus propios métodos “ñapa”, que acaban siendo igual de complicados (o más), pero mucho más inseguros e ineficientes.

El hecho es que escribir un artículo científico, un trozo de software científico o una tesis son procesos complejos. Y lo son independientemente de las herramientas que uno use. Los programadores tienen décadas de experiencia en este tipo de problemas de gestión de la información… ¿por qué no usar sus herramientas de probada eficacia en lugar de reinventar la rueda?

Este post ha sido realizado por Pablo Rodríguez (@DonMostrenco) y es una colaboración de Naukas.com con la Cátedra de Cultura Científica de la UPV/EHU.

4 comentarios

  • […] Algunas cosas que los científicos pueden aprender de los programadores: “Para empezar, en programación no hay ninguna magia de por medio. Y quizá más importante, lo realmente relevante es la lógica detrás de los comandos y no los propios comandos. Sin embargo, hay un paralelismo con las “palabras mágicas” sobre el que quiero llamar la atención: basta un error tipográfico, o un comando mal utilizado, para que todo se vaya al traste. Sabido esto, sorprende que existan programas informáticos con miles e incluso millones de líneas, escritos entre varios programadores, con diferentes versiones a lo largo de los años y a menudo dependiendo de otros programas y submódulos, … y que a pesar de todo funcionen. ¿Cuál es su secreto?, ¿cómo hacen para no meter la pata?” […]

  • Avatar de Teorema de Gödel

    Las herramientas que propones son útiles sólo desde filosofías de programación no modulares. Si se emplea la filosofía de UNIX (https://www.youtube.com/watch?v=XvDZLjaCJuw), la «complejidad» se torna irrelevante, y así tanto la presencia de fallas como la inherente necesidad de evaluar todo considerando nuevas «funcionalidades» se vuelve un asunto trivial por ser inexistente.

    ¿Cómo implementar eso para una publicación? Dividiendo la publicación en partes enteramente funcionales. Esforzarse para que modificar una de ellas no implique alterar las restantes, teniendo que evaluar innecesariamente todo de nuevo. Así también el sistema de versiones se simplifica mucho, pues sólo se diversifica lo que es necesario modificar, no todo el cuerpo de la publicación.

    Para ser más claro, si la función «suma» ya suma, no es necesario añadirle más funcionalidades. Que se quede tal cual está, por muy simple que parezca. Lo importante es siempre considerar que esa suma se pueda conectar eficientemente con otras funciones diferentes. En UNIX (y en GNU/Linux), si se desea calcular (x+y)^0.5, es decir, extraer la raíz cuadrada de una suma, bastaría con teclear

    echo «4.1 3.2″ | suma | raiz

    Así «echo», que sirve para imprimir en pantalla lo que esté entre las comillas, imprime 4.1 y 3.2. Esa función, «echo» también puede imprimir otro tipo de texto, como en

    echo «Hola»

    Esa función sólo hace eso y no otra cosa. Luego «suma» efectúa la suma de lo que «echo» imprimiría. Finalmente, «raiz» efectúa la raíz del resultado que «suma» arroja, sin importar la procedencia de los números. Las herramientas son tan simples y están hechas tan universalmente, que adquieren mucha flexibilidad. Por ejemplo, con

    echo «4.1» | seno | raiz

    se calcula (sen(4.1))^0.5, pero puede cambiarse el orden de las funciones, es decir,

    echo «4.1» | raiz | seno

    y así se calcula sen(4.1^0.5). No se modificó en ninguna forma la estructura de ninguna función: simplemente se aprovecha la flexbilidad que éstas adquieren por existir de un modo simple e independiente, pero con el potencial de conectarse entre sí.

    Implementar eso con archivos de texto implica pensar en que la «introducción» estará almacenada en un único archivo y que será sólo eso, una introducción, y no otra cosa. El archivo luce demasiado simple, sin duda, pero funciona bien. Esta «introducción» estará conectada a un «marco teórico» que sólo debe ser eso, almacenándolo en otro archivo con aspecto simple, casi simplón. Haciendo eso resulta muy fácil ver dónde están las fallas, qué se debe modificar, qué no se debe modificar, o qué sí va y qué no, y en qué orden, dentro del texto final. Es posible unir todo en un único archivo cuyo cuerpo se conforme de los pequeños y simplones archivos con los cuales se trabajó.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.Los campos obligatorios están marcados con *