Como ya comentamos en Por qué siempre digo que la gestión de versiones es imprescindible. Una experiencia real ilustrativa o en ¿Qué es Jenkins? Explicado en menos de 10 min para quienes no lo conocen de nada, el uso del control de versiones y más concretamente que todo el equipo siga una correcta estrategia de versionado, es la base imprescindible de cualquier proyecto.
Pero muchas veces suelen surgir dudas sobre qué estrategia de control de versiones llevar a cabo en un equipo Scrum. ¿Cómo gestionamos el control de versiones cuando trabajamos por historias de usuario o tareas? ¿Dónde entra la integración continua? ¿Cómo podemos combinar historias de usuario, tareas, control de versiones e integración continua?
Lo cierto es que no hay un único enfoque para tratar todo esto. Lo que voy a hacer es contarte un poco las principales estrategias de control de versiones que hay, cómo combinarlas con el trabajo de Scrum y la integración continua y cuál creo que es la mejor.
Lo primero, es que en cuanto a técnicas de versionado se refiere, se distinguen dos alternativas: desarrollar en una única línea de control de versiones (desarrollo lineal) o en paralelo, utilizando varias ramas.
Desarrollo lineal en el control de versiones
Esto sería un desarrollo lineal, en una única línea del control de versiones. Todos los desarrolladores van subiendo las tareas que han terminado a la misma línea del control de versiones.
Uno de los principales problemas que tiene este enfoque, es que los errores se propagan más fácilmente. Como puedes ver en la figura anterior, Pepe introduce un error al principio, que luego consigue solucionar. No obstante, para cuando lo hace, tanto Ana como Javier han continuado desarrollando con el código antiguo, el que tenía el error, por lo que no podemos estar seguros de que esta línea principal tenga una versión estable del código, posible candidata para un paso a un entorno de pruebas, producción etc.
Ahora ambos tendrán que hacer un esfuerzo mayor para combinar la parte nueva de Ana y Javier, con la parte de Pepe sin el error.
Además, por este motivo, puede que los desarrolladores no suban su código al control de versiones muy a menudo, hasta que no sea estable, por temor a introducir errores y que se propaguen a los otros desarrolladores. Con ello, incumplimos la recomendación de que todo el código esté en el control de versiones e integrar frecuentemente.
Integración continua y desarrollo lineal en el control de versiones
Un enfoque para la integración continua aquí podría ser que cuando un desarrollador intente subir su código a esta única línea principal, el servidor de integración continua intente compilar su código con el que ya hay en la línea principal y ejecutar los tests. Si todo es un éxito, el código se subirá correctamente al control de versiones, y si no, el desarrollador tendrá que solucionar los errores.
¿Qué pasa con este enfoque? Además de los problemas mencionados antes, ¿dónde se queda el código mientras que el servidor de integración continua está compilándolo y pasando los tests? Hasta que el servidor no termina, no se sube al control de versiones, por lo que puede que pasemos un tiempo sin hacer nada hasta recibir si todo ha sido un éxito o no.
Por ello, un enfoque que podría funcionar mejor es…
Desarrollo paralelo, o a través de ramas
Podemos cambiar el desarrollo lineal por un enfoque de una historia de usuario o tarea por rama, es decir, creamos una rama cada vez que vamos a desarrollar una tarea, una historia de usuario o solucionar un error.
Cuando terminemos de implementar dicha tarea, el código esté probado y sea estable, procederemos a integrarlo en la rama principal o línea de desarrollo principal.
Si en nuestro proyecto trabajan varios equipos, por ejemplo front-end y desarrollo, crearemos una rama para front-end y otra rama para desarrollo y de cada una de ellas saldrán ramas para cada una de las tareas de los equipos.
De esta forma en la rama de desarrollo, tendremos el código estable e integrado de las tareas de desarrollo, y lo mismo en la rama de front-end con sus tareas correspondientes. Por último, en la rama principal, integraremos el código de desarrollo y de front-end, teniendo la garantía de que es una versión estable.
Estas ramas tienen que tener nombres descriptivos, como por ejemplo el nombre de la tarea, historia de usuario o el id del bug. Así mejoramos la trazabilidad del código, y se establece claramente qué propósito tiene cada rama.
Pero también tiene más ventajas:
1 – Cada desarrollador puede subir su código al control de versiones (a la rama que creó para desarrollar su tarea) sin temor a estropear el código de la línea principal de desarrollo.
Da igual que la tarea no esté terminada, o tenga errores, su código estará a salvo en el control de versiones, para posteriormente ser perfeccionado, sin afectar a nadie más.
2 – Además, reducimos la propagación de los errores. Si un desarrollador introduce un error en su código, que está en una rama separada, y no se da cuenta, se puede solucionar antes de integrarlo en la rama principal, sin afectar a posteriores desarrollos.
3 – De esta forma también, garantizamos que el código que está en la línea principal de desarrollo sea estable. Todas las ramas/tareas que se desarrollen a partir de la rama principal, partirán de un punto estable, sin errores.
Por lo tanto, si al integrar una rama con la principal el código falla, acotaremos el momento y lugar donde se ha producido el error. Sabremos que hemos sido nosotros quienes hemos introducido el fallo.
4 – En relación con lo anterior, también aseguramos que las tareas son independientes unas de otras. Nuestro código parte de algo estable, y no depende de los fallos que la implementación de otras tareas haya introducido en el software final.
Integración continua y desarrollo paralelo en el control de versiones
En este enfoque, una alternativa es que el servidor de integración continua sea el que cuando terminemos una tarea o historia de usuario, combine la rama de esa tarea con la rama principal, y compruebe que pasan los tests.
Esa versión estable integrada puede etiquetarse como candidata estable para pasar al entorno de testing o producción.
Pero también podrían ser los propios desarrolladores los que se encarguen de pasar los tests y combinar la rama de la tarea con la línea principal, y el servidor de integración continua sea el que etiquete la versión estable como candidata para pasar a testing o producción.
Terminando…
Lo más importante de todo esto, es que te quedes con la idea de que existen otras alternativas a que todo el mundo suba el código a la misma línea del control de versiones.
Como ya hemos visto, perder el miedo a desarrollar en distintas ramas puede aportarnos muchas ventajas, por eso es recomendable que le eches un vistazo a esta estrategia. La elección de crear una rama para cada historia de usuario o una rama para cada tarea dependerá del equipo.
Para más información te dejo estos enlaces:
– Introduction to Task-Driven Development. Pablo, y en general a gente de Plastic Scm saben muchísimo de este tema, y es de donde he sacado una gran inspiración para hacer este post.
– A successful Git branching model
- Diario: cómo Javier Garzás evita quedarse obsoleto estudiando a un X10 con IA-Esteroides - 7 noviembre, 2024
- Si creas Historias de Usuario con IA ¿A quién pertenecen? ¿A ti o la IA? El mono Naruto te lo explica - 31 octubre, 2024
- HistorIAs de usuario y como a Maximiliano lo ENGAÑABAN con la IA y como una viejuna historia del 1500 le salvó - 24 octubre, 2024
Algo parecido documenté yo hace un tiempo como parte del Plan de Gestión de la Configuración.
Puedes echarle un vistazo en http://manumontero.com/varios/Uso_de_Control_de_Versiones.docx
Lo que sí está claro es que hace falta una estrategia en ateria de control de versiones para evitar el caos y la ira del cliente :)) Además de preservar la salud del responsable del desarrollo.
Después hay que revisar que los componentes del equipo están siguiendo los procedimientos…
Dos palabras: git flow 😉
Vaya qué pena que llego tarde a este artículo!
No estoy muy de acuerdo con lo expuesto. En líneas generales se está recomendando una estrategía de control de versiones por ramas para la metodología Scrum. Se buscan ejemplos para que la estrategia lineal quede mal (algunos muy tontos) y se buscan ejemplos para que la estrategia por ramas quede bien. Pero estos últimos y las ventajas que se citan van totalmente en contra de la integración continua (la integración continua no es Hudson/Jenkins sino la práctica de integrar tu trabajo con el del equipo de forma continua y regular, varias veces al día propone Martin Fowler).
En ningún momento se habla del coste (por no llamarlo trauma) de hacer merges de ramas. Esto cualquiera que lo haya vivido sabe lo duro que puede llegar a ser, incluso arriesgado de cara a introducir bugs. No nos engañemos, los tests no lo cubren todo (ni con una cobertura del 100%).
Tampoco se explica qué ramas hacer ni en base a qué. Se comenta por un lado dividir entre front-end y desarrollo y más adelante se da a entender que cada desarrollador tendría su propia rama, supongo que se refiere a una historia por desarrollador y entonces una rama por historia.
Por otro lado, el hecho de que sea Scrum no parece que se tenga muy en cuenta. Un desarrollo que utilice Scrum lo normal será que saquen versiones del software cada 2 semanas(ó 1 ó 3), con reuniones diarias donde los miembros se ponen al día. De nuevo dónde queda la integración continua. Y habría que ver cómo afecta el sobrecoste de los merges a realizar en los últimos días del sprint para poder sacar la versión.
Como sé que he llegado tarde no me extenderé más aunque me encantaría tener un debate sobre el tema con personas que realmente hayan usado una estrategia por ramas en Scrum con equipos de 4+ personas y sprints de 2 semanas max.
En mi opinión, si el equipo está ubicado en la misma sala y sigue una metodologia iterativa con versiones cada 1-3 semanas siempre una estrategía líneal, usando ramas para excepciones bien justificadas.
Si el equipo está distribuido en varios centros de trabajo entonces estrategia por ramas y ya habría que meterse a ver qué ramas se hacen.
Esto se pone interesante.
Hay varios puntos que comenta @Julio que son clave y que creo que es necesario aclarar:
1- Los merges no son consecuencia de hacer ramas, son consecuencia de trabajar en paralelo. Me refiero, por supuesto, a merges con conflictos que puedan llegar a ser complicados, los que no tengan conflictos son triviales y no merece la pena darles muchas vueltas.
Es decir, si trabajas en paralelo, con más de una persona tocando el mismo fichero, los conflictos ocurrirán igual uses o no uses ramas. Igual de complicado es resolver un merge antes de un checkin para sincronizarte con trunk que después cuando quieras integrar una «task branch».
2- Julio resalta un punto que para mi es clave: «No nos engañemos, los tests no lo cubren todo (ni con una cobertura del 100%)». Efectivamente, y precisamente por eso usar task branches evita las tan temidas ‘broken builds’. He explicado esto muchas veces, pero la verdad es que todo está en este artículo sobre branch per task.
La integración continua es un gran avance, pero muchos de sus practicantes beben a grandes sorbos de Subversion y tienen una visión tremendamente distorsionada de lo que es el control de versiones. En el famoso libro de Paul Duvall Continuous Integration (ahora ya un poco más anticuado), al final del todo, introduce todo un mecanismo para lograr que cada ‘commit’ se pruebe antes de llegar a la mainline, y para eso habla de un sistema de 2-phase commit… ¡¡dios mío!! ¿¿Por qué reinventar la rueda otra vez?? Eso es una rama. Creas una rama, la pruebas, y cuando pasa las pruebas la integras.
Sólo hay una razón para no hacer eso: que tu control de versiones sea una patata haciendo merges… CVS, SVN, VSS y muchos otros entran en esa categoría de ‘patatas’ pero afortunadamente Git y nuestro Plastic superan eso. ClearCase hace décadas ya hacía eso mejor que SVN tb, pero a un precio.
Y no es solamente cuestión de la merge tool que uses, es de cómo calcule la trazabilidad el sistema, de si es merge de 2 o 3 vías, como expliqué en un artículo de DDJ hace poco.
Esa capacidad de hacer buenos merges y buenas ramas es uno de los factores del éxito de los distributed version controls (mucho más incluso que el hecho de ser distributed).
Y por último, la tecnología de merge avanza sin parar, para hacer que esos ‘merges de pesadilla’ se conviertan en triviales. Como ejemplo: The state of the art in merge tech que combina lo último de merge machine de Plastic on nuestro SemanticMerge.
Julio, no te preocupes que no llegas tarde 😉
Hay distintas estrategias, y en función de tu equipo tendrás que elegir o readaptar la tuya.
Como bien comentó Miguel, git flow se suele usar bastante en Scrum (http://nvie.com/posts/a-successful-git-branching-model/)
Por ejemplo, en uno de los proyectos en los que estoy trabajando ahora hay varios equipos de desarrollo, se usa Scrum, iteraciones de 1 semana y se ha pasado de un desarrollo lineal en una única rama, a desarrollar historia de usuario en cada rama.
Luego a la hora de la release, se saca una rama para hacar bugfixing. Estamos todavía adaptándolo y no descartamos sacar otras ramas con funciones bien definidas, pero la base es rama por tarea.
Cabe mencionar que las veces que he usado estrategias de este tipo ha sido con Git o Mercurial.
En general, se ha notado la diferencia. Los desarrolladores integran más frecuentemente, se detectan errores antes y se sabe realmente qué historias de usuario pasan a qa, pre o producción.
La gente tenía miedo a hacer ramas, pero en cuanto a merges no ha habido muchos más problemas que con el desarrollo lineal y como he dicho, al final nos ha venido mejor.
Como bien dices Julio (y dudo que yo haya dicho lo contrario), integración continua no es Jenkins. Integración continua es integrar frecuentemente el código.
Personalmente creo que al desarrollar cada historia de usuario en una rama favoreces las integraciones frecuentes: primero los desarrolladores pueden subir su código al control de versiones sin temor a afectar a los otros desarrolladores (aunque el código todavía no esté perfecto) y segundo, integras frecuentemente pequeños fragmentos de código (al terminar la historia de usuario, juntas la rama de la historia con la principal, asegurándote de que se pasen las pruebas y demás). Integras tu código con el del resto (fragmentos de código pequeños en comparación con las integraciones de 3 meses que te puedes encontrar por ahí), cada poco tiempo. No hablo de hacer integraciones ni mergeos al final del sprint, sino al terminar cada historia.
Cuanto más frecuentemente se integre, en pasos más pequeños mejor, menos costosos serán los mergeos y se detectarán fallos de integración antes (lo que dice la integración continua).
Yo te comento lo que mejor nos ha ido funcionando. Repito que cada uno puede tener la estrategia que quiera.
Lo creo que es un error, es no querer hacer ramas por temor a los merges. En el comentario de Pablo está todo muy bien justificado 🙂
Llego aquí a través del twitter de Pablo. Es un debate siempre interesante en el que suele faltar mucha información. Los puntos que comenta el artículo, que intenta rebatir Julio y las ideas de Pablo me parecen correctas. En mi experiencia con diferentes workflows solo puedo decir una cosa: depende de cada equipo, proyecto y requerimientos.
Algunos apuntes de por qué en mi equipo (engineering.plumbee.com) utilizamos git con push directos al master. Hemos pasado mucho tiempo debatiendo y probando estrategias, algunas de las razones son estas:
– Como dice Pablo, los conflictos aparecen continuamente, trabajes en ramas o no. Al trabajar en master aumentamos el número de merges por hora, reduciendo significativamente los conflictos. Somos 30 ingenieros, en general un equipo bastante senior. A pesar de tener grabado a fuego que branch por feature es solo factible con short-lived branches, mantener la disciplina era muy complicado.
– «keep the main branch pristine»: por nuestro tipo de proyectos y flujo de trabajo no nos da ningún valor. Desplegamos varias veces al día, y hace meses que no hacemos un rollback (y creo que ahora mismo no sabríamos ni como hacerlo). Nuestro codebase es de varias decenas de módulos con nuestros árboles de dependencias siempre actualizándose a la última versión. El histórico de cambios sólo nos sirve de referencia.
– El build se rompe. Para eso está, no entiendo que deba de ser temido. Lo único importante es que la bocina suene lo antes posible.
– En general al trabajar con muchos módulos pequeños interdependientes el sistema de build se complica. Cuando se hacen los release build antes de integrar al master, la resolución de artefactos es muy difícil, el sistema tiene que tener en cuenta que debe usar los artefactos producidos en ese changeset exclusivamente. Se puede argumentar que es mejor integrar los módulos 1 a 1, pero el desarrollador tendría que estar encima del proceso, y puede llevar horas.
Podría dar muchas razones por las que es conveniente utilizar modelos con ramas, pero creo que ya lo hacéis bien vosotros 😉
En realidad sólo quiero dejar claro que no hay absolutos. Que Scrum no tiene nada que ver con el control de versiones, y que si nosotros (o Facebook, o Google) trabajamos más rápido con push directos al master no es por ignorancia, son decisiones fuertemente valoradas.
Hola de nuevo! Vaya cómo se ha movido esto. Disculpadme porque no puse la alarma y no me he enterado hasta hoy que me ha dado por ver si alguien había contestado…
Y resulta que me he encontrado lecciones y experiencias muy interesantes sobre control de versiones! Muchas gracias por vuestras respuestas. Antes de que se me olvide me encantaría añadiros en Twitter pero sólo he encontrado a Pablo.
Volviendo al hilo, me quedo con la conclusión de Miguel de que no hay una estrategia ideal para todos los casos, ni siquiera para todos los proyectos Scrum. Por eso me chocó tanto la aparente recomendación de usar una estrategia por ramas del artículo.
Tomo buena nota de las ventajas de los nuevos SCMs, en especial en la resolución de conflictos, comentadas por Pablo. Reconozco que nosotros seguimos con SVN y aunque algo ha mejorado respecto al pasado sigue siendo un poco tontito, por no hablar del tratamiento de los renames de ficheros.
Pero aunque como bien dice Pablo, el problema no son los merges sino los conflictos que conllevan, no es lo mismo tratar con unos pocos conflictos cada pocas horas, poco a poco, y teniendo a tu lado al compañero para ayudar a solucionarlo que encontrarte 2-3 días después con un gran número de ellos. Nosotros no hacemos pair-programming pero sí que nos consultamos frecuentemente muchas decisiones de diseño, desde estrategias de implementación a nombres de clases o métodos.
Aunque en el enlace de Pablo habla de que los branches se hacen sobre tareas y que las tareas deben ser de duración muy corta, dice que como si fueran bugs. ¿Esto es así en vuestro caso? ¿Dividís las historias en tareas de menos de 1 día y hacéis un branch para cada tarea?
La verdad es que me encantaría ver cómo se trabaja así en un proyecto real porque intento imaginarmelo y de verdad que veo un importante sobrecoste a la hora de hacer los merges.
Porque si las tareas son de varios días y sólo trabaja un desarrollador en cada una se pierde integración continua, con el riesgo del integration hell que es como yo me imagino que puede ocurrir cuando 3 desarrolladores tienen que hacer merge de 3 tareas a la vez después de llevar varios días trabajando por su cuenta.
Comenta también Ana que lo bueno de que cada desarrollador programe en su rama es que si sube código malo como no lo hace en el trunk/master no afecta a sus compañeros. Pero, de nuevo, esto es integración continua, aunque sea a partir de un broken build. Y si se tiene que romper, mejor antes que después. Aquí vuelvo a coincidir con Miguel.
Insisto, muchas gracias por vuestros comentarios. Aunque no lo creáis me habéis despertado el interés por probar con ramas a este nivel (nosotros ya las usamos para versiones paralelas o bugs en producción cuando tenemos la siguiente versión a medias).
Muchas gracias voy iniciando con Integración continua y el debate estuvo genial, no puedo opinar mucho al respecto porque voy despegando pero pronto daré mis opiniones gracias a todos 🙂
Hola:
No soy experto en estos temas, pero a raíz de leer el artículo y ver los comentarios, creo que lo más beneficioso de usar ramas para Scrum es que SÓLO las historias de usuario que están completas deberían estar en el producto al final del Sprint. No sirve un botón que pulsas y no hace nada, porque si se decide que ya no es prioritario hay que dedicar esfuerzo a quitarlo; cuando lo fácil hubiera sido no ponerlo.
Otro ejemplo son tareas que se han quedado bloqueadas y no se terminan por la razón que sea. No se pueden quedar a medias en el resultado final…
Saludos,
Primero que todo quiero agradecer a cada uno de los participantes en esta discusión por los comentarios que hacen los cuales adicionan valor a este artículo.
De acuerdo a mi experiencia de trabajo en diferentes equipos de desarrollo con metodologías ágiles y utilizando Git, considero que la estrategia de ramas es la mejor en trabajo paralelo y depende obviamente de qué tan grande sea el equipo de desarrollo, la madurez del mismo, de las herramientas con que cuente y del conocimiento para poder llevar a cabo un ciclo completo de desarrollo que vaya acorde a las necesidades de su organización utilizando buenas prácticas.
La integración continua hoy en día cuenta con muchas herramientas que permiten hacer dicha labor de una forma más simple como lo mencionaban, podes tener un Jenkins o un Semaphore o un Travis y muchas otras más, todo depende de la evaluación y beneficios que le encuentres a la herramienta seleccionada. También dependiendo del lenguaje en el que desarrolles puedes encontrar herramientas que te permitan hacer todo en forma local haciendo deployment en los servers donde se continua el workflow del ciclo de desarrollo.
Generalmente en la Estrategia por ramas se crea la específica para el issue a tratar, staging para QA y production que es la que llevaría a Master, muchas veces incluso se puede pensar en un clon de master que se mantiene copia y se actualiza en forma semanal y esto se convierte en tan solo otra estrategia contra posibles errores humanos.
Me parece importante resaltar lo que mencionaban acerca de la rapidez de encontrar los errores y es allí donde la integración continua tiene esa gran ventaja y es de permitir saber cuando cambias algo en tu código si eso afecta tus pruebas unitarias y demás, de ahí la relevancia de introducir buenas prácticas y con esto servicios que te permitan saber si tu código está limpio(Rubocop, Codeclimate) y sigue unas buenas prácticas, si tus pruebas pasan(Semaphore, Guard), si tus servidores están funcionando en los diferentes ambientes en forma adecuada(NewRelic), darle tratamientos a las excepciones y bugs con servicios especializados(AirBrake) y que te permiten monitorear estos sucesos con mayor rapidez, entre otros.
Otro punto mencionado importante la definición de directrices para el equipo de desarrollo donde se definen las convenciones que deberá seguir el el equipo de desarrollo y que beneficia al seguimiento, mantenimiento y legibilidad de tu código.
Estoy convencido que cada caso como lo comentaban puede ser muy distinto pero todo depende de las variables que inicialmente expuse, ya que cada equipo tratará de buscar la mejor estrategia para concebir su ciclo de desarrollo y aplicarlo de la forma más idónea de acuerdo a su necesidad,
Saludos!.
Muchas gracias por las herramientas que mencionas, no las conocía.
En cuanto a la estrategia de ramas estoy de acuerdo con lo que mencionas al principio, depende de la experiencia del equipo en cuestión.
Saludos.