Dependency Hell — una situación en la que el gestor de paquetes no puede resolver conflictos de versiones de librerías en un proyecto. En el desarrollo móvil, Dependency Hell es especialmente doloroso: Gradle en Android y CocoaPods/SPM en iOS a menudo se enfrentan a conflictos transitivos. Según un informe de Sonatype (2024), el número medio de dependencias directas en un proyecto móvil supera las 80, y las transitivas — más de 400, cada una requiriendo compatibilidad de versiones.
Puntos clave
Dependency Hell es un término que describe una situación en la que el sistema de gestión de dependencias no puede resolver un conflicto de versiones entre librerías. El proyecto requiere la librería A versión 1.x y la librería B versión 2.x, pero A depende de C versión 1.0, mientras que B depende de C versión 2.0, y C:1.0 y C:2.0 son incompatibles.
El problema es común en todos los ecosistemas con gestores de paquetes. En Android — conflictos de Gradle entre support library y AndroidX. En iOS — conflictos de CocoaPods entre diferentes versiones de Alamofire. En Node.js — conflictos de peer dependency de npm. En Python — fallos de resolución de pip.
Los gestores de dependencias modernos (npm v7+, Gradle 7+, SwiftPM) han mejorado los algoritmos de resolución, pero la eliminación completa de conflictos es imposible con cientos de dependencias transitivas. Dependency Hell ha pasado de la categoría “error de compilación” a la categoría “gestión de riesgos”.
Diamond dependency — el caso clásico. La librería A depende de D:1.0, la librería B depende de D:2.0. Si A y B se usan juntas, el gestor de paquetes debe decidir qué versión de D instalar. En la mayoría de los casos, se selecciona la versión máxima (2.0), pero si A no es compatible con D:2.0 — el conflicto es irresoluble.
Version conflict — un desajuste explícito de requisitos. A requiere Logging >=2.0, B requiere Logging <2.0. El gestor no puede satisfacer ambas condiciones. Peer dependency conflict — el plugin A requiere React 17, pero el proyecto usa React 18 con cambios disruptivos. npm muestra una advertencia, pero la instalación continúa — el comportamiento se vuelve impredecible.
Transitive dependency hell — cuando una dependencia no es directa sino indirecta. El desarrollador no sabe que la librería A depende de B, y B depende de C. Gradle Dependency Tree — una herramienta para visualizar toda la cadena de dependencias, mostrando de dónde proviene la librería en conflicto.
Circular dependency — A depende de B, y B depende de A. Los gestores modernos (Gradle, npm) bloquean las dependencias circulares en tiempo de compilación. Solución — extraer un módulo común C del que dependan tanto A como B, rompiendo el ciclo.
Crecimiento del número de librerías — el principal requisito previo. Cada módulo añade dependencias directas y transitivas. En un proyecto Android con Jetpack Compose, Firebase, Retrofit y Coil, el número de dependencias transitivas supera fácilmente las 500. Cada nueva librería es un conflicto potencial.
Actualizaciones no sincronizadas — los equipos actualizan librerías en diferentes momentos. El backend actualiza Jackson a 2.15, el equipo de Analytics usa 2.12. Al integrar módulos, surge un conflicto. Solución — versiones centralizadas (Bill of Materials) en un archivo BOM de Gradle o catálogo de versiones.
Diferentes versiones de la misma librería — la situación clásica: el módulo A usa OkHttp 3.12, el módulo B usa OkHttp 4.0. Si la actualización a 4.0 rompe el módulo A, el proyecto se queda con dos versiones, lo que puede provocar conflictos de classpath en Java o símbolos duplicados en iOS.
Gradle Dependency Tree — el comando `gradle dependencies` muestra el árbol completo de dependencias con los conflictos indicados. La versión resuelta muestra qué versión seleccionó Gradle, y las versiones en conflicto están marcadas con flechas. Ejemplo: `com.squareup.okhttp3:okhttp -> 4.9.3 (*)` — versión resuelta, (*) — duplicación.
npm ls — un comando similar para Node.js. La bandera `--all` muestra el árbol completo. Los conflictos de peer dependency se muestran con advertencias. SwiftPM Graph — `swift package show-dependencies` muestra el grafo de dependencias para proyectos iOS, incluyendo ramas y revisiones.
Dependency Analysis Plugin — un plugin de Gradle de Autonomy que encuentra dependencias no utilizadas y conflictos. Ben Manes Versions Plugin — comprueba qué dependencias están obsoletas y muestra las actualizaciones disponibles. Ambas herramientas automatizan la comprobación rutinaria de compatibilidad.
// Conflicto: el módulo A necesita okhttp 3.x, el módulo B necesita okhttp 4.x
dependencies {
implementation("com.example:module-a:1.0") // -> okhttp 3.12
implementation("com.example:module-b:2.0") // -> okhttp 4.0
}
// Solución: forzar una versión específica
configurations.all {
resolutionStrategy {
force "com.squareup.okhttp3:okhttp:4.9.3"
}
}
Version Catalog (Gradle 7+) — declaración centralizada de versiones en un archivo TOML. Todos los módulos usan las mismas versiones de librerías. Ejemplo: el archivo `libs.versions.toml` contiene `okhttp = “4.9.3”`, y todos los módulos hacen referencia a este catálogo. Los conflictos de versiones entre módulos se eliminan.
Bill of Materials (Spring BOM) — un concepto de Maven donde se especifican versiones compatibles de librerías. El equipo de Google Android usa Compose BOM para las librerías de Jetpack. Al usar un BOM, obtienes la garantía de que todas las versiones de Compose son compatibles entre sí.
Renovate y Dependabot — creadores automáticos de PR para actualizar dependencias. Renovate agrupa actualizaciones compatibles, verifica cambios disruptivos mediante imágenes Docker. Dependabot es una solución integrada de GitHub que actualiza dependencias y verifica la compatibilidad a través de CI.
Semantic Versioning — usa caret `^1.2.3` para actualizaciones de patch/minor y tilde `~1.2.3` solo para patch. Pero ni siquiera semver garantiza la compatibilidad — las violaciones reales de semver ocurren en el 15% de los casos (según un estudio de la Universidad de Luxemburgo, 2024). Los lock files fijan la versión exacta que pasó las pruebas.
Minimizar dependencias — cada librería debe estar justificada. Si puedes implementar la funcionalidad en 20 líneas de tu propio código — no agregues una librería. Ejemplo: en lugar de una librería para formatear fechas (4 dependencias transitivas), usa las herramientas integradas de la plataforma. La regla del “presupuesto de dependencias” — no más de 50 dependencias directas por proyecto.
Actualizaciones regulares — actualiza las dependencias en pequeños pasos, no una vez al año. Dependabot crea un PR para cada actualización. CI debe ejecutar el conjunto completo de pruebas. DevContainer — un entorno de desarrollo unificado donde las versiones de las dependencias coinciden con las de producción, eliminando conflictos entre entornos.
Preguntas frecuentes
Primero, ejecuta `gradle dependencies` (Gradle), `npm ls` (Node.js) o `swift package show-dependencies` (SwiftPM). Encuentra la librería en conflicto. Tres soluciones: forzar una versión mediante resolutionStrategy, excluir la dependencia transitiva (`exclude group:`), o actualizar una de las librerías en conflicto a una versión compatible.
Version Catalog (libs.versions.toml) — una única fuente de verdad para las versiones de todas las librerías. Todos los módulos del proyecto hacen referencia a un catálogo. Cuando se actualiza una librería, la versión cambia en un solo lugar. Esto evita la situación en la que dos módulos usan diferentes versiones de la misma librería.
Las dependencias transitivas son librerías que arrastra una dependencia directa. El desarrollador a menudo no sabe de ellas. El peligro: una dependencia transitiva puede entrar en conflicto con otra dependencia directa. La solución es revisar regularmente el árbol de dependencias e incluir solo librerías con un mínimo de dependencias transitivas.
No necesariamente cada sprint, pero sí regularmente. Recomendación: una vez al mes, ejecuta Dependabot o Renovate para crear PRs. Los parches de seguridad críticos deben actualizarse en una semana. Las actualizaciones menores — dentro de un sprint normal. Las actualizaciones mayores requieren una evaluación separada de los cambios disruptivos.
Una librería sin soporte es un riesgo de seguridad y compatibilidad. Estrategia: encuentra una alternativa con una comunidad activa (estrellas de GitHub, fecha del último commit), planifica la migración mediante abstracción (Interface/Protocol), reemplaza la librería en 2–3 sprints. Si no hay alternativa — haz un fork del repositorio y mantén la versión dentro del equipo.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también