¿Por qué usar un sistema de compilación?

En esta página, se explica qué son los sistemas de compilación, qué hacen, por qué deberías usar un sistema de compilación y por qué los compiladores y las secuencias de comandos de compilación no son la mejor opción a medida que tu organización comienza a escalar. Está dirigida a desarrolladores que no tengan mucha experiencia con un sistema de compilación.

¿Qué es un sistema de compilación?

En esencia, todos los sistemas de compilación tienen un propósito sencillo: transforman el código fuente que escriben los ingenieros en objetos binarios ejecutables que las máquinas pueden leer. Los sistemas de compilación no son solo para código creado por humanos; también permiten que las máquinas creen compilaciones de forma automática, ya sea para pruebas o lanzamientos para producción. En una organización con miles de ingenieros, es común que los ingenieros activen la mayoría de las compilaciones automáticamente, en lugar de hacerlo de forma directa.

¿No puedo usar un compilador?

Es posible que la necesidad de un sistema de compilación no sea obvia de inmediato. La mayoría de los ingenieros no usan un sistema de compilación mientras aprenden a programar; la mayoría comienza con la invocación de herramientas como gcc o javac directamente desde la línea de comandos, o su equivalente en un entorno de desarrollo integrado (IDE). Siempre que todo el código fuente esté en el mismo directorio, un comando como este funcionará bien:

javac *.java

Esto le indica al compilador de Java que tome cada archivo fuente Java del directorio actual y lo convierta en un archivo de clase binario. En el caso más simple, esto es todo lo que necesitas.

Sin embargo, apenas se expande el código, comienzan las complicaciones. javac es lo suficientemente inteligente como para buscar en los subdirectorios del directorio actual a fin de encontrar código para importar. Sin embargo, no tiene forma de encontrar el código almacenado en otras partes del sistema de archivos (quizás una biblioteca compartida por varios proyectos). Además, solo sabe cómo compilar código Java. Los sistemas grandes suelen implicar diferentes piezas escritas en una variedad de lenguajes de programación con redes de dependencias entre esas piezas, lo que significa que ningún compilador para un solo lenguaje puede compilar el sistema completo.

Una vez que trabajas con código de varios lenguajes o varias unidades de compilación, compilar código ya no es un proceso de un solo paso. Ahora debes evaluar de qué depende tu código y compilar esas partes en el orden adecuado, quizás con un conjunto de herramientas diferente para cada una. Si cambia alguna dependencia, debes repetir este proceso para evitar depender de los objetos binarios inactivos. Incluso para una base de código de tamaño moderado, este proceso se vuelve tedioso y propenso a errores con rapidez.

El compilador tampoco sabe nada sobre cómo controlar las dependencias externas, como los archivos JAR de terceros en Java. Sin un sistema de compilación, puedes solucionar esto descargando la dependencia de Internet, pegándola en una carpeta lib del disco duro y configurando el compilador para que lea las bibliotecas de ese directorio. Con el tiempo, es difícil mantener las actualizaciones, las versiones y el origen de estas dependencias externas.

¿Qué ocurre con las secuencias de comandos de shell?

Supongamos que, al principio, tu proyecto de pasatiempo es lo suficientemente simple como para que puedas compilarlo solo con un compilador, pero comienzas a tener algunos de los problemas descritos anteriormente. Tal vez aún no creas que necesitas un sistema de compilación y puedes automatizar las partes tediosas con algunas secuencias de comandos de shell simples que se encargan de compilar los elementos en el orden correcto. Esto es de ayuda por un tiempo, pero muy pronto comienzas a tener más problemas:

  • Se vuelve tedioso. A medida que tu sistema se vuelve más complejo, comienzas a trabajar casi tanto tiempo en las secuencias de comandos de compilación como en el código real. La depuración de secuencias de comandos de shell es dolorosa, ya que cada vez más hackeos se superponen.

  • Es lento. Para asegurarte de no utilizar bibliotecas inactivas por accidente, tienes que hacer que la secuencia de comandos de compilación compile cada dependencia en orden cada vez que la ejecutes. Piensas en agregar algo de lógica para detectar qué partes deben reconstruirse, pero eso suena terriblemente complejo y propenso a errores para una secuencia de comandos. O piensas en especificar qué partes deben reconstruirse cada vez, pero luego regresas al primer paso.

  • Buenas noticias: ¡Llegó el momento del lanzamiento! Será mejor que descubras todos los argumentos que necesitas pasar al comando jar para crear tu compilación final. Y recuerda cómo subirlo y enviarlo al repositorio central. Compila y envía las actualizaciones de la documentación, y envía una notificación a los usuarios. Mmm, tal vez esto requiera otro script...

  • ¡Desastre! Tu disco duro falla y ahora debes volver a crear todo el sistema. Era lo suficientemente inteligente como para mantener todos sus archivos de origen en el control de versión, pero ¿qué pasa con las bibliotecas que descargaste? ¿Puedes encontrarlos todos de nuevo y asegurarte de que sean la misma que cuando los descargaste? Es probable que tus secuencias de comandos dependan de herramientas particulares que se instalaron en lugares determinados, ¿puedes restablecer ese mismo entorno para que las secuencias de comandos vuelvan a funcionar? ¿Qué pasa con todas las variables de entorno que configuraste hace mucho tiempo para que el compilador funcionara bien y luego las olvidaste?

  • A pesar de los problemas, tu proyecto es lo suficientemente exitoso como para que puedas comenzar a contratar más ingenieros. Ahora te das cuenta de que no se necesita un desastre para que surjan los problemas anteriores, ya que debes pasar por el mismo proceso de arranque difícil cada vez que un desarrollador nuevo se une a tu equipo. Y, a pesar de tus mejores esfuerzos, aún existen pequeñas diferencias en el sistema de cada persona. Por lo general, lo que funciona en la máquina de una persona no funciona en el de otra, y, cada vez, se necesitan algunas horas de rutas de acceso de herramientas de depuración o versiones de biblioteca para descubrir dónde se encuentra la diferencia.

  • Decides que necesitas automatizar tu sistema de compilación. En teoría, esto es tan simple como obtener una computadora nueva y configurarla para que ejecute tu secuencia de comandos de compilación todas las noches con cron. Aún debes realizar el doloroso proceso de configuración, pero ahora no tienes el beneficio de que un cerebro humano pueda detectar y resolver problemas menores. Ahora, todas las mañanas, cuando ingresas, ves que la compilación de la noche anterior falló porque ayer un desarrollador hizo un cambio que funcionó en su sistema, pero no en el automático. Cada vez es una solución simple, pero sucede tan a menudo que pasas mucho tiempo cada día descubriendo y aplicando estas correcciones simples.

  • Las compilaciones se vuelven más lentas a medida que el proyecto crece. Un día, mientras esperas a que se complete una compilación, observas tristemente el escritorio inactivo de tu compañero de trabajo, que está de vacaciones, y deseas que haya una forma de aprovechar toda esa potencia de procesamiento desperdiciada.

Te encuentras con un problema clásico de escala. Para un desarrollador que trabaja como máximo en un par de cientos de líneas de código durante una o dos semanas (lo que podría haber sido toda la experiencia hasta el momento de un desarrollador junior que acaba de graduarse de la universidad), todo lo que necesitas es un compilador. Los guiones te pueden llevar un poco más lejos. Sin embargo, en cuanto necesitas coordinar entre varios desarrolladores y sus máquinas, incluso una secuencia de comandos de compilación perfecta no es suficiente porque se vuelve muy difícil tener en cuenta las pequeñas diferencias en esas máquinas. En este punto, este enfoque simple se desglosa y es hora de invertir en un sistema de compilación real.