Para darle un poco de historia: trabajo para una empresa con aproximadamente doce desarrolladores de Ruby on Rails (pasantes +/-). El trabajo remoto es común. Nuestro producto está hecho de dos partes: un núcleo bastante grueso y delgado para proyectos de grandes clientes basados en él. Los proyectos de los clientes suelen ampliar el núcleo. La sobrescritura de características clave no ocurre. Podría agregar que el núcleo tiene algunas partes bastante malas que necesitan urgentemente refactorizaciones. Hay especificaciones, pero principalmente para los proyectos del cliente. La peor parte del núcleo no se ha probado (no como debería ser ...).
Los desarrolladores se dividen en dos equipos, trabajando con uno o dos PO para cada sprint. Por lo general, un proyecto de cliente está estrictamente asociado con uno de los equipos y OP.
Ahora nuestro problema: con bastante frecuencia, rompemos las cosas del otro. Alguien del Equipo A expande o refactoriza la característica central Y, causando errores inesperados para uno de los proyectos de clientes del Equipo B. En su mayoría, los cambios no se anuncian en los equipos, por lo que los errores casi siempre son inesperados. El equipo B, incluido el PO, pensó que la característica Y era estable y no la probó antes de su lanzamiento, sin darse cuenta de los cambios.
¿Cómo deshacerse de esos problemas? ¿Qué tipo de 'técnica de anuncio' me puede recomendar?
Respuestas:
Recomendaría leer Trabajar eficazmente con código heredado de Michael C. Feathers . Explica que realmente necesita pruebas automatizadas, cómo puede agregarlas fácilmente, si aún no las tiene, y qué "código huele" para refactorizar de qué manera.
Además de eso, otro problema central en su situación parece ser la falta de comunicación entre los dos equipos. ¿Qué tan grandes son estos equipos? ¿Están trabajando en diferentes atrasos?
Casi siempre es una mala práctica dividir los equipos de acuerdo con su arquitectura. Por ejemplo, un equipo central y un equipo no central. En cambio, crearía equipos en un dominio funcional, pero de componentes cruzados.
fuente
Este es el problema. La refactorización eficiente depende en gran medida del conjunto de pruebas automatizadas. Si no los tiene, los problemas que está describiendo comienzan a aparecer. Esto es especialmente importante si usa un lenguaje dinámico como Ruby, donde no hay un compilador para detectar errores básicos relacionados con pasar parámetros a métodos.
fuente
Las respuestas anteriores que apuntan a mejores pruebas unitarias son buenas, pero creo que puede haber problemas más fundamentales que abordar. Necesita interfaces claras para acceder al código central desde el código para los proyectos del cliente. De esta manera, si refactoriza el código central sin alterar el comportamiento observado a través de las interfaces , el código del otro equipo no se romperá. Esto hará que sea mucho más fácil saber qué se puede refactorizar de forma "segura" y qué necesita un rediseño, posiblemente una ruptura de la interfaz.
fuente
Otras respuestas han resaltado puntos importantes (más pruebas unitarias, equipos de características, interfaces limpias para los componentes principales), pero falta un punto, que es el control de versiones.
Si congela el comportamiento de su núcleo haciendo una versión 1 y coloca esa versión en un sistema privado de administración de artefactos 2 , entonces cualquier proyecto del cliente puede declarar su dependencia de la versión principal X , y no se romperá en la próxima versión X + 1 .
La "política de anuncios" se reduce a tener un archivo CAMBIOS junto con cada versión, o tener una reunión de equipo para anunciar todas las características de cada nueva versión principal.
Además, creo que necesita definir mejor qué es "núcleo" y qué subconjunto de eso es "clave". Parece que (correctamente) evita hacer muchos cambios en los "componentes clave", pero permite cambios frecuentes en el "núcleo". Para confiar en algo, debes mantenerlo estable; Si algo no es estable, no lo llame núcleo. ¿Tal vez podría sugerir llamarlo componentes "auxiliares"?
EDITAR : si sigue las convenciones en el sistema de versiones semánticas , cualquier cambio incompatible en la API del núcleo debe estar marcado por un cambio de versión importante . Es decir, cuando cambia el comportamiento del núcleo previamente existente o elimina algo, no solo agrega algo nuevo. Con esa convención, los desarrolladores saben que la actualización de la versión '1.1' a '1.2' es segura, pero pasar de '1.X' a '2.0' es arriesgado y debe revisarse cuidadosamente.
1: Creo que esto se llama gema, en el mundo de Ruby
2: el equivalente a Nexus en Java o PyPI en Python
fuente
Como dijeron otras personas, un buen conjunto de pruebas unitarias no resolverá su problema: tendrá problemas al fusionar los cambios, incluso si cada conjunto de pruebas de equipo pasa.
Lo mismo para TDD. No veo cómo puede resolver esto.
Su solución no es técnica. Debe definir claramente los límites "centrales" y asignar un rol de "perro guardián" a alguien, ya sea el desarrollador principal o el arquitecto. Cualquier cambio en el núcleo debe pasar por este perro guardián. Es responsable de asegurarse de que cada salida de todos los equipos se fusionará sin demasiados daños colaterales.
fuente
Como una solución a más largo plazo, también necesita una comunicación mejor y más oportuna entre los equipos. Cada uno de los equipos que alguna vez utilizarán, por ejemplo, la función central Y, deben participar en la construcción de los casos de prueba planificados para la función. Esta planificación, en sí misma, resaltará los diferentes casos de uso inherentes a la característica Y entre los dos equipos. Una vez que se define cómo debería funcionar la función y se implementan y acuerdan los casos de prueba, se requiere un cambio adicional en su esquema de implementación. El equipo que lanza la función es necesario para ejecutar el caso de prueba, no el equipo que está a punto de usarlo. La tarea, si la hay, que debería causar colisiones, es la adición de un nuevo caso de prueba de cualquiera de los equipos. Cuando un miembro del equipo piensa en un nuevo aspecto de la función que no se prueba, deberían tener la libertad de agregar un caso de prueba que hayan verificado que pasa en su propia caja de arena. De esta manera, las únicas colisiones que ocurrirán serán en el nivel de intención, y deben ser clavadas antes de que la característica refactorizada se libere en la naturaleza.
fuente
Si bien cada sistema necesita conjuntos de pruebas efectivos (lo que significa, entre otras cosas, automatización), y si bien estas pruebas, si se usan de manera efectiva, detectarán estos conflictos antes de lo que son ahora, esto no resuelve los problemas subyacentes.
La pregunta revela al menos dos problemas subyacentes: la práctica de modificar el 'núcleo' para satisfacer los requisitos de los clientes individuales, y la falla de los equipos para comunicarse y coordinar su intención de hacer cambios. Ninguna de estas son causas fundamentales, y necesitará comprender por qué se está haciendo esto antes de poder solucionarlo.
Una de las primeras cosas por determinar es si tanto los desarrolladores como los gerentes se dan cuenta de que hay un problema aquí. Si al menos algunos lo hacen, entonces debe averiguar por qué piensan que no pueden hacer nada al respecto, o eligen no hacerlo. Para aquellos que no lo hacen, puede intentar aumentar su capacidad para anticipar cómo sus acciones actuales pueden crear problemas futuros, o reemplazarlos con personas que puedan hacerlo. Hasta que tenga una fuerza de trabajo que sepa cómo van las cosas mal, es poco probable que pueda solucionar el problema (y tal vez ni siquiera entonces, al menos a corto plazo).
Puede ser difícil analizar el problema en términos abstractos, al menos inicialmente, así que concéntrese en un incidente específico que haya resultado en un problema e intente determinar cómo sucedió. Como es probable que las personas involucradas se pongan a la defensiva, deberá estar atento a las justificaciones egoístas y post-hoc para descubrir lo que realmente está sucediendo.
Hay una posibilidad que dudo en mencionar porque es muy poco probable: los requisitos de los clientes son tan dispares que no hay suficientes elementos en común para justificar el código central compartido. Si esto es así, entonces tiene múltiples productos separados, y debe administrarlos como tales, y no crear un acoplamiento artificial entre ellos.
fuente
Todos sabemos que las pruebas unitarias son el camino a seguir. Pero también sabemos que es realmente difícil adaptarlos de manera realista a un núcleo.
Una técnica específica que puede ser útil para usted al extender la funcionalidad es intentar verificar de manera temporal y local que la funcionalidad existente no ha cambiado. Esto se puede hacer así:
Pseudocódigo original:
Código de prueba temporal en el lugar:
Ejecute esta versión a través de las pruebas de nivel de sistema que existan. Si todo está bien, sabe que no ha roto las cosas y puede proceder a eliminar el código anterior. Tenga en cuenta que cuando verifica la coincidencia de resultados antiguos y nuevos, también puede agregar código para analizar diferencias para capturar casos que sabe que deberían ser diferentes debido a un cambio previsto, como una corrección de errores.
fuente
"Principalmente, los cambios no se anuncian en los equipos, por lo que los errores casi siempre son inesperados" ¿
Algún problema de comunicación? ¿Qué pasa con (además de lo que todos los demás ya han señalado, que debe realizar pruebas rigurosas) para asegurarse de que haya una comunicación adecuada? ¿Que las personas sean conscientes de que la interfaz en la que escriben va a cambiar en la próxima versión y cuáles serán esos cambios?
Y déles acceso a al menos una interacción ficticia (con implementación vacía) lo antes posible durante el desarrollo para que puedan comenzar a escribir su propio código.
Sin todo eso, las pruebas unitarias no harán mucho, excepto señalar durante las etapas finales que hay algo fuera de control entre las partes del sistema. Quiere saber eso, pero quiere saberlo temprano, muy temprano, y hacer que los equipos hablen entre sí, coordinen esfuerzos y tengan acceso frecuente al trabajo que el otro equipo está haciendo (por lo que se compromete regularmente, no uno masivo comprometerse después de varias semanas o meses, 1-2 días antes del parto).
Su error NO está en el código, ciertamente no en el código del otro equipo que no sabía que estaba jugando con la interfaz en la que están escribiendo. Su error está en su proceso de desarrollo, la falta de comunicación y colaboración entre las personas. El hecho de que estés sentado en habitaciones diferentes no significa que debas aislarte de los otros chicos.
fuente
Principalmente, tiene un problema de comunicación (probablemente también relacionado con un problema de trabajo en equipo ), por lo que creo que una solución a su caso debería centrarse en ... bueno, la comunicación, en lugar de las técnicas de desarrollo.
Doy por sentado que no es posible congelar o bifurcar el módulo principal al iniciar un proyecto de cliente (de lo contrario, simplemente debe integrar en su empresa los cronogramas de algunos proyectos no relacionados con el cliente que apuntan a actualizar el módulo central).
Así que nos queda la cuestión de tratar de mejorar la comunicación entre los equipos. Esto se puede abordar de dos maneras:
Puede encontrar más información sobre CI como proceso de comunicación aquí .
Finalmente, todavía tiene un problema con la falta de trabajo en equipo a nivel de la empresa. No soy un gran admirador de los eventos de team building, pero este parece ser un caso en el que serían útiles. ¿Tiene reuniones periódicas para desarrolladores? ¿Puedes invitar a personas de otros equipos a las retrospectivas de tu proyecto? ¿O tal vez tomar cerveza el viernes por la noche a veces?
fuente