Educativo

Codice Morto: Come Trovarlo, Rimuoverlo e Prevenirlo

Guida pratica al codice morto: come trovare il codice irraggiungibile e inutilizzato, rimuoverlo in sicurezza e prevenirne l'accumulo nel tuo codebase.

Screenshot di uno strumento di analisi statica che evidenzia il codice morto irraggiungibile in un codebase

In questo articolo:

Cos’è il codice morto e perché si accumula

Il codice morto è qualsiasi codice che esiste nel codebase ma non viene mai eseguito in un ambiente live. Include rami irraggiungibili (codice dopo un’istruzione return, rami di condizioni che non possono mai essere veri), variabili e parametri inutilizzati, funzioni che non vengono mai chiamate, classi che non vengono mai istanziate, condizioni di feature flag sempre false, blocchi commentati lasciati per riferimento futuro e API deprecate ancora presenti ma non più usate.

Il codice morto si accumula per ragioni prevedibili. Le funzionalità vengono rimosse ma l’implementazione no. Un refactoring sostituisce una vecchia funzione ma non la cancella “per ogni evenienza.” Un flag per fare rollback di una release di funzionalità diventa permanentemente falso ma rimane nel codice. Una classe viene superata da una nuova implementazione ma l’eliminazione sembra rischiosa perché nessuno è certo che nulla la chiami.

In ogni codebase che valutiamo in Eden Technologies, il codice morto rappresenta in modo coerente tra il 10% e il 25% del conteggio totale dei file. In un caso, un codebase di 400.000 righe conteneva oltre 80.000 righe di codice che non erano mai raggiungibili da alcun percorso di codice.

I costi reali del codice morto nei sistemi in produzione

Il codice morto ha costi che i team di engineering sottostimano perché il codice “non sta facendo nulla.” Questo ragionamento è scorretto.

Overhead cognitivo. Ogni developer che legge il codebase deve analizzare il codice morto insieme al codice live. Quando un nuovo ingegnere si unisce a un team e vede una funzione che sembra importante ma non viene mai chiamata, trascorre del tempo cercando di capirne lo scopo.

Gonfiore delle build e delle dipendenze. Il codice morto che importa librerie o framework fa sì che quelle dipendenze vengano incluse nella build. In un’applicazione JavaScript che abbiamo esaminato, dodici pacchetti npm erano importati solo da codice morto che non era mai raggiungibile in nessun ambiente di deployment.

Rischio di sicurezza. Il codice morto che contiene operazioni sensibili alla sicurezza è particolarmente pericoloso. Un vecchio endpoint di autenticazione che “non viene mai chiamato” potrebbe essere ancora raggiungibile tramite un URL non documentato. Con il GDPR e le normative NIS2, la superficie di attacco di un sistema include tutti i percorsi di codice, non solo quelli intenzionali.

Metriche di copertura false. Gli strumenti di copertura contano il codice morto in modo diverso a seconda della loro configurazione. Il codice tecnicamente presente ma irraggiungibile può gonfiare i numeri di copertura, dando un falso senso di sicurezza sull’adeguatezza dei test.

Come trovare il codice morto: strumenti e tecniche

Trovare il codice morto richiede sia strumenti automatizzati che revisione manuale.

Analisi statica. Strumenti come SonarQube, JaCoCo (Java), Istanbul (JavaScript) e Coverage.py (Python) possono identificare il codice che non viene mai referenziato da nessun sito di chiamata nel codebase. Questo rileva efficacemente funzioni, classi e import inutilizzati.

Analisi della copertura. L’esecuzione dell’intera suite di test con strumentazione di copertura mostra quali righe vengono eseguite durante i test. Le righe con zero copertura sono candidati per la revisione del codice morto.

Tracing in runtime. Per i sistemi dove l’analisi statica è insufficiente (codebase altamente dinamici, sistemi con uso estensivo di reflection), l’analisi del traffico in produzione può identificare endpoint, funzioni o moduli che non ricevono traffico per un periodo prolungato.

Revisione manuale durante il lavoro degli sprint. Quando un developer tocca un modulo per una modifica di funzionalità, una pratica consolidata di “rimuovi il codice morto che incontri” previene l’accumulo al margine.

Come fare refactoring del codice: rimuovere il codice morto in sicurezza

Il processo di eliminazione sicura segue questi passaggi.

Primo, conferma l’irraggiungibilità attraverso più segnali: l’analisi statica non mostra chiamanti, l’analisi della copertura mostra zero esecuzione nei test e (dove possibile) le metriche di runtime mostrano zero invocazione in produzione negli ultimi 90 giorni.

Secondo, aggiungi un’istruzione di log o un alert di monitoraggio al codice prima di eliminarlo. Fai il deploy di questa versione in produzione e osserva per 72 ore. Se il log si attiva, qualcosa sta chiamando il codice.

Terzo, quando sei sicuro che il codice sia irraggiungibile, eliminalo. Non commentarlo. Non spostarlo in una cartella di archivio. Eliminalo. La cronologia del controllo di versione preserva ogni riga mai scritta.

Quarto, esegui l’intera suite di test dopo l’eliminazione per confermare che nessun test si affidasse al codice morto.

Come prevenire il ritorno del codice morto

Gestione del ciclo di vita dei feature flag. Ogni feature flag dovrebbe avere una scadenza definita: una data o una milestone di release dopo la quale il flag viene rimosso indipendentemente dal fatto che il rollout sia completo.

Definizione di done. La definizione di done degli sprint per la rimozione di funzionalità dovrebbe includere “tutto il codice, i test e la configurazione associati eliminati.”

Rilevamento automatizzato nella CI. Strumenti come ts-prune per TypeScript o vulture per Python possono rilevare export inutilizzati e funzioni morte come parte del processo di pull request.

Sprint di pulizia regolari. Un mezzo sprint trimestrale dedicato alla salute del codebase, inclusa la rimozione del codice morto, rafforza la pratica e previene la deriva graduale.

Le nostre valutazioni di legacy modernization includono sempre la mappatura del codice morto come primo passo, perché rimuoverlo prima del refactoring riduce significativamente l’ambito del refactoring.

Conclusione

Il codice morto non è innocuo. Crea overhead cognitivo, gonfia le dipendenze, introduce rischi di sicurezza e produce segnali falsi nelle metriche di copertura e salute. Nei codebase con accumulo significativo, può rappresentare il 20% o più delle dimensioni totali del codebase.

Rimuoverlo in sicurezza richiede un processo di verifica, non solo un comando di eliminazione. Prevenirne il ritorno richiede cambiamenti di processo nel flusso di lavoro degli sprint e nella pipeline CI.

Eden Technologies tratta la rimozione del codice morto come un primo passo ad alto valore e basso rischio in qualsiasi remediation del codebase. In un impegno, la rimozione del codice morto ha ridotto il codebase effettivo del 18% prima che iniziasse qualsiasi refactoring strutturale.

Hai un codebase con questi problemi? Parliamo del tuo sistema