Skip to main content

Actualizaciones continuas de dependencias: mejora los procesos al abordar primero el trabajo difícil

Escrito por
Headshot of James Bowes

James Bowes

24 de mayo de 2021

0 minutos de lectura

Esta es una historia sobre anticipar el trabajo difícil, pedir perdón en lugar de permiso y mejorar continuamente. En los primeros días de Manifold, mucho antes de que nos uniéramos a Snyk, estábamos creando un marketplace independiente para servicios de desarrollo, como bases de datos o servicios de correo electrónico transaccional. La estructura de nuestro código era típica: teníamos una aplicación frontend en React y una colección de microservicios en Go que se comunicaban con una base de datos. Una estructura típica también implicaba problemas típicos. Uno de ellos era mantener nuestras dependencias actualizadas. Como miembro sénior del equipo y alguien que llevaba suficiente tiempo en la empresa como para haber causado muchos de nuestros problemas, me propuse resolver los problemas con las dependencias, con la ayuda de algunas personas dispuestas a colaborar.

«¡Actualizar dependencias requiere mucho trabajo!»

A lo largo de los años, con distintos lenguajes y frameworks, las quejas sobre la actualización de dependencias son constantes. A menos que necesites una actualización para crear una nueva funcionalidad o corregir un nuevo error (y muchas veces incluso en esos casos), el trabajo es tedioso y ofrece poca recompensa. ¿Cómo sabes siquiera que hay una actualización? ¿Los cambios te sirven o impedirán la compilación o introducirán errores? ¿Cuántos conflictos tendrá tu cambio con otros cambios al actualizar las firmas de funciones en innumerables archivos?

Si el trabajo es tedioso y la recompensa es pequeña, es natural postergarlo o ignorarlo por completo.

«Nos tomará dos sprints actualizar nuestro router de React».

Con el tiempo, te encontrarás en una situación propia de un cuento infantil. Postergaste la actualización de dependencias durante todo el verano, y ahora llegó el invierno y necesitas una pequeña actualización del paquete grasshopper-food, pero su propio árbol de actualizaciones de dependencias entra tanto en conflicto con tu base de código que no sabes cuánto tiempo te tomará.

En Manifold, el caso más evidente fue actualizar las versiones del router que usábamos con React. No manteníamos actualizadas las dependencias de nuestra base de código frontend. Cuando quisimos pasar a una nueva versión principal de una biblioteca de router, los pequeños cambios en las firmas de la API se enredaron con innumerables conflictos e incompatibilidades de otras dependencias. Otros paquetes se habían actualizado varias veces desde que empezamos a usarlos y desde nuestro primer intento de hacer la actualización. Desenredar las otras dependencias de la actualización del router fue laborioso, sobre todo al intentar encontrar versiones incrementales más pequeñas para actualizar, con el objetivo de dividir el trabajo.

Al principio, la actualización parecía demasiado laboriosa, así que volvimos a postergarla. Cuanto más esperábamos sin cambiar nuestros hábitos, más se agravaba el problema a medida que se actualizaban más dependencias. Y cuanto más esperábamos, más tiempo nos tomaría actualizar la versión del router.

«Anticipa el trabajo difícil».

Actualizar dependencias es difícil, pero es una tarea ideal para «anticipar el trabajo difícil»: actualizarlas con más frecuencia y de forma explícita. Las dependencias se actualizan con el tiempo, así que cuanto más a menudo las actualices, más pequeños serán los cambios y menor será el esfuerzo total. (En cierto modo, esto es integración continua clásica). Los cambios más pequeños ayudan a definir mejor el problema: en lugar de decir «actualizar dependencias es difícil», ahora puedes decir «para actualizar el paquete X a la versión Y, hay que hacer cambios en estos 12 archivos». Enfrentarte al trabajo tedioso que queda te llevará a reducirlo mediante la automatización u otros medios.

Activamos las actualizaciones automatizadas de dependencias en todos nuestros repositorios de Manifold. Con todas las herramientas disponibles (como Snyk), implementar este cambio no fue difícil; una sola persona podía hacerlo por su cuenta y generar un impacto significativo. Podíamos hacer este cambio sin «pedir permiso» a los responsables de los repositorios, sino «pedir perdón» después: no hacía falta llegar a un consenso ni tomar una decisión formal. Quien implementaba el cambio sopesó el costo de hacerlo (poco esfuerzo) frente al impacto positivo que podía tener (alto) y la dificultad de revertirlo (baja: bastaba con desactivar la opción).

Quien implementó el cambio lo hizo en varios de nuestros repositorios y les explicó a los demás desarrolladores qué había hecho, por qué y cuáles serían los beneficios. Después, invitó a cualquiera que quisiera revertir el cambio a pedir ayuda o hacerlo por su cuenta si no quería recibir actualizaciones de dependencias.

Poco a poco, otros desarrolladores habilitaron las actualizaciones automatizadas de dependencias en el resto de nuestros repositorios y, día a día, empezaron a llegar las actualizaciones.

«Estamos dedicando demasiado tiempo a revisar estos pull requests automatizados de dependencias».

Al principio, puede parecer extraño que algo que antes ocurría con poca frecuencia y resultaba desestabilizador y aterrador suceda a diario. Con el tiempo, todos se acostumbran y se sienten cómodos con que el problema original (actualizar dependencias) ya no sea un problema. Cuando llegas a este punto, cambia la percepción y el siguiente aspecto más difícil de actualizar dependencias se convierte en el nuevo gran problema.

Actualizar dependencias a diario, incluso con un proceso automatizado para crear pull requests, sigue generando mucho trabajo tedioso. Este se convirtió en nuestro nuevo gran problema en Manifold: los desarrolladores sentían que dedicaban demasiado tiempo a revisar y aprobar pull requests para actualizar dependencias. Esto dio lugar a tres comportamientos distintos:

  • Ignorar los pull requests y dejar que se acumulen en la cola de revisión.

  • Quejarse en voz alta de la situación, ya sea pidiendo que se desactiven las actualizaciones automatizadas de dependencias o que se resuelva el problema de cualquier manera posible.

  • Seguir revisando (por encima) los pull requests, aprobarlos y hacer merge.

El segundo comportamiento (quejarse en voz alta) fue el motor de la mejora continua de nuestro proceso de actualización de dependencias.

Quien acababa de implementar una mejora en el proceso probablemente ya tenía ideas sobre el siguiente paso y, seguramente, estaba conforme con cómo iban las cosas. Los demás desarrolladores no estaban tan involucrados en el cambio y tenían más posibilidades de identificar el problema más inmediato. Quienes alzaron la voz en lugar de ignorar o aceptar el problema nos ayudaron a seguir avanzando (aunque puede doler escuchar que la nueva mejora del proceso no es suficiente).

A medida que avanzábamos, nos esforzábamos por hacer cambios y mejoras incrementales, fáciles de revertir, y los proponíamos con humildad. Cuando las quejas por tener que lidiar con un gran volumen de pull requests para actualizar dependencias se hacían oír, el desarrollador que había implementado la mejora les recordaba a todos que podían desactivar las actualizaciones automatizadas, pero les pedía tiempo para aliviar primero el problema. Los responsables de la base de código siempre tenían el control, pero confiaban en que se harían mejoras: ¡ya lo habían visto antes!

En lugar de desactivar o reducir la frecuencia de las actualizaciones automatizadas de dependencias, proporcionamos un mecanismo para aprobarlas y hacer merge automáticamente, siempre que las pruebas pasaran.

«Los cambios automatizados de dependencias están generando un retraso en nuestro pipeline de implementación continua».

Después de suficientes ciclos de mejora, el problema que sientes ya no se parece en nada al que anticipaste al principio. Pasarás a tener el «tipo de problema bueno»: uno que solo existe gracias a tu éxito. Puede que resolver estos problemas requiera más esfuerzo, pero a menudo beneficia a un área más amplia.

Una vez que empezamos a hacer merge automáticamente de los pull requests de actualización de dependencias, los servicios de Manifold que se actualizaban con más frecuencia se toparon con el siguiente problema. Ya teníamos un pipeline de GitOps que llevaba los cambios de PR a staging y luego a producción, agrupando cambios para staging e implementando el mismo conjunto en producción. Como cada implementación en producción tenía que ser aprobada por el responsable del servicio y no modificábamos el conjunto de cambios aplicado a staging una vez que llegaba allí, los cambios de los desarrolladores terminaban atascados detrás de una acumulación de pull requests automatizados de dependencias.

En realidad, este no era un problema exclusivo de los cambios automatizados de dependencias. Era una señal temprana de que un gran volumen de cambios atascaría nuestro pipeline de implementación, y motivó a los desarrolladores a mejorar los tiempos de compilación y prueba, y al equipo de plataforma a reducir el tiempo que tardaba el sistema de implementación en detectar y aplicar los cambios.

Para los pull requests de dependencias, implementamos una lógica en nuestro pipeline de implementación que aprobaba automáticamente su promoción a producción y hacía merge y aplicaba automáticamente los cambios aprobados (con el efecto adicional de ahorrarle a los desarrolladores un paso en cada uno de los demás cambios). Pasar a un proceso sin intervención humana para algunas clases de cambios significó que teníamos que confiar mucho en nuestras pruebas: ya fuera mejorando las que teníamos o admitiendo explícitamente que eran tan eficaces como cualquier evaluación humana.

La mejora continua funciona

Lo que comenzó con el deseo de evitar que las actualizaciones de dependencias ocuparan todo un sprint nos dio eso y mucho más: implementaciones más rápidas, un proceso totalmente automatizado desde el pull request hasta producción y un acuerdo explícito sobre la eficacia de nuestras pruebas.

Casi olvidamos el problema de actualizar dependencias, que fue reemplazado por nuestro pipeline de implementación, un desafío mayor. Por bueno que fuera ese pipeline, no estábamos conformes con él y nunca lo estaríamos: ¡así funciona la mejora continua!

Leer más

feature insights context
Blog

Los ataques autónomos ya están aquí. La defensa debe estar a su altura.

Los atacantes autónomos están reduciendo el tiempo disponible para defenderse. Descubre cómo el descubrimiento, la corrección, la validación y la prevención continuos pueden ayudar a los equipos de seguridad a seguirles el ritmo.

illustration hero ai
Blog

El huracán de la IA ha llegado

La IA está acelerando por igual la creación de software y los ciberataques. Los líderes deben proteger los agentes y el código desde el inicio, aplicar controles en tiempo de ejecución y validar las defensas de forma independiente.

feature insights context
Blog

¿La prevención es, en esencia, un problema ya resuelto?

La prevención en el código generado por agentes está resuelta desde el punto de vista arquitectónico, pero elegir controles que protejan la seguridad sin ralentizar el desarrollo sigue siendo el desafío.