Codice morto e codice zombie nello sviluppo: cosa sono, cause e ricerca

Autore: IT Sectr Pubblicato: 2026-07-26 Tempo di lettura: 10 min

Il codice morto — sono frammenti di programma che non vengono mai eseguiti e non influenzano il risultato, ma rimangono fisicamente nei file sorgente del progetto. A differenza delle parti commentate, il codice morto viene compilato e finisce nel binario, aumentandone le dimensioni e complicando la navigazione. Secondo lo studio di TIOBE Index (2025), un progetto commerciale medio contiene dal 10 al 25 per cento di codice che non viene mai chiamato. Codice zombie — un sottotipo di codice morto che funzionava in passato, ma dopo il refactoring ha perso rilevanza e ora occupa solo spazio. La pulizia regolare di questi frammenti riduce il carico cognitivo degli sviluppatori e diminuisce il rischio di errori quando si apportano modifiche.

Punti chiave

  • Codice morto — frammenti che non vengono mai eseguiti, ma rimangono nel progetto.
  • Codice zombie — codice che veniva eseguito prima, ma dopo le modifiche è diventato irraggiungibile.
  • Il codice morto aumenta le dimensioni del binario, il tempo di build e il carico cognitivo del team.
  • Principali strumenti di ricerca: analisi statica (SonarQube, ESLint) e profilatori di copertura.
  • Rimuovere il codice morto in sicurezza tramite verifica della copertura dei test e code review.

Cos’è il codice morto?

Codice morto (dead code) — è il codice sorgente incluso nel programma, ma che non viene mai eseguito in nessuno scenario di utilizzo. Il compilatore o interprete lo elabora, ma in fase di esecuzione il controllo non raggiunge mai queste parti.

Esempi classici di codice morto: variabili a cui viene assegnato un valore ma non vengono mai lette; funzioni o metodi che non vengono chiamati da nessuna parte; rami condizionali che non diventano mai veri (if(false)); cicli il cui corpo non viene eseguito nemmeno una volta.

Secondo il rapporto SonarQube State of Code Quality (2025), circa il 15 per cento di tutti gli avvisi nei progetti Java commerciali riguardano metodi e campi privati non utilizzati. Nei progetti JavaScript, la percentuale di codice non utilizzato può raggiungere il 30 per cento a causa della natura dinamica del linguaggio e dell’abbondanza di librerie di terze parti.

Controlla regolarmente il tuo progetto per la presenza di codice morto — specialmente dopo grandi refactoring e rimozione di funzionalità. Un import dimenticato o una funzione non utilizzata oggi può trasformarsi domani in codice zombie che inganna i nuovi membri del team.

Differenze tra codice morto e codice zombie

Codice zombie (zombie code) — è un caso speciale di codice morto che si distingue per il contesto storico. Il codice zombie un tempo funzionava, ma dopo modifiche al sistema ha smesso di essere raggiungibile, ma non è stato rimosso, bensì mantenuto «per sicurezza».

La differenza tra codice morto e zombie — nell’origine. Il codice morto può essere stato scritto erroneamente (non ha mai funzionato), mentre il codice zombie — è un codice precedentemente vivo che ha perso rilevanza dopo il refactoring. Ad esempio, una funzione di calcolo dello sconto secondo una vecchia logica di business che è stata sostituita da una nuova, ma il vecchio metodo non è stato rimosso — nel caso si dovesse tornare indietro.

Il pericolo principale del codice zombie — l’illusione di funzionalità operativa. Un nuovo sviluppatore vede una funzione, legge la sua documentazione, presume che sia chiamata da qualche parte — e perde tempo studiando un artefatto. Cercando di chiamarla direttamente, potrebbe scoprire che dipende da entità rimosse o API obsolete.

Tieni traccia del codice zombie attraverso la cronologia git: se una funzione non viene modificata da due anni e non viene utilizzata — è zombie. Rimuovila senza esitazione, perché git mantiene la cronologia e il codice può sempre essere ripristinato se necessario.

Cause della comparsa del codice morto

La prima e più frequente causa — lo sviluppo iterativo con refactoring incompleto. Il team aggiunge nuove funzionalità che sostituiscono le vecchie, ma non rimuove i moduli sostituiti. Gli sprint accumulano queste «coda», e dopo un anno il progetto si ricopre di uno strato di codice morto.

La seconda causa — i test A/B e i feature toggle. Le condizioni di attivazione di una nuova funzionalità possono fissarsi nel tempo (ad esempio, sempre true), ma il ramo else con la logica alternativa rimane nel codice. Gli sviluppatori temono di rimuoverlo per non rompere accidentalmente il sistema se il toggle venisse riattivato.

La terza causa — la generazione automatica e il copia-incolla. I generatori di codice (IDE, template engine) creano scheletri con metodi che lo sviluppatore non riempie o non utilizza. Il codice copiato da un altro progetto contiene spesso interi blocchi non rilevanti per il nuovo contesto.

La quarta causa — la paura di rimuovere. Nei grandi progetti, gli sviluppatori temono di rimuovere codice perché non sono sicuri che non sia davvero utilizzato da nessuna parte. Questa paura è aggravata da un sistema di test debole: se non c’è verifica automatica, la rimozione può portare a bug scoperti solo in produzione.

Pericoli del codice morto

Il codice morto influisce direttamente su quattro aspetti della qualità del progetto: prestazioni del build, dimensione dell’artefatto, carico cognitivo del team e affidabilità del refactoring.

Aumento del tempo di compilazione: il compilatore elabora file non utilizzati, analizza le dipendenze e genera bytecode o codice macchina per frammenti che non verranno mai eseguiti. Nei grandi progetti, questo aggiunge minuti a ogni build. Per i linguaggi interpretati (JavaScript, Python), aumentano i tempi di caricamento del modulo e il consumo di memoria.

Rischio di bug durante le modifiche: uno sviluppatore modifica il codice e non sospetta che la funzione sia utilizzata solo in un ramo morto. Dopo il refactoring, il codice morto smette di compilare o genera errori — il team perde tempo a diagnosticare un problema che non influisce sul funzionamento dell’applicazione.

Il carico cognitivo — il fattore più costoso. Ogni funzione non utilizzata richiede attenzione durante la lettura del codice. Lo sviluppatore spende energia mentale per capire perché questo codice esiste e dove viene chiamato. La ricerca del Developer Productivity Lab (2025) ha mostrato: rimuovere il 20 per cento del codice morto riduce il tempo di onboarding in media del 18 per cento.

Rimuovi il codice morto immediatamente quando lo scopri. Ogni giorno di ritardo aumenta la probabilità che qualcuno del team perda ore a studiare un artefatto che avrebbe dovuto essere rimosso ieri.

Strumenti di ricerca del codice morto

La ricerca del codice morto viene effettuata con due metodi principali: analisi statica (senza eseguire il programma) e analisi dinamica (profilazione della copertura in fase di esecuzione). Ogni approccio è efficace per diversi tipi di codice morto.

Gli analizzatori statici supportano tutti i linguaggi di programmazione popolari. Per Java e Kotlin — SonarQube, IntelliJ IDEA Inspections, SpotBugs. Per JavaScript e TypeScript — ESLint con le regole no-unused-vars e no-unused-modules. Per Swift — SwiftLint con la regola unused_declaration. Per Python — pylint con l’opzione unused-import e vulture per la ricerca approfondita.

Esempio di ricerca in Kotlin tramite ProGuard

groovy
// build.gradle.kts - configurazione ProGuard per Android
android {
    buildTypes {
        release {
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

// proguard-rules.pro - mantenere solo le classi necessarie
-keep class com.example.app.** { *; }
-assumenosideeffects class Timber {
    static <methods>;
}

ProGuard non solo rimuove classi e metodi non utilizzati, ma minimizza anche i nomi nel build di rilascio. Il build con ProGuard attivato mostra automaticamente quali classi e metodi sono considerati non utilizzati — nel rapporto usage.txt viene elencato tutto il codice rimosso.

Analisi dinamica tramite copertura dei test

Gli strumenti di copertura del codice (JaCoCo per Java, XCTest coverage per Swift, Istanbul per JavaScript) mostrano quali righe e rami vengono eseguiti durante i test. I metodi con copertura zero — candidati al codice morto. Tuttavia, l’assenza di copertura non garantisce che il codice non venga chiamato in produzione — per certezza assoluta, usa una combinazione di analisi statica e dinamica.

Configura la tua pipeline CI in modo che il build fallisca al superamento della soglia di dichiarazioni non utilizzate. Il Quality Gate di SonarQube con la regola «Proporzione di codice privato non utilizzato non superiore al 3%» previene l’accumulo di codice morto a livello di processo di sviluppo.

Come rimuovere il codice morto in sicurezza

Il processo di rimozione del codice morto consiste in quattro passaggi: trovare, verificare, rimuovere, verificare di nuovo. Saltare qualsiasi passaggio aumenta il rischio di regressione.

Primo passaggio — ricerca dei candidati tramite analizzatore statico. Ottieni un rapporto delle dichiarazioni non utilizzate: funzioni, classi, variabili, import. Filtra i falsi positivi — gli analizzatori a volte sbagliano con reflection, caricamento dinamico di classi o chiamate nascoste tramite serializzazione.

Secondo passaggio — verifica tramite git blame e cronologia delle modifiche. Controlla quando e perché il codice è stato scritto. Se il codice faceva parte di una funzionalità disattivata da un feature toggle — assicurati che il toggle sia fisso e non verrà riattivato. Commenta il codice che esiti a rimuovere e lascia un TODO con ticket per una nuova verifica tra un mese.

Terzo passaggio — rimozione in un branch separato con esecuzione completa dei test. Se i test passano — la probabilità di regressione è bassa. Se i test falliscono — il codice è ancora utilizzato e bisogna capire in quale scenario.

cpp
// before - codice morto e codice zombie nello stesso file
int calculateV1(int price) { // non chiamato da nessuna parte
    int tax = price * 0.18;
    return price + tax;
}

int calculateV2(int price, double rate) {
    return static_cast<int>(price * (1 + rate));
}

// after - codice morto rimosso, codice zombie pulito
int calculatePrice(int price, double rate) {
    return static_cast<int>(price * (1 + rate));
}

Quarto passaggio — code review delle modifiche. Il revisore deve confermare che il codice sia realmente morto. Se il revisore non è sicuro — lascia un commento nel codice e posticipa la rimozione fino all’analisi completa. Dopo il merge del branch — elimina il branch per non proliferare codice zombie nel repository git.

Applica la regola: nessuna pull request deve contenere nuovo codice morto. Aggiungi un linter nei hook di pre-commit che blocchi il commit in presenza di variabili o import non utilizzati. La prevenzione è sempre più economica della pulizia.

Domande frequenti

Il codice morto può causare errori di compilazione?

Sì, se il codice morto contiene errori di sintassi o fa riferimento a tipi rimossi. I compilatori moderni controllano comunque i rami morti, quindi un errore in un blocco if(false) causerà un fallimento del build. Questa è una protezione: il codice non deve essere così morto da non essere controllato dal compilatore.

Qual è il pericolo del codice zombie per i nuovi membri del team?

Il codice zombie inganna: un nuovo sviluppatore vede una funzione con documentazione e presume che sia utilizzata. Perde tempo a studiare codice che non funziona e potrebbe accidentalmente legare nuova logica a un’entità obsoleta, creando un bug difficile da tracciare.

Come trovare codice morto in un progetto JavaScript?

Usa ESLint con le regole no-unused-vars e no-unused-modules, insieme all’utilità knip — analizza exports e imports in tutto il progetto, trovando file, funzioni e dipendenze non utilizzati. Per i grandi monorepository, knip mostra il quadro più completo.

Bisogna rimuovere il codice morto prima del rilascio?

È meglio rimuovere prima del rilascio, ma non all’ultimo momento. La rimozione del codice morto è un lavoro tecnico che si pianifica nello sprint separatamente. Subito prima del rilascio, la rimozione potrebbe portare instabilità se il codice non fosse così morto come sembrava.

I compilatori aiutano a rimuovere automaticamente il codice morto?

Sì, i compilatori moderni e i minificatori (ProGuard, R8, Terser, Closure Compiler) rimuovono il codice irraggiungibile a livello di Dead Code Elimination. Tuttavia, ciò non elimina la necessità di pulire le sorgenti: il compilatore rimuove il codice dal binario, ma non dal repository — gli sviluppatori continuano a inciamparvi durante la lettura.

Riepilogo

  • Codice morto — frammenti non utilizzati che non vengono mai eseguiti ma rimangono nel progetto.
  • Codice zombie — sottotipo di codice morto che funzionava prima ma ha perso rilevanza dopo il refactoring.
  • Cause principali: sviluppo iterativo, feature toggle, generazione automatica e paura di rimuovere.
  • Il codice morto aumenta il tempo di build, la dimensione del binario e il carico cognitivo del team.
  • Strumenti di ricerca: SonarQube, ESLint, SwiftLint, pylint, vulture, knip, ProGuard, JaCoCo.
  • La rimozione sicura include: ricerca, analisi git, rimozione in branch, esecuzione test e code review.
  • Prevenzione del codice morto: linter nella CI, avviso sul codice non utilizzato nel code review e cultura del refactoring.

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche