In jedem Legacy-Projekt, das ich mir zum ersten Mal anschaue, gibt es ihn: Code, der schon seit Jahren niemand mehr aufgerufen hat. Auskommentierte Funktionen, vergessene Klassen, Methoden, die irgendwann ersetzt wurden – aber nie gelöscht. Toten Code einfach liegen zu lassen ist verlockend. Schließlich tut er ja nichts. Leider stimmt das nicht ganz.

Warum toter Code ein echtes Problem ist

Entwickler lesen toten Code. Sie verlassen sich auf ihn, wenn sie eine Funktion suchen. Sie warten ihn manchmal sogar, weil sie nicht wissen, dass er tot ist. Jede Zeile Code, die existiert, muss mental verarbeitet werden – auch wenn sie nie ausgeführt wird. Und schlimmer noch: toter Code kann sicherheitsrelevante Schwachstellen enthalten, die zwar nie getriggert werden – aber das weiß man ja erst hinterher.

Werkzeuge zur Erkennung

Ich starte immer mit den automatisierten Werkzeugen, bevor ich selbst anfange zu suchen:

# PHPStan findet nicht erreichbaren Code
vendor/bin/phpstan analyse src --level=6

# PHP_CodeSniffer mit Dead-Code-Regeln
vendor/bin/phpcs --standard=Generic src

# Grobe Suche nach auskommentierten Funktionen
grep -rn "^[[:space:]]*//" src/ | grep "function\|class"

PHPStan ist dabei mein verlässlichster Helfer. Es findet Methoden, die nach einem return-Statement stehen, Branches, die niemals wahr werden können, und Variablen, die gesetzt aber nie gelesen werden. Das alles ist kein Beweis, aber ein guter Startpunkt.

Drei Kategorien von totem Code

1. Eindeutig tot

Auskommentierte Blöcke, Funktionen die nachweislich nie aufgerufen werden, ganze Dateien die nicht eingebunden sind. Hier kann ich relativ entspannt vorgehen.

2. Möglicherweise tot

Methoden, die nur über einen Code-Pfad erreichbar sind, der unter normalen Bedingungen nie durchlaufen wird. Ein Admin-Feature, das seit Jahren keine aktiven Nutzer hat. Hier muss ich genauer hinschauen – und im Zweifel fragen.

3. Scheinbar tot, aber doch aktiv

Das ist die gefährliche Kategorie. Methoden, die über __call() dynamisch aufgerufen werden. Klassen, die per Reflection instanziiert werden. Event-Handler, die als String registriert sind. Wer hier blind löscht, erlebt sein blaues Wunder.

// Diese Methode sieht tot aus – ist es aber nicht
class UserFactory {
    public function createAdmin() { ... }  // Wird per Reflection aufgerufen
    public function createEditor() { ... } // Wird per Reflection aufgerufen
}

Das sichere Vorgehen: markieren, isolieren, testen

Ich lösche nie direkt. Erst kommt ein Kommentar mit Datum: // TODO: toter Code, zu entfernen ab 2026-06-01. Dann beobachte ich, ob irgendwas aufschreit. Danach kommt ein isolierter Branch, in dem ich entferne. Tests laufen durch. Staging sieht gut aus. Dann der Merge.

Git ist hier das eigentliche Sicherheitsnetz. Nichts geht verloren. Wenn sich herausstellt, dass der Code doch gebraucht wurde, ist er in der History – und auffindbar.

Die Frage ist nie "kann ich das löschen?" – sondern "was passiert, wenn ich es lösche?". Diese Umkehrung ändert, wie sorgfältig man vorgeht.

Fazit

Toten Code zu entfernen ist eine der wirkungsvollsten Maßnahmen bei der Modernisierung – und eine der am meisten unterschätzten. Weniger Code bedeutet weniger Leseverwirrung, weniger Wartungsaufwand, kleinere Diffs und kürzere Onboarding-Zeiten. Das Risiko ist beherrschbar, wenn man methodisch vorgeht. Und am Ende ist es einfach schöner, in einem Codebase zu arbeiten, der nicht aussieht wie ein Archäologie-Projekt.