Publicado en
Autor
En el número anterior de SG (SG #27), Agustín Ramos presentó un artículo acerca de estrategias para lograr una modularización efectiva en Java. Varios de los puntos de su artículo me llevaron a dedicar la presente columna a explicar a qué llamamos una distribución de Linux, y cuál es su relación –y la solución que ofrece– a los problemas derivados de la complejidad derivada de la modularización, que bien describió Agustín. Como deben ya saberlo, a diferencia de otros sistemas operativos, el entorno operativo completo al que nos referimos como Linux no es desarrollado por un sólo grupo, ni sigue un roadmap, criterios o visión en común. Mucho se ha argumentado acerca de las características que diferencían a los proyectos de software libre, pero no abordaré dicha discusión en este momento. Simplemente no quiero dejar pasar la oportunidad que abrió el artículo de Agustín, para ilustrar cómo abordamos la relación entre proyectos independientes en el software libre, por qué enfatizamos tanto en lo prevalente que resulta dicha modularización, y cómo nos enfrentamos a su inherente complejidad.
Los sistemas basados en Linux son estructurados, pues, en distribuciones. Una distribución es un conjunto de programas –típicamente miles de ellos– que, entre todos, presentan la funcionalidad que un usuario espera ya no sólo de un sistema operativo sino que de todo un entorno operativo integrado. Además, se encargan de gestionar las relaciones de dependencia entre dichos programas, y resolver dichas dependencias, para facilitar al usuario la instalación, remoción y actualización de software.
Una instalación mínima de cualquier distribución de Linux no tiene menos de un par de centenares de paquetes individuales. El núcleo Linux, por sí mismo, no representa más que la interfaz más básica para presentar una abstracción del hardware ante los programas que corren sobre él. Formalmente, al mencionar Linux ni siquiera nos referimos a la interfaz de línea de comandos (misma que generalmente es provista por los paquetes bash, dash o sash, dependiendo de las necesidades del administrador). Son en realidad contados los paquetes que no dependen más que de paquetes esenciales. Toda distribución define un pequeño conjunto (decenas) de programas que es fundamental tener instalados como base para cualquier otro, conjunto mínimo que asumimos que siempre estará ahí para asegurarnos la funcionalidad mínima.
Si bien las primeras distribuciones tuvieron por objetivo facilitar la instalación de un sistema basado en Linux a usuarios con menor involucramiento técnico, y fueron instrumentales en la primer ola expansiva de usuarios que fuimos adoptando Linux hacia mediados de los 1990, han trascendido ya a éste rol para dar respuesta al infierno de dependencias al que se refiere en su texto Agustín Ramos: Hacia fines de los 1990, aproximadamente cuando todas las distribuciones comenzaban a contar en miles los paquetes independientes que ofrecían, comenzó a hacerse obvio que no bastaba con que cada paquete indicara de qué otros paquetes y versiones dependía, sino que era necesario contar con una arquitectura de paquetes: un esquema orientado a depósitos y no a paquetes individuales, que se encargara de resolver las dependencias cada que el administrador instalara o eliminara un paquete.
La primera arquitectura fue introducida por la distribución Debian, bajo el nombre de apt (A Package Tool). Es gracias a apt que al día de hoy la versión de desarrollo de Debian cuenta con 15640 paquetes fuente independientes, que resultan en 27886 paquetes binarios, cubriendo prácticamente todas las áreas de necesidad en cómputo, tanto a nivel de aplicaciones como de bibliotecas. A diferencia de otras arquitecturas previas, como los ports de los Unixes BSD, apt está además construido basado en el manejo de depósitos múltiples. Esto significa que, además de servirme para instalar los paquetes oficiales de la distribución, nos permite definir depósitos adicionales con el software que desarrollemos localmente, así como de paquetes adicionales que preparemos localmente para uso en nuestra organización.
Con esto como introducción, veamos cómo se aplica al texto de Agustín. Por razones de espacio, me enfoco a cómo éste esquema reduce fuertemente los efectos negativos de la modularización, permitiendo a los desarrolladores crear software más robusto y temer menos a esta fuente de dolores de cabeza. Demasiadas dependencias. El argumento principal mencionado en el texto al que hago referencia en contra de tener demasiadas dependencias es la posterior dificultad de instalación de nuestros sistemas. Sin embargo, al crear paquetes que contienen toda la información de las dependencias, convertimos el fastidioso proceso de instalación (y todo el tiempo que requiere documentarlo) en una sóla instrucción al sistema.
Dependencias cíclicas. Coordinar el trabajo de cientos de voluntarios en todo el mundo, sin más factores de cohesión que su voluntad por crear un sistema de calidad trae como resultado natural el que parte importante de sus esfuerzos estén encaminados a la creación de documentos de políticas comprehensivos — y a que su comunidad de desarrolladores comprenda la importancia de dichos documentos. Una política esencial es la de prohibir dependencias cíclicas entre paquetes. Sin embargo, conforme la complejidad de cada uno de los paquetes aumenta, es posible que aparezcan dependencias cíclicas indirectas. Para evitar problemas como este, se hacen revisiones periódicas de todos los procesos imaginables. En este aspecto, si bien no hay balas de plata para evitar las dependencias cíclicas, contamos con una comunidad de desarrolladores verificando que estos problemas se mantengan al mínimo.
Largas cadenas de dependencias. Este fue precisamente el punto que motivó al desarrollo de las arquitecturas de paquetes. Podemos confiar en que la arquitectura que elijamos, empleando los depósitos del sistema y de nuestra organización, sepan resolver este punto sin que represente problemática alguna. Además, no tenemos por qué limitarnos al uso de este esquema para las dependencias de los componentes externos que empleemos. Si dentro de nuestra organización nos acostumbramos a empaquetar nuestro código en componentes, la reutilización de código que podamos hacer será mucho más simple y natural.
Dependencias en conflicto. Nuevamente, aquí acudimos a la sabiduría de las masas, a la fuerza de la multitud. Una distribución basada en Linux no es sólo un conjunto de programas disponibles a través de un mismo depósito, la mayor parte del trabajo de sus creadores es asegurarse que todos los componentes sean mutuamente compatibles y asegurar a los usuarios un producto de calidad con todas las actualizaciones necesarias para asegurar su seguridad (cuidando no comprometer su estabilidad) durante su ciclo de vida. Este puede ser un punto fundamental al elegir qué distribución vamos a usar para determinado proyecto: Hay distribuciones principalmente orientadas al perfil de usuario de escritorio, para el cual es fundamental tener siempre soporte completo para el último hardware, aceleración gráfica completa y demás bondades. Sin embargo, para basar nuestros desarrollos empresariales, típicamente preferiremos las distribuciones con ciclos de vida más largos, con un mayor soporte a largo plazo.
Como pueden ver, manejar una arquitectura de paquetes simplifica algunas de las tareas más complicadas (y más ingratas) del desarrollo de software, el manejo de toda la talacha creada por los componentes que, a fin de cuentas, incluimos para ahorrarnos trabajo. Cuando veo los instaladores típicos, que crean enormes amasijos (típicamente en forma de enormes instaladores .msi o archivos .jar que incluyen copia de todas las dependencias para evitar estos problemas), no exagero: me dan ganas de llorar. Porque además, al tener varias copias de una misma biblioteca en el sistema, tengo la certeza de que en caso de aparecer algún defecto en alguna de ellas, habrá componentes de mi sistema que reciban la corrección en cuestión — pero habrá otros que no.
Acostumbrarse al manejo de dependencias externas nos reduce la tentación de acudir al tan temido ligado estático, reduce el peso de nuestras imágenes de instalación (y de nuestros sistemas ya instalados), y contribuye significativamente a mantener un mayor control de calidad en nuestros procesos. Mucha gente evita aprovechar la modularización como medida preventiva para no perder la razón resolviendo dependencias y bugs creados por compatibilidad incompleta entre versiones. Sin embargo, dejar de usar buen software, ya existente y probado por terceros, sólo por no contar con las herramientas de seguimiento correctas es un triste error en el que debemos evitar caer. La hidra de la modularización a la que se refiere Agustín puede ser un mounstro mortal, pero si aprendemos a hablar con ella puede ser nuestro mayor aliado. Porque si dos cabezas piensan mejor que una, ¿qué decir de decenas de cabezas?
Gunnar Wolf es administrador de sistemas para el Instituto de Investigaciones Económicas de la UNAM y desarrollador del proyecto Debian GNU/Linux. www.gwolf.org
- Log in to post comments