Toter Code

Optimierungsmethode für Computerprogramme

Toter Code (englisch dead code) ist in der Programmierung ein Begriff für Teile eines Computerprogramms, „die an keiner Stelle im Programm verwendet werden“.[1] In erweitertem Sinn kann toter Code als Sonderform redundanten Codes (= „überflüssig“) betrachtet werden. Toter Code kann Anweisungen/Befehle enthalten oder sich auf nicht verwendete Datendeklarationen beziehen.

Unerreichbarer Code ist ein Programmteil, der durch keinen möglichen Kontrollfluss erreicht und deshalb erst gar nicht ausgeführt werden kann; auch hierbei wird mitunter von totem Code gesprochen.

Bedeutung

Bearbeiten

Alle Formen toten Codes gelten aus verschiedenen Gründen als unerwünscht bzw. Mangel in der Softwarequalität.

Je nach Situation kann toter Code auch bewusst entstehen: Er soll beispielsweise einen vorläufigen oder ehemaligen Quelltextteil konservieren. Häufig liegt jedoch auch ein Programmfehler vor, dessen Entdeckung ein Ziel beim Softwaretest ist.

Obwohl die Ergebnisse von totem Code nie verwendet werden, kann er Ausnahmebehandlungen auslösen oder globale Status beeinflussen. So kann eine Codeänderung die Programmausgabe verändern und unbeabsichtigte Programmfehler verursachen. In solchen Fällen ist es umstritten, ob weiterhin von totem Code gesprochen werden kann.

Beispiel

Bearbeiten
int foo (int x) {
  int y = 100 / x;  // toter Code, da y nicht verwendet wird
  int z = x * x;    // redundanter Code zu 2 Zeilen weiter unten
  if (z >= 0) {     // unnoetiger Code, da die Abfrage immer wahr ist
    return x * x;   // redundanter Code zu 2 Zeilen weiter oben
  }
  return -1;        // unerreichbarer Code, da z immer >= 0 ist
}
  • Toter Code – Im Beispiel wird in Zeile 2 die Zahl 100 durch x dividiert, das Ergebnis aber nie verwendet. Es handelt sich somit um toten Code. Ist allerdings x Null, wird eine Ausnahme ausgelöst. Eine Entfernung dieses Codes führt somit zu einer Änderung der Funktionalität. Da Ausnahmen auszulösen aber niemals ein Teil der Funktionalität sein sollte, handelt es sich dabei um eine fehlerhafte Funktionalität, die zu entfernen ist.
  • Redundanter Code – Im Beispiel wird x in Zeile 3 und 5 quadriert, ohne dass x dazwischen geändert wird. Das Ergebnis ist somit immer dasselbe, damit ist der Code redundant. return z wäre somit in Zeile 5 angebracht.
  • Unerreichbarer Code – Im Beispiel wird die Zeile 7 niemals erreicht, da die Abfrage z >= 0 in Zeile 4 immer wahr ist und in Zeile 5 die Methode verlassen wird. Somit handelt es sich bei Zeile 7 um unerreichbaren Code.
  • Unnötiger Code – Nachdem die Abfrage z >= 0 in Zeile 4 immer wahr ist, ist sie sinnlos und kann ebenso entfernt werden. Unnötiger Code fällt zwar in keine der drei genannten Kategorien, wird aber landläufig oft auch als toter Code bezeichnet.

Toter Code kann unter anderem entstehen durch

  • Programmierfehler; bei (noch) unvollständigem Testen eines neuen oder veränderten Programms;
  • einen neuen Fehler, der während der Korrektur eines Fehlers von einem Programmierer unbeabsichtigt hinzugefügt wurde und den unerreichbaren Code umgeht und während des Tests unentdeckt blieb;
  • überflüssigen Code, den ein Programmierer nicht entfernen wollte, weil er mit funktionalem Code vermischt ist;
  • überflüssigen Code, den ein Programmierer vergaß zu löschen;
  • vorher sinnvollen Code, der nie mehr erreicht werden kann, weil die Eingabedaten sich verändert haben, so dass dieser Code nie wieder aufgerufen wird;
  • komplexen überflüssigen Code, der absichtlich nicht entfernt, aber unerreichbar gemacht wurde, damit man ihn bei Bedarf „wiederbeleben“ kann;
  • defektlokalisierende Konstrukte (Eng. debugging constructs) und Reste von Entwicklungscode, der noch aus dem Programm entfernt werden muss.

In den letzten fünf Fällen ist der derzeit unerreichbare Code eine Altlast, d. h. Code, der früher sinnvoll war, aber nicht mehr benötigt wird.

Beispiel

Oft wird absichtlich toter Code erzeugt, um Ausgaben während der Programmentwicklung später zu deaktivieren:

int main()
{
#define DEBUG  0
    int a = 3;
    // ...
    if (DEBUG)
        printf("%d\n", a);
    // ...
    return a;
}

Während der Entwicklungsphase kann hier der Wert der Variablen a ausgegeben werden, falls das Makro DEBUG auf einen Wert ungleich Null (wahr) gesetzt wird. Wenn diese Kontrollausgabe nicht mehr benötigt wird, setzt man den Wert auf 0 und der Präprozessor erkennt und entfernt das nun tote Stück Code.

Toten Code zu entdecken ist eine Form von statischer Codeanalyse und benötigt eine genaue Analyse der Ablaufsteuerung, um den Code unabhängig von den Variablen und anderen Laufzeitbedingungen zu finden. Mit Hilfe geeigneter Analysewerkzeuge kann ein Großteil toter und unerreichbarer Codeteile gefunden werden. In einigen Sprachen (wie z. B. Java) sind einige Formen von unerreichbarem Code ausdrücklich verboten und führen zu Kompilierungsfehlern.

In großen Softwareprojekten ist es manchmal schwierig, toten Code zu erkennen und zu entfernen, insbesondere wenn ganze Module davon betroffen sind. Der Testgerüstbau kann solchen Code als noch „lebendig“ zeigen, und es kann sogar sein, dass aus vertraglichen Gründen der irrelevante Code geliefert werden muss.[2]

In einigen Fällen ist ein praktischer, nicht allzu aufwändiger Ansatz eine Kombination von einfachen Unerreichbarkeitskriterien und die Verwendung von einem Profiler, um komplexe Fälle zu bearbeiten. Mit Profiling kann man nicht die Unerreichbarkeit von Code beweisen. Es ist aber eine gute heuristische Methode, um potenziell unerreichbaren Code zu entdecken. Wird einmal ein Codeteil als suspekt gesehen, können andere Methoden wie z. B. wirksamere Codeanalysewerkzeuge verwendet oder die Analyse von Hand durchgeführt werden. Dadurch kann man dann entscheiden, ob der gefundene Codeteil wirklich unerreichbar ist oder nicht.

Optimierung

Bearbeiten

Seit Mitte der 1990er Jahre ist es Stand der Technik, dass Compiler und Linker unbenutzte Codeabschnitte erkennen und entfernen. Diese Optimierungstechnik bezeichnet man als dead code elimination. Seit Mitte der 2010er Jahre haben äquivalente Techniken in IDEs (Anzeige von unbenutzten Code schon beim Editieren) und in Debugger (Code, der im aktuellen Durchlauf nicht mehr erreicht werden kann, wird beispielsweise abgedunkelt) Einzug gehalten.

Die korrekte, redundanzfreie Umsetzung des oberen Beispiels würde folgendermaßen aussehen:

int foo (int x) {
    return x*x;
}

Die Optimierung toten Codes funktioniert bei allen Arten toten Codes ähnlich, meist durch einfaches Entfernen der betreffenden Codestellen. Bei der Entfernung von totem Code ist besondere Vorsicht geboten, da toter Code Seiteneffekte haben kann, die aus dem Code selbst nicht erkennbar sind.

Compileroptimierungen sind typischerweise konservative Ansätze, um toten oder unerreichbaren Code zu entfernen. Ist eine Mehrdeutigkeit bezüglich Programmverhalten vorhanden, wird der Code nicht entfernt. Die Optimierung während der Kompilierung wird Dead code elimination genannt. Sie kann für toten Code durch Variablenanalyse, für unerreichbaren Kode mit Datenflusskontrolle gemacht werden.

Der Code kann auch durch Transformationen, die der Compiler durchführt, unerreichbar werden, wie z. B. die sogenannte common subexpression elimination (Entfernen gemeinsamer Teilausdrücke).

In der Praxis hat die Ausgereiftheit der Analyse einen entscheidenden Einfluss auf den Anteil des gefundenen unerreichbaren Codes. Z. B. kann durch constant folding und einfache Flussanalyse gezeigt werden, dass der Funktionsaufruf foo() im folgenden Beispiel unerreichbar ist:

int i = 2 + 1;

if (i == 4)
   foo();

Einer vollständigen Analyse zur Kompilierungszeit sind jedoch theoretische Grenzen gesetzt, die eng mit der Entscheidbarkeitsproblematik der theoretischen Informatik verbunden sind.

Öffentliche Wahrnehmung

Bearbeiten

Im November 2010 veröffentlichte Microsoft eine neue Version des Internet Explorers, der scheinbar alle anderen Browser in puncto JavaScript-Geschwindigkeit weit hinter sich ließ. Es stellte sich jedoch schon bald heraus, dass Microsoft eine spezielle Implementation der dead code elimination nutzte, um sich an die Spitze eines bekannten JavaScript-Benchmarks zu katapultieren. In anderen Benchmarks waren die Resultate eher im Mittelfeld.[3]

Literatur

Bearbeiten
  • S. S. Muchnick: Advanced Compiler Design and Implementation. Morgan Kaufmann, 1997.
Bearbeiten
  • DCD Alternatives Vergleich unterschiedlicher Werkzeuge zur statischen Code Analyse mit Funktionalitäten zum Auffinden toten Codes

Tools zum Auffinden toten/unerreichbaren Codes

Einzelnachweise

Bearbeiten
  1. Fernuni Hagen: Ein Eclipse Plugin zum Aufspüren toter Codefragmente (…) , PDF.
  2. Douglas W. Jones: Dead Code Maintenance. (Memento vom 8. Juli 2011 im Internet Archive) Risks 8.19, 1. Februar 1989
  3. Herbert Braun: Browser-Debatte: Hat Microsoft geschummelt? Heise Online, 18. November 2010.