viernes, 5 de junio de 2015

Trunks, tags & branches

¡Hola! Les acerco mi experiencia en la organización más eficiente de un directorio de control de versiones de código fuente; especialmente en los casos en donde tenemos diferentes desarrollos en paralelo, o tenemos que realizar diversos releases intermedios, y mantenerlos, mientras seguimos desarrollando una nueva funcionalidad aparte.

Si bien mi experiencia se centra en TFS y SVN, con los cuales vengo trabajando ya desde hace ya varios años, estos conceptos pueden aplicarse también a otros sistemas como GIT, CVS, Mercurial, etc.

Introducción a las herramientas de SCM

Sin ahondar mucho en hechos históricos, podemos inferir que cuando surgieron los equipos de desarrollo de software, nacieron los problemas de tener que sincronizar el trabajo que cada uno de los miembros realizaba. Integrar los cambios y modificaciones, volver atrás una versión, trackear un cambio para detectar cuando se hizo (o quien lo hizo), entre otras cosas, se volvieron actividades comunes en los diferentes proyectos que se realizaban.

Y así surgieron las primeras herramientas de repositorio, que nos brindaron la posibilidad de almacenar nuestro código fuente, versionándolo, resguardándolo y agregándole la metadata necesaria para poder realizar el tracking correspondiente. Con el paso del tiempo fueron evolucionando, convirtiéndose en las hoy llamadas herramientas de SCM (por Software Configuration Management) con una mayor cantidad de características y beneficios, ya que no sólo se encargan de administrar los elementos de nuestro software, sino que además ofrecen soporte a la gestión y administración de los proyectos.

¿Qué beneficios nos aportan las herramientas de SCM?

Actualmente es casi imposible pensar en llevar adelante un proyecto con éxito sin contar con una de estas herramientas para el equipo. Y aún en el caso de que la solución fuera desarrollada por una única persona, el contar con una herramienta de SCM trae numerosos beneficios, más allá de la sincronización de nuestro código fuente:

· Mantenimiento de la integridad de los componentes de nuestra solución (sea código fuente o no)

· Evaluación y tracking de cambios

· Auditoría de la configuración

· Respaldo de versiones anteriores de cada uno de los elementos

· Gestión de actividades y tareas

· Planificación y seguimiento del estado de avance

· Trazabilidad horizontal (tareas asignadas vs. changesets de código)

· Reducción de costos del proyecto como producto de mantener todo más ordenado

Definiciones de Trunk, Branch y Tag

Ahora bien, pasemos a definir tres de los conceptos más importantes que deberemos manejar a la hora de trabajar con una herramienta de SCM y sobre los cuales luego vamos a discutir diferentes estrategias de organización y algunas buenas prácticas de trabajo para que apliquemos en nuestros proyectos.

clip_image001

Trunk (Tronco): Es la línea principal de desarrollo, en donde se realizan los cambios menos importantes o con un menor impacto en el día a día del proyecto. Además, es el lugar en donde se integran los cambios realizados en otros branches una vez que están finalizados y validados. Suele haber un único trunk por proyecto.

Branch (Rama): Es una copia del código fuente, derivada del trunk u otro branch, en un determinado instante. Permite trabajar en paralelo sobre sí misma sin afectar el trunk o el branch de origen. Básicamente existen de dos tipos: los que se utilizan para introducir cambios, correcciones o mejoras significativas en nuestra aplicación, y los que se generan como producto del lanzamiento de un nuevo release.

Tag (Etiqueta): Permite identificar un cierto momento en nuestro ciclo de desarrollo que deseamos preservar para luego poder reconstruir nuestra aplicación tal cual estaba en dicho instante. En otras palabras, un tag es un snapshot. Generalmente se los crea para identificar y preservar un nuevo release o un punto de estabilidad ante futuros cambios.

Otro concepto muy usado actualmente en la gestión de nuestros repositorios de código fuente, especialmente en herramientas más modernas, es el concepto de fork (bifurcación), que es cuando se crea un nuevo proyecto a partir de otro replicando el código fuente en dicho instante, pero con una dirección distinta a la original. En los casos de los proyectos open source se suele dar con mayor frecuencia, especialmente cuando surgen diferencias entre los desarrolladores, o cuando se quiere partir del producto actual para realizar una versión independiente (como por ejemplo sucede con las distribuciones de Linux). En el ámbito de los proyectos de software propietario se suelen dar con menor frecuencia, por ejemplo, cuando se quiere llevar una versión de una aplicación a otro país y hay que aplicar cambios importantes, pero independientes a los del producto original.

Buenas prácticas en la organización del trunk, los branches y los tags

En lo que respecta a la interacción que los desarrolladores y arquitectos tenemos con las herramientas de SCM, quiero enfocarme en esta parte del artículo en algunas buenas prácticas y estrategias acerca de cómo podríamos organizar mejor nuestro repositorio para responder satisfactoriamente al desarrollo de actividades en paralelo, o tener disponibles múltiples versiones de un sistema en el mismo instante. Por ejemplo, para que puedan mantenerse y evolucionarse mientras dichas versiones que viven en los diferentes Stages Pre-productivos de nuestros clientes.

En lo que respecta al manejo del trunk, les propongo las siguientes buenas prácticas para aplicar en sus proyectos de desarrollo:

· Cada uno de nuestros proyectos deberá tener un único trunk como línea base de desarrollo. Si tenemos más de un trunk, entonces algo no estamos haciendo bien… o tenemos un fork y no lo estamos viendo, o son branches tratados como trunk.

· En el trunk no deberemos hacer cambios con un impacto alto, o larga duración, ya que en caso de tener que pensar en armar una entrega, la solución podría no compilar o no ser estable, afectando la calidad del producto final. Los grandes cambios requieren tiempo de estabilización y ejecutar pruebas para validar que todo siga funcionando correctamente, y esto no es algo que podamos hacer de un día para el otro.

· El trunk siempre debe compilar y pasar todas las pruebas unitarias, de integración y de regresión en todo momento. Para lograr este objetivo podemos apoyarnos en alguna herramienta de Integración Continua, como Jenkins por ejemplo, para que nos ayude a correr los tests.

· Si algún miembro del equipo rompe el trunk, es decir, que la solución deja de compilar, se lo suele penalizar con el castigo de tener que traer algo para desayunar al día siguiente… por ejemplo, unas ricas facturas. Esto se suele arreglar con el equipo al comenzar el proyecto, y es interesante ya que todos vamos a estar pendientes de que el trunk esté estable en todo momento, tanto para no tener que traer facturas para todo el equipo, como para cazar a la persona que lo rompió, exigirle el pago y disfrutar de un buen desayuno al día siguiente.

A la hora de gestionar y administrar los diferentes branches, vale la pena aclarar que existen tres grandes estrategias a considerar. Elegir entre una u otra dependerá en gran medida del tipo de proyecto, y de la fase de vida en la que se encuentre (no es lo mismo uno que recién arranca que otro que ya se encuentra operativo y evolucionando continuamente). Las tres estrategias que podemos adoptar son:

· The Never-Branch Strategy

o Generalmente se emplea en proyectos que recién están comenzando y aún no tienen una aplicación funcionando en un ambiente del cliente (productivo o pre-productivo).

o Los desarrolladores trabajan directamente sobre el trunk, lo cual a veces genera que el mismo deje de compilar y/o pasar las pruebas definidas.

· The Always-Branch Strategy

o En el otro extremo, se encuentra la estrategia de generar siempre un branch por cada nueva tarea de codificación que haya que realizar, no importa su complejidad, no importa su duración o esfuerzo. Solamente cuando la tarea se haya realizado completamente y esté correctamente validada, se pasarán los cambios al trunk.

o Se la suele utilizar en proyectos complejos, o en donde el equipo no ha participado en la fase primaria del desarrollo y existe el riesgo de que un cambio desestabilice el resto de la aplicación, o en proyectos en donde existe un management muy exigente.

· The Branch-When-Needed Strategy

o En el medio de las dos estrategias anteriores, se encuentra la estrategia de realizar un nuevo branch sólo si es necesario, generalmente para los cambios más complejos o con un alto impacto en el resto de la aplicación.

o Es la estrategia favorita para los proyectos que ya se encuentren operativos y sobre los cuales tengan que convivir diferentes versiones del mismo: la versión productiva, las versiones pre-productivas y las versiones futuras con nuevos desarrollos o modificaciones.

o Desde mi punto de vista, es la más sensata y la que suelo usar en mis proyectos a partir del primer release de la aplicación a un ambiente del cliente, independientemente de si vaya a producción o simplemente quede en un ambiente de QA previo.

Si optamos, entonces, por implementar las estrategias que poseen branches, es bueno que sepan que existen tres diferentes tipos de branches que podemos crear en el repositorio:

· Feature Branches: O también conocidos como Ramas de Soporte o Ramas de Funcionalidad. Son aquellos que se crean de forma puntual para desarrollar una funcionalidad específica; y una vez estabilizada se sincroniza con lo que está en el trunk.

· Hotfix Branches: Se utilizan en los casos en que tenemos que generar un hotfix para corregir un error de forma urgente, y que suponemos que va a tener un alto impacto en nuestro código y por eso no lo hacemos en el trunk.

· Release Branches: Se crean para poder dar soporte a la salida de una nueva versión de producción de la aplicación. Permiten tener bajo control los entregables de la versión y poder realizar el mantenimiento de estabilización y de corrección sobre ella.

Considerando los dos puntos anteriores, entonces paso a compartirles algunas buenas prácticas para que trabajemos correctamente con Branches sin morir en el intento:

· Idealmente, para cada tarea compleja, que tenga un alto impacto, que sea de larga duración, y/o que aún no sabemos cuándo debemos disponibilizarla al cliente, deberemos trabajarla en un branch aparte para no perjudicar la estabilidad del trunk y de nuestra aplicación en general.

· Integrar en el trunk una funcionalidad desarrollada en un branch únicamente cuando esté totalmente desarrollada y testeada. No hacer integraciones parciales, ya que esto sólo va a terminar afectando la calidad final del producto.

· Tener la funcionalidad desarrollada en branches aparte nos permite, más allá de tenerla completamente desarrollada y validada, la posibilidad de elegir cuándo deseamos integrarla al trunk para liberarla en un próximo release.

· Si vamos a experimentar con nuestra aplicación, por ejemplo realizando alguna PoC (Proof of Concept) probando un nuevo componente o validando un upgrade de algún framework, hagámoslo sí o sí en un branch aparte para no ensuciar el trunk.

· Por cada release que necesitemos preparar para entregar al cliente, creemos un branch aparte, de forma de poder trabajar tranquilamente sobre él, realizando la estabilización final del producto antes de realizar el delivery.

· Sobre los branches de release no se deberán realizar tareas como refactor, code cleaning, etc. Sólo estarán permitidas pequeñas correcciones en código, pero no grandes modificaciones. Esto es en pos de facilitar luego el merging de los fuentes con el trunk.

· Otra de las ventajas de tener branches de release es que nos van a permitir efectuar correcciones en paralelo al desarrollo que se encuentre en el trunk y liberar versiones intermedias antes del siguiente major release a producción.

· Dado que con el tiempo nuestro repositorio va a tender a llenarse de branches, es bueno mantenerlo organizado desde un comienzo. Una buena práctica consiste en agrupar los diferentes tipos de branches. Para ello, se podrán nombrar los diferentes branches agregando un prefijo que indique su tipo; o se podrán crear subcarpetas por cada uno de los tipos (por ejemplo, en TFS esto es posible y queda mucho más limpio). Otra recomendación es seguir una nomenclatura estandarizada y conocida por todo el equipo del proyecto.

Y por último, en lo que respecta al armado de los tags, les propongo seguir las buenas prácticas que enumeraremos a continuación, lo que les facilitará enormemente encontrar y bajarse un determinado hito de la aplicación en un futuro:

· Primero y antes que nada, sobre un tag no deberemos realizar jamás cambios y subirlos al repositorio. Un tag es una versión congelada de la aplicación y no deberá modificarse.

· Realizando una analogía, veamos al tag como un branch en el que los archivos correspondientes al código fuente no evolucionan, sino que permanecen congelados.

· Realizar un tag por cada release de la aplicación que se realice, independientemente de si se trate de un major release o de uno intermedio.

· Realizar un tag siempre que consideremos que vamos a necesitar recuperar el estado actual del repositorio en un futuro, por ejemplo, es útil si en un branch queremos implementar una mejora, pero no estamos seguros de si finalmente la vamos a conservar.

· Definitivamente, es una muy buena práctica crear un tag antes y después de realizar un merge entre diferentes branches o desde un branch al trunk.

· Definir una nomenclatura estándar y darla a conocer a todo el equipo para nominar los diferentes tags en nuestro repositorio.

Bueno, espero que les haya resultado interesante el artículo, que les sean útiles los tips anteriores y que puedan aplicarlos en el día a día en sus respectivos proyectos.

De la misma forma que les comentaba en artículos anteriores, no existe una solución mágica, ni una única forma de resolver las cosas. El manejo del repositorio SCM no es la excepción, y si queremos mantenerlo organizado y funcional, deberemos utilizar nuestro criterio para determinar cuál es la mejor estrategia de branching, y principalmente, cuándo branchear y cuándo no, dependiendo en gran medida del contexto del proyecto y de las tareas que tengamos que realizar.

Ing. Ariel Martín Bensussán

.Net Practice Manager