In jedem gewachsenen System steckt eine Menge Funktionalität, die niemand mehr benutzt. Der Ausdruck im Format eines Kunden, der 2019 gegangen ist. Der zweite Anmeldeweg von vor der Umstellung. Der Bericht, den einmal jemand aus dem Controlling bestellt hat und der seitdem jede Nacht erzeugt wird.
Diese Dinge kosten nicht nichts. Sie kosten in jedem Upgrade, weil sie mitgetestet werden. Sie kosten in jeder Migration, weil ihre Tabellen mitwandern. Sie kosten bei jeder Sicherheitsprüfung, bei jeder Einarbeitung und bei jeder Frage, ob man eine Stelle anfassen darf.
Löschen ist die am meisten unterschätzte Form der Modernisierung, und sie ist die einzige, die das System kleiner macht statt größer. Dieser Artikel beschreibt, wie man es tut, ohne dass es ein Vabanquespiel wird.
Warum es trotzdem nicht passiert
Der Grund ist nicht technisch, und solange er nicht ausgesprochen ist, hilft kein Vorgehen dagegen.
Das Risiko ist einseitig verteilt. Wer eine Funktion entfernt, die doch jemand brauchte, hat einen Fehler gemacht, mit Namen dran. Wer sie behält, obwohl sie niemand braucht, hat nichts falsch gemacht; die Kosten verteilen sich auf alle und auf Jahre, und niemand kann sie einer Entscheidung zuordnen.
In dieser Lage ist Behalten die rationale Wahl für jeden Einzelnen, und das Ergebnis ist ein System, das nur wächst. Die Gegenmaßnahme ist deshalb keine Ermahnung, sondern ein Ablauf, in dem sich Löschen nicht wie ein Wagnis anfühlt: messen, ankündigen, abschalten, warten, löschen. Der Schritt, an dem alles hängt, ist der vierte.
Messen statt raten
„Das benutzt bestimmt keiner mehr" ist keine Grundlage. Es gibt drei Ebenen, auf denen sich Nutzung tatsächlich feststellen lässt, und sie beantworten unterschiedliche Fragen.
Die Zugriffsebene ist die billigste. Die Protokolle des Webservers beantworten für jede Adresse, wann sie zuletzt aufgerufen wurde, und decken damit alles ab, was eine eigene Seite oder Schnittstelle hat.
Die Datenebene beantwortet die Frage für alles, was schreibt. Ein Blick auf den jüngsten Datensatz je Tabelle sortiert in zehn Minuten, was lebt und was steht:
-- Wann wurde zuletzt in diese Tabellen geschrieben? Alles, was
-- hier zwei Jahre alt ist, gehoert auf die Liste.
SELECT 'export_auftrag' AS tabelle, max(erstellt_am) FROM export_auftrag
UNION ALL
SELECT 'faxversand', max(erstellt_am) FROM faxversand
UNION ALL
SELECT 'altbericht', max(erstellt_am) FROM altbericht
ORDER BY 2 NULLS FIRST;Die Codeebene ist für alles zuständig, was weder eine Adresse noch eine Tabelle hat: die Methode, die vielleicht noch aus einem Stapellauf gerufen wird. Dafür gibt es einen alten und sehr wirksamen Trick, einen Grabstein: eine Zeile, die meldet, dass jemand hier vorbeikam.
public function alteRabattstaffel(Bestellung $bestellung): Rabatt
{
// Grabstein, gesetzt am 02.11.2026. Wenn bis Februar niemand
// hier vorbeikommt, faellt die Methode samt ihrer Tabelle.
// Warnstufe, nicht Info: eine Info-Zeile sieht niemand.
$this->logger->warning('grabstein', [
'stelle' => __METHOD__,
'aufrufer' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'] ?? '?',
'seit' => '2026-11-02',
]);
return $this->rechneAlt($bestellung);
}Der Aufrufer im Protokoll ist der wichtige Teil. Die Information „wurde aufgerufen" führt zu einer Diskussion, die Information „wurde von dieser einen nächtlichen Auswertung aufgerufen" führt zu einer Entscheidung.
Die Wartezeit, die alle falsch ansetzen
Der Schritt, an dem dieses Vorgehen steht und fällt, ist die Beobachtungsdauer nach dem Grabstein. Die übliche Wahl sind zwei bis vier Wochen, und sie ist bei einer ganzen Klasse von Funktionen falsch.
Vieles in einem Geschäftssystem läuft nicht täglich. Der Jahresabschluss läuft einmal im Jahr. Die Inventur einmal im Jahr. Die Meldung an die Berufsgenossenschaft einmal im Jahr, im Februar. Eine Funktion, die vier Wochen lang unbenutzt war, kann trotzdem die sein, ohne die im nächsten Februar niemand seine Meldung abgeben kann.
Die Regel, die daraus folgt, ist unbequem und spart den einen richtig teuren Fehler: Die Beobachtungsdauer richtet sich nach dem Takt der Sache, nicht nach dem Takt des Vorhabens. Für alles, was nach Abschluss, Meldung, Inventur, Jahreswechsel oder Saison klingt, sind es dreizehn Monate. Für den Rest reichen vier bis acht Wochen.
Wer dreizehn Monate nicht warten will, muss nicht raten: Ein Blick in die Daten der letzten drei Jahre zeigt, ob diese Funktion je im Februar lief.
Der Schalter dafür ist derselbe wie beim Einführen einer Funktion: Feature Flags in einem Monolithen ohne Framework-Support.
Abschalten ist nicht löschen
Zwischen „wir glauben, das braucht niemand" und „es ist weg" gehört ein Zustand, in dem die Funktion nicht mehr erreichbar, aber in einer Minute wieder da ist.
Der Schalter dafür ist ein Feature-Flag, und die Abfolge ist immer dieselbe: Ankündigung an die Nutzer mit Datum, dann Abschaltung, dann die Wartezeit von oben. In dieser Zeit meldet sich entweder jemand, und dann hat man eine Anforderung statt einer Vermutung, oder es meldet sich niemand, und dann ist das Löschen keine Entscheidung mehr, sondern eine Aufräumarbeit.
Die Ankündigung ist dabei kein Formalismus. Sie ist der Unterschied zwischen einem Ausfall, der als Ausfall wahrgenommen wird, und einer Abschaltung, die als Abschaltung wahrgenommen wird. Dieselbe Sache, zwei sehr verschiedene Gespräche.
Was mitgeht, und warum halb gelöscht schlimmer ist
Eine Funktion ist mehr als ihr Code, und eine halb entfernte Funktion ist schlimmer als eine, die noch ganz da ist: Sie sieht aus, als gäbe es sie, und tut nichts.
Mit weg gehören: die Tabellen und ihre Fremdschlüssel, der nächtliche Lauf, der sie füllte, die Berechtigungen, die es nur für sie gab, die Konfigurationswerte, die Übersetzungen, die Einträge im Menü, die Überwachung, die jetzt eine Kennzahl beobachtet, die immer null ist, und der Abschnitt in der Anleitung.
Die Überwachung wird dabei am häufigsten vergessen, und sie rächt sich am unangenehmsten: Ein Alarm, der nie mehr auslöst, oder schlimmer, einer, der ab jetzt jede Nacht auslöst, weil ein Wert fehlt, den niemand mehr liefert. Nach zwei Wochen schaltet jemand den Alarm stumm, und drei Monate später ist derselbe Kanal für einen echten Vorfall taub.
Praktisch heißt das: Die Liste dessen, was mitgeht, entsteht beim Messen, nicht beim Löschen. Wer die Tabellen erst sucht, wenn der Code weg ist, findet sie nicht mehr.
Die Daten überleben die Funktion
Ein Unterschied, der Ärger spart: Das Entfernen einer Funktion ist keine Entscheidung über ihre Daten.
Für einen Teil dieser Daten gibt es eine Aufbewahrungspflicht, die nichts damit zu tun hat, ob die Anwendung sie noch anzeigen kann. Rechnungen, Buchungen und alles, was daran hängt, bleiben zehn Jahre, auch wenn der Weg dorthin gelöscht wird.
Das Vorgehen ist deshalb zweigeteilt: Der Code geht, die Daten gehen in ein Archiv, das lesbar bleibt, ohne dass die Anwendung dafür gebraucht wird. Eine Datei mit einer Beschreibung daneben genügt, und sie ist in einem Jahr mehr wert als eine Tabelle im Bestand, die niemand mehr zu deuten weiß.
Der umgekehrte Fehler kommt genauso oft vor: Daten aufzuheben, für die es keine Pflicht und keinen Zweck gibt, ist nicht vorsichtig, sondern eine Verarbeitung ohne Grundlage.
Dieselbe Übung für Zwischenspeicher statt für Funktionen steht in Der Cache als Schuld.
Woran man den Erfolg misst
Zum Schluss die Frage, was so ein Aufräumen eigentlich einbringt, und die naheliegende Antwort ist die falsche. Entfernte Zeilen sind keine Kennzahl; sie sagen nichts darüber, ob das System danach leichter zu ändern ist.
Was etwas sagt, sind drei andere Zahlen, und alle drei bewegen sich sofort: die Laufzeit der Testsuite, die Zahl der Tabellen, die bei der nächsten Migration mitmüssen, und die Zahl der Stellen, die ein Upgrade der Sprache anfasst.
Der Nebeneffekt ist größer als alle drei. In einem System, aus dem regelmäßig etwas entfernt wird, weiß das Team, was benutzt wird, weil es das ein paar Mal nachgesehen hat. Das ist eine andere Art von System als eines, in dem seit Jahren nur addiert wurde, und der Unterschied zeigt sich bei jeder Modernisierung, die danach kommt.
Dieser Artikel gehört zu einer Reihe über Systeme, die es schon gibt. Der Rückblick ordnet alle Artikel der Reihe nach Anlass.

