Dependencias binarias y dependencias de código fuente

En C#, como en cualquier lenguaje compilado, cuando llega el momento de usar una librería tenemos siempre dos opciones:

  1. Utilizar una dependencia binaria, es decir, referenciar el assembly que contiene las clases que queremos utilizar.
  2. Copiar y pegar el código de las clases que queremos utilizar en nuestro proyecto y compilarlas junto con el proyecto.

Hasta hace no mucho tiempo, la primera opción parecía la forma más ortodoxa y andar copiando código de un sitio a otro podía llegar a verse como un “atajo”, algo que podías hacer pero que, en el fondo, sabías que estaba mal y que deberías convertir en una librería “de verdad” porque cuando hubiera que cambiar algo tendrías que ir proyecto a proyecto corrigiéndolo.

Las dependencias binarias son una opción más cómoda cuando se trata de librerías grandes, porque andar copiando y pegando 400 clases es bastante incómodo, sobre todo si lo que estamos copiando está minimamente organizado y las 400 clases están repartidos en otros tantos archivos que, a su vez, se distribuyen en varias carpetas. Algunos han pensado en esto e incluso hay una herramienta, tape, que permite fusionar esos 400 ficheros es un único .cs listo para añadir al proyecto, aunque el propio autor dice que no la ha probado extensivamente.

La segunda opción, copiar y pegar el código, es algo que, en el fondo, casi todos hemos estado haciendo continuamente. Hay algunos fragmentos de código que usamos en casi todos los proyectos pero que no llegan a tener la categoría de librerías. A veces acaban en una librería utils.dll o similar, pero en general suelen copiarse (y modificarse ligeramente) de un proyecto a otro. A priori, esta segunda opción puede parecer peor idea ya que estamos duplicando código en un montón de sitios, y todos sabemos que duplicar código es malo, DRY y todas esas cosas.

Sin embargo, con la llegada de mejores gestores de paquetes como NuGet, que simplifica el proceso de copia de los ficheros implicados en el proyecto de destino, y la “moda” de los micro-frameworks (Massive, TinyIoC, etc.), que hace que los frameworks sean más pequeños y manejables, la segunda opción empieza a ganar adeptos.

Duplicar el código tiene sus ventajas:

  • Reduce el número de assemblies a desplegar. Aparte de mejorar algo el rendimiento, simplifica el despliegue de la aplicación y simplificar cosas es siempre bueno. Es cierto que se pueden usar herramientas como ilmerge para integrar las librerías en el proyecto, pero siempre es una complicación adicional.
  • Facilita la depuración. Al incluir el código fuente en el proyecto es más sencillo depurar. Claro que puedes incluir símbolos de depuración con una librería binario, pero es mucho más sencillo depurar directamente código fuente y eso siempre funciona; con los símbolos a veces Visual Studio se pone un poco más pesado para dejarte depurar si no tienes el código fuente por ahí.
  • Evita dependencias ocultas entre aplicaciones.

    Si tenemos una librería que usamos en dos aplicaciones distintas y queremos cambiarla para añadir cosas necesarias en una de las aplicaciones, tenemos que tener cuidado para que estos cambios no afecten a la otra aplicación. Es de suponer que en un futuro la otra aplicación se actualizará a la nueva versión de la librería y no sería agradable que se rompiera.

    Al tener el código compartido entre dos aplicaciones diferentes, estamos obligados a valorar con mucho más cuidado cada cambio que hacemos en la librería. Por supuesto tenemos la opción de hacer un fork del código inicial y tener distintas ramas de la librería por cada aplicación pero en ese caso, ¿qué ventaja tendría crear una librería común?

Viendo esto parece que sea mucho mejor, copiar y pegar el código que usar una librería compilada, pero no hay que olvidar que usar una librería compilada tiene también sus ventajas:

  • Evitamos duplicar código, por lo que cuando se produce un problema o queremos introducir una mejora, sólo tenemos que tocar en un sitio.
  • Tenemos más herramientas para controlar la visibilidad de las clases (internal) y eso puede ayudar tener un API más clara.
  • Es más sencillo desplegar nuevas versiones porque sólo hace falta copiar una dll en lugar de varios archivos de código fuente.
  • Se reduce el tiempo de compilación.

Cuándo usar cada tipo

A la hora de decidir la forma en que usamos una dependencia la primera variable a tener en cuenta es cómo se distribuye. Por ejemplo, si quieres usar NHibernate, es un bastante incómodo copiar su miles de clases en tu proyecto, así que no seas terco y usa la dll que ya te dan compilada. Si vas a usar Massive, no compiles una dll sólo para él y añádelo directamente al proyecto que contenga el código de acceso a datos.

En cuanto a las librerías que se mantienen internamente dentro de una organización (empresa/equipo/etc.), depende mucho del tipo de dependencia:

Para librerías grandes, de propósito general, en las que estés dispuesto a invertir tiempo manteniéndolas estables, compatibles hacia atrás y con una calidad elevada, merece la pena crear dependencias binarias por su facilidad a la hora de añadirlas a otros proyectos y la ventaja de poder corregir bugs para todos los proyectos que usan la librería.

Cuando sea algo que parece que es general, pero de momento sólo vas a usar en una aplicación, no crees una librería, es una pérdida de tiempo. Cuando encuentres un problema en esa aplicación, en lugar de poder corregirlo allí tienes que irte a la librería, corregirlo, generar una nueva versión y actualizarla en el proyecto de la aplicación. Mantén el código en la aplicación y ya veremos que pasa en el futuro. YAGNI.

Si la librería es pequeña o está compuesta por muchas pequeñas clases de utilidad, cópiala y pégala de un sitio a otro. Es bastante probable que un mismo proyecto no necesite todos esos extension methods tan chulos que has creado, sino sólo una parte de ellos, y es también bastante probable que incluso parte de ellos tengan que ser ligeramente modificados para usarlos en ese proyecto. No merece la pena el esfuerzo de intentar cubrir el caso general cuando copiar/pegar/adaptar es muchas veces la opción más rentable.

Conclusiones

Puede parecer que hay más argumentos a favor de copiar y pegar que de crear librerías reutilizables y seguramente sea así porque realmente no hay tantas oportunidades como a nosotros nos gustaría de reutilizar el código de un proyecto a otro sin realizar ningún cambio. Más de una vez me ha pasado pensar que parte de un proyecto era “digna” de convertirse en librería para luego, con el tiempo, ver que sólo se usa en el proyecto original, complicando la corrección de fallos y la implementación de nuevas mejoras.

Por otra parte, hay componentes que tiene sentido convertir en librerías, bien porque son muy estables en el tiempo, muy genéricos, o tan útiles que merece la pena luchar por mantener su flexilbidad y compatibilidad. En ese caso, cuando estés seguro de que merece la pena, la opción de la librería es buena, pero no intentes crear el super framework universal, eso no suele funcionar.

Un comentario en “Dependencias binarias y dependencias de código fuente

  1. Hay una opción que «ayuda» un poco a lo de copiar y pegar, y es la opción de agregar un archivo .cs de otro proyecto en un nuevo proyecto, pero utilizar la opción de «enlazarlo», así modificas un solo archivo, pero lo utilizas en mas de un proyecto.

Comentarios cerrados.