Código morto — são fragmentos de programa que nunca são executados e não afetam o resultado, mas permanecem fisicamente nos arquivos fonte do projeto. Ao contrário de trechos comentados, o código morto é compilado e vai para o binário, aumentando seu tamanho e complicando a navegação. De acordo com a pesquisa TIOBE Index (2025), um projeto comercial médio contém de 10 a 25 por cento de código que nunca é chamado. Código zumbi — um subtipo de código morto que funcionava no passado, mas após refatoração perdeu relevância e agora só ocupa espaço. A limpeza regular desses fragmentos reduz a carga cognitiva dos desenvolvedores e diminui o risco de erros ao fazer alterações.
Principais pontos
Código morto (dead code) — é o código fonte incluído no programa, mas que nunca é executado em nenhum cenário de uso. O compilador ou interpretador o processa, mas em tempo de execução o controle nunca chega a essas partes.
Exemplos clássicos de código morto: variáveis que recebem valor mas nunca são lidas; funções ou métodos que não são chamados em lugar nenhum; ramos de condição que nunca se tornam verdadeiros (if(false)); loops cujo corpo não é executado nenhuma vez.
De acordo com o relatório SonarQube State of Code Quality (2025), cerca de 15 por cento de todos os warnings em projetos Java comerciais estão relacionados a métodos e campos privados não utilizados. Em projetos JavaScript, a proporção de código não utilizado pode chegar a 30 por cento devido à natureza dinâmica da linguagem e à abundância de bibliotecas terceiras.
Verifique regularmente seu projeto quanto à presença de código morto — especialmente após grandes refatorações e remoção de funcionalidades. Um import esquecido ou uma função não utilizada hoje pode se transformar amanhã em código zumbi que engana novos membros da equipe.
Código zumbi (zombie code) — é um caso especial de código morto que se diferencia pelo contexto histórico. O código zumbi já funcionou, mas após mudanças no sistema deixou de ser alcançável, porém não foi removido, mas sim mantido «por precaução».
A diferença entre código morto e zumbi — na origem. O código morto pode ter sido escrito erroneamente (nunca funcionou), enquanto o código zumbi — é um código vivo anterior que perdeu relevância após refatoração. Por exemplo, uma função de cálculo de desconto segundo lógica de negócio antiga que foi substituída por uma nova, mas o método antigo não foi removido — caso seja necessário retornar.
Principal perigo do código zumbi — a ilusão de funcionalidade em funcionamento. Um novo desenvolvedor vê uma função, lê sua documentação, presume que ela é chamada em algum lugar — e perde tempo estudando um artefato. Ao tentar chamá-la diretamente, pode descobrir que ela depende de entidades removidas ou APIs antigas.
Monitore código zumbi através do histórico git: se uma função não foi alterada por dois anos e não é usada — é zumbi. Remova-a sem hesitação, porque o git mantém o histórico, e o código sempre pode ser restaurado se necessário.
A primeira e mais frequente causa — desenvolvimento iterativo com refatoração incompleta. A equipe adiciona nova funcionalidade substituindo a antiga, mas não remove os módulos substituídos. As sprints acumulam essas «caudas», e depois de um ano o projeto fica coberto por uma camada de código morto.
A segunda causa — testes A/B e feature toggle. As condições de ativação de um novo recurso podem se fixar com o tempo (por exemplo, sempre true), mas o ramo else com a lógica alternativa permanece no código. Os desenvolvedores temem removê-lo para não quebrar o sistema acidentalmente se o toggle for reativado.
Terceira causa — geração automática e copy-paste. Geradores de código (IDE, template engines) criam esqueletos com métodos que o desenvolvedor não preenche ou não usa. Código copiado de outro projeto frequentemente contém blocos inteiros não relevantes para o novo contexto.
Quarta causa — medo de remover. Em projetos grandes, desenvolvedores temem remover código porque não têm certeza de que ele realmente não é usado em lugar nenhum. Esse medo é agravado por um sistema de testes fraco: se não há verificação automática, a remoção pode levar a bugs que só são descobertos em produção.
Código morto afeta diretamente quatro aspectos da qualidade do projeto: desempenho do build, tamanho do artefato, carga cognitiva da equipe e confiabilidade da refatoração.
Aumento do tempo de compilação: o compilador processa arquivos não utilizados, analisa dependências e gera bytecode ou código de máquina para fragmentos que nunca serão executados. Em projetos grandes, isso adiciona minutos a cada build. Para linguagens interpretadas (JavaScript, Python), aumenta o tempo de carregamento do módulo e o consumo de memória.
Risco de bugs ao modificar: um desenvolvedor altera código e não suspeita que a função é usada apenas em um ramo morto. Após refatoração, o código morto para de compilar ou gera erros — a equipe perde tempo diagnosticando um problema que não afeta o funcionamento da aplicação.
Carga cognitiva — o fator mais caro. Cada função não utilizada exige atenção ao ler o código. O desenvolvedor gasta energia mental para entender por que esse código existe e onde é chamado. Pesquisa do Developer Productivity Lab (2025) mostrou: remover 20 por cento do código morto reduz o tempo de integração (onboarding time) em média 18 por cento.
Remova código morto imediatamente ao descobri-lo. Cada dia de atraso aumenta a probabilidade de alguém da equipe perder horas estudando um artefato que deveria ter sido removido ontem.
A busca de código morto é feita por dois métodos principais: análise estática (sem executar o programa) e análise dinâmica (perfil de cobertura em tempo de execução). Cada abordagem é eficaz para diferentes tipos de código morto.
Analisadores estáticos suportam todas as linguagens de programação populares. Para Java e Kotlin — SonarQube, IntelliJ IDEA Inspections, SpotBugs. Para JavaScript e TypeScript — ESLint com regras no-unused-vars e no-unused-modules. Para Swift — SwiftLint com regra unused_declaration. Para Python — pylint com opção unused-import e vulture para busca profunda.
// build.gradle.kts - configuração do ProGuard para Android
android {
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
// proguard-rules.pro - manter apenas classes necessárias
-keep class com.example.app.** { *; }
-assumenosideeffects class Timber {
static <methods>;
}
ProGuard não apenas remove classes e métodos não utilizados, mas também minifica nomes em builds de release. O build com ProGuard ativado mostra automaticamente quais classes e métodos são considerados não utilizados — no relatório usage.txt lista todo o código removido.
Ferramentas de cobertura de código (JaCoCo para Java, XCTest coverage para Swift, Istanbul para JavaScript) mostram quais linhas e ramos são executados durante os testes. Métodos com cobertura zero — candidatos a código morto. No entanto, a ausência de cobertura não garante que o código não seja chamado em produção — para certeza total, use uma combinação de análise estática e dinâmica.
Configure seu pipeline de CI para que o build falhe ao ultrapassar o limite de declarações não utilizadas. O Quality Gate do SonarQube com a regra «Proporção de código privado não utilizado não superior a 3%» previne o acúmulo de código morto no nível do processo de desenvolvimento.
O processo de remoção de código morto consiste em quatro etapas: encontrar, verificar, remover, verificar novamente. Pular qualquer etapa aumenta o risco de regressão.
Primeira etapa — busca de candidatos através de analisador estático. Obtenha um relatório de declarações não utilizadas: funções, classes, variáveis, imports. Filtre falsos positivos — analisadores às vezes erram em casos de reflexão, carregamento dinâmico de classes ou chamadas ocultas via serialização.
Segunda etapa — verificação via git blame e histórico de alterações. Veja quando e por que o código foi escrito. Se o código fazia parte de uma funcionalidade desativada por feature toggle — certifique-se de que o toggle está fixo e não será reativado. Comente o código que você hesita em remover e deixe um TODO com ticket para reavaliação em um mês.
Terceira etapa — remoção em branch separada com execução completa dos testes. Se os testes passarem — a probabilidade de regressão é baixa. Se os testes falharem — significa que o código ainda é usado, e é preciso entender em qual cenário.
// before - código morto e código zumbi no mesmo arquivo
int calculateV1(int price) { // não chamado em lugar nenhum
int tax = price * 0.18;
return price + tax;
}
int calculateV2(int price, double rate) {
return static_cast<int>(price * (1 + rate));
}
// after - código morto removido, código zumbi limpo
int calculatePrice(int price, double rate) {
return static_cast<int>(price * (1 + rate));
}
Quarta etapa — code review das alterações. O revisor deve confirmar que o código está realmente morto. Se o revisor não tiver certeza — deixe um comentário no código e adie a remoção até análise completa. Após o merge do branch — remova o branch para não proliferar código zumbi no repositório git.
Implemente a regra: nenhum pull request deve conter novo código morto. Adicione um linter em hooks de pre-commit que bloqueie o commit na presença de variáveis ou imports não utilizados. Prevenção é sempre mais barata que limpeza.
Perguntas frequentes
Sim, se o código morto contiver erros de sintaxe ou referenciar tipos removidos. Compiladores modernos ainda verificam ramos mortos, portanto um erro no bloco if(false) causará falha no build. Isso é proteção: o código não deve ser tão morto a ponto de o compilador não o verificar.
Código zumbi engana: um novo desenvolvedor vê uma função com documentação e presume que ela é usada. Ele perde tempo estudando código que não funciona e pode acidentalmente amarrar nova lógica a uma entidade obsoleta, criando um bug difícil de rastrear.
Use ESLint com as regras no-unused-vars e no-unused-modules, além da utilidade knip — ela analisa exports e imports em todo o projeto, encontrando arquivos, funções e dependências não utilizados. Para grandes monorepositórios, o knip mostra o quadro mais completo.
É melhor remover antes do lançamento, mas não no último momento. Remover código morto é trabalho técnico que se planeja na sprint separadamente. Imediatamente antes do lançamento, a remoção pode trazer instabilidade se o código não estiver tão morto quanto parecia.
Sim, compiladores modernos e minificadores (ProGuard, R8, Terser, Closure Compiler) removem código inalcançável no nível de Dead Code Elimination. No entanto, isso não elimina a necessidade de limpeza dos fontes: o compilador remove o código do binário, mas não do repositório — desenvolvedores continuam tropeçando nele ao ler.
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