Simplicidad real: lo que importa al diseñar pipelines y librerías
Una reflexión sobre la diferencia entre ser pequeño y ser sencillo, con ejemplos de Unix, Clojure y Bash.
La pregunta que surge de la charla de @notjack.space es simple: ¿qué significa realmente "simple" cuando hablamos de código y no solo de tamaño? En el post se compara un pipeline de Unix con un programa en Clojure y un script Bash, ilustrando cómo la complejidad se introduce cuando los componentes se entrelazan.
Unix: pequeño pero acoplado
El pipeline que cuenta la historia usa los comandos clásicos: cat, tr, sort, uniq -c y otro sort. Cada comando es pequeño y hace una cosa. Sin embargo, el hecho de que uniq -c dependa de que la entrada esté ordenada hace que el flujo sea acoplado. El orden y la agregación están ligados, lo que obliga a usar un segundo sort y a gestionar archivos temporales. En términos de rendimiento, esta solución consume memoria y tiempo de disco, porque el programa debe leer todo el archivo antes de continuar.
Clojure: funcional pero verboso
El equivalente en Clojure muestra el mismo algoritmo pero con una sintaxis más expresiva. Se usan funciones de orden superior (->>, re-seq, frequencies, sort-by) y la lógica se encadena en una sola expresión. A pesar de la legibilidad, el programa sigue siendo complejo cuando se quiere cambiar la salida: para preservar el orden original se añaden varias estructuras de datos (word_seq, freq_map) y se recorre la secuencia una vez más. El código se vuelve más largo y menos lineal que el pipeline.
Bash: mucho más complejo
La versión en Bash requiere varios archivos temporales, expresiones regulares y join para combinar los datos. Cada paso adicional introduce una capa de complejidad operativa, y el resultado final es difícil de mantener. Se pierde la claridad del flujo original y el script se vuelve frágil ante cambios de formato.
La definición de "simple"
El autor cita a Rich Hickey, quien define "simple" como algo con un solo lazo de control. En el contexto de Unix, la simplicidad se pierde cuando la lógica de orden y agregación se entrelaza. En Clojure, la simplicidad se logra mejorando la abstracción, pero el código aún puede volverse verboso. En Bash, la simplicidad es prácticamente inexistente.
¿Qué nos dice esto sobre el diseño de herramientas?
El mensaje central es que la simplicidad no es equivalente a ser pequeño. Una herramienta puede contener muchos módulos y seguir siendo simple si cada módulo hace una cosa bien y se comunica de forma desacoplada. Al diseñar pipelines o librerías, la prioridad debe ser la separación de responsabilidades y la minimización de la dependencia entre ellas.
Implicaciones prácticas
Para los administradores de sistemas y arquitectos, esto significa que al elegir o crear herramientas de procesamiento de datos, se deben evaluar no solo su tamaño sino su acoplamiento interno. Una solución que exige varios pasos intermedios, incluso si cada paso es pequeño, puede generar problemas de rendimiento y mantenimiento. Por otro lado, una librería bien desacoplada, aunque más grande, puede ofrecer mayor flexibilidad y rendimiento.
Al final, la reflexión invita a revisar nuestros flujos de trabajo y a considerar si estamos sacrificando la simplicidad al buscar soluciones pequeñas. La respuesta no es clara, pero la pregunta sí lo es: ¿qué valor le damos realmente a la sencillez en nuestro código?