Dependency Hell — uma situação em que o gerenciador de pacotes não consegue resolver conflitos de versões de bibliotecas em um projeto. No desenvolvimento móvel, o Dependency Hell é especialmente doloroso: Gradle no Android e CocoaPods/SPM no iOS frequentemente enfrentam conflitos transitivos. De acordo com um relatório da Sonatype (2024), o número médio de dependências diretas em um projeto móvel ultrapassa 80, e as transitivas — mais de 400, cada uma exigindo compatibilidade de versões.
Pontos principais
Dependency Hell é um termo que descreve uma situação em que o sistema de gerenciamento de dependências não consegue resolver um conflito de versões entre bibliotecas. O projeto requer a biblioteca A versão 1.x e a biblioteca B versão 2.x, mas A depende de C versão 1.0, enquanto B depende de C versão 2.0, e C:1.0 e C:2.0 são incompatíveis.
O problema é comum em todos os ecossistemas com gerenciadores de pacotes. No Android — conflitos do Gradle entre support library e AndroidX. No iOS — conflitos do CocoaPods entre diferentes versões do Alamofire. No Node.js — conflitos de peer dependency do npm. No Python — falhas de resolução do pip.
Os gerenciadores de dependências modernos (npm v7+, Gradle 7+, SwiftPM) melhoraram os algoritmos de resolução, mas a eliminação completa de conflitos é impossível com centenas de dependências transitivas. Dependency Hell passou da categoria “erro de compilação” para a categoria “gerenciamento de riscos”.
Diamond dependency — o caso clássico. A biblioteca A depende de D:1.0, a biblioteca B depende de D:2.0. Se A e B forem usadas juntas, o gerenciador de pacotes deve decidir qual versão de D instalar. Na maioria dos casos, a versão máxima (2.0) é selecionada, mas se A não for compatível com D:2.0 — o conflito é insolúvel.
Version conflict — uma incompatibilidade explícita de requisitos. A requer Logging >=2.0, B requer Logging <2.0. O gerenciador não pode satisfazer ambas as condições. Peer dependency conflict — o plugin A requer React 17, mas o projeto usa React 18 com mudanças de quebra. npm exibe um aviso, mas a instalação prossegue — o comportamento se torna imprevisível.
Transitive dependency hell — quando uma dependência não é direta, mas indireta. O desenvolvedor não sabe que a biblioteca A depende de B, e B depende de C. Gradle Dependency Tree — uma ferramenta para visualizar toda a cadeia de dependências, mostrando de onde vem a biblioteca conflitante.
Circular dependency — A depende de B, e B depende de A. Os gerenciadores modernos (Gradle, npm) bloqueiam dependências circulares no momento da compilação. Solução — extrair um módulo comum C do qual tanto A quanto B dependem, quebrando o ciclo.
Crescimento do número de bibliotecas — o principal pré-requisito. Cada módulo adiciona dependências diretas e transitivas. Em um projeto Android com Jetpack Compose, Firebase, Retrofit e Coil, o número de dependências transitivas facilmente ultrapassa 500. Cada nova biblioteca é um conflito potencial.
Atualizações não sincronizadas — as equipes atualizam bibliotecas em momentos diferentes. O backend atualiza o Jackson para 2.15, a equipe de Analytics usa 2.12. Ao integrar módulos, surge um conflito. Solução — versões centralizadas (Bill of Materials) em um arquivo BOM do Gradle ou catálogo de versões.
Versões diferentes da mesma biblioteca — a situação clássica: o módulo A usa OkHttp 3.12, o módulo B usa OkHttp 4.0. Se a atualização para 4.0 quebrar o módulo A, o projeto fica preso em duas versões, o que pode levar a conflitos de classpath em Java ou símbolos duplicados em iOS.
Gradle Dependency Tree — o comando `gradle dependencies` exibe a árvore completa de dependências com os conflitos indicados. A versão resolvida mostra qual versão o Gradle selecionou, e as versões conflitantes são marcadas com setas. Exemplo: `com.squareup.okhttp3:okhttp -> 4.9.3 (*)` — versão resolvida, (*) — duplicação.
npm ls — um comando similar para Node.js. A flag `--all` mostra a árvore completa. Conflitos de peer dependency são exibidos com avisos. SwiftPM Graph — `swift package show-dependencies` mostra o grafo de dependências para projetos iOS, incluindo branches e revisões.
Dependency Analysis Plugin — um plugin do Gradle da Autonomy que encontra dependências não utilizadas e conflitos. Ben Manes Versions Plugin — verifica quais dependências estão desatualizadas e mostra as atualizações disponíveis. Ambas as ferramentas automatizam a verificação rotineira de compatibilidade.
// Conflito: o módulo A precisa de okhttp 3.x, o módulo B precisa de okhttp 4.x
dependencies {
implementation("com.example:module-a:1.0") // -> okhttp 3.12
implementation("com.example:module-b:2.0") // -> okhttp 4.0
}
// Solução: forçar uma versão específica
configurations.all {
resolutionStrategy {
force "com.squareup.okhttp3:okhttp:4.9.3"
}
}
Version Catalog (Gradle 7+) — declaração centralizada de versões em um arquivo TOML. Todos os módulos usam as mesmas versões de bibliotecas. Exemplo: o arquivo `libs.versions.toml` contém `okhttp = “4.9.3”`, e todos os módulos referenciam este catálogo. Conflitos de versão entre módulos são eliminados.
Bill of Materials (Spring BOM) — um conceito do Maven onde versões compatíveis de bibliotecas são especificadas. A equipe do Google Android usa Compose BOM para as bibliotecas Jetpack. Ao usar um BOM, você obtém a garantia de que todas as versões do Compose são compatíveis entre si.
Renovate e Dependabot — criadores automáticos de PR para atualização de dependências. Renovate agrupa atualizações compatíveis, verifica mudanças de quebra através de imagens Docker. Dependabot é uma solução integrada do GitHub que atualiza dependências e verifica compatibilidade através do CI.
Semantic Versioning — use caret `^1.2.3` para atualizações de patch/minor e tilde `~1.2.3` apenas para patch. Mas mesmo o semver não garante compatibilidade — violações reais de semver ocorrem em 15% dos casos (de acordo com um estudo da Universidade de Luxemburgo, 2024). Os lock files fixam a versão exata que passou nos testes.
Minimizar dependências — cada biblioteca deve ser justificada. Se você pode implementar a funcionalidade em 20 linhas do seu próprio código — não adicione uma biblioteca. Exemplo: em vez de uma biblioteca de formatação de datas (4 dependências transitivas), use as ferramentas integradas da plataforma. A regra do “ orçamento de dependências” — não mais que 50 dependências diretas por projeto.
Atualizações regulares — atualize as dependências em pequenos passos, não uma vez por ano. Dependabot cria um PR para cada atualização. O CI deve executar o conjunto completo de testes. DevContainer — um ambiente de desenvolvimento unificado onde as versões das dependências correspondem às da produção, eliminando conflitos entre ambientes.
Perguntas frequentes
Primeiro, execute `gradle dependencies` (Gradle), `npm ls` (Node.js) ou `swift package show-dependencies` (SwiftPM). Encontre a biblioteca conflitante. Três soluções: forçar uma versão através de resolutionStrategy, excluir a dependência transitiva (`exclude group:`), ou atualizar uma das bibliotecas conflitantes para uma versão compatível.
Version Catalog (libs.versions.toml) — uma fonte única de verdade para as versões de todas as bibliotecas. Todos os módulos do projeto referenciam um catálogo. Quando uma biblioteca é atualizada, a versão muda em um único lugar. Isso elimina a situação em que dois módulos usam versões diferentes da mesma biblioteca.
Dependências transitivas são bibliotecas que uma dependência direta traz consigo. O desenvolvedor muitas vezes não sabe delas. O perigo: uma dependência transitiva pode entrar em conflito com outra dependência direta. A solução é verificar regularmente a árvore de dependências e incluir apenas bibliotecas com o mínimo de dependências transitivas.
Não necessariamente a cada sprint, mas regularmente — sim. Recomendação: uma vez por mês, execute Dependabot ou Renovate para criar PRs. Correções de segurança críticas devem ser atualizadas em uma semana. Atualizações menores — dentro de um sprint normal. Atualizações principais requerem uma avaliação separada das mudanças de quebra.
Uma biblioteca sem manutenção é um risco de segurança e compatibilidade. Estratégia: encontre uma alternativa com uma comunidade ativa (estrelas no GitHub, data do último commit), planeje a migração através de abstração (Interface/Protocol), substitua a biblioteca em 2–sprints. Se não houver alternativa — faça um fork do repositório e mantenha a versão dentro da equipe.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também