Redundanter Code

aus Wikipedia, der freien Enzyklopädie
(Weitergeleitet von Toter Code)
Wechseln zu: Navigation, Suche
Die Seiten Redundanter Code und Dead code elimination überschneiden sich thematisch. Hilf mit, die Artikel besser voneinander abzugrenzen oder zusammenzuführen (→ Anleitung). Beteilige dich dazu an der betreffenden Redundanzdiskussion. Bitte entferne diesen Baustein erst nach vollständiger Abarbeitung der Redundanz und vergiss nicht, den betreffenden Eintrag auf der Redundanzdiskussionsseite mit {{Erledigt|1=~~~~}} zu markieren. Sebastian.Dietrich 19:33, 1. Apr. 2013 (CEST)

Redundanter Code (auch redundanter Kode) ist in der Programmierung der Begriff für Quelltext oder übersetzten Quelltext eines Computerprogramms, der eine Form der Redundanz enthält.[1]

Spezialformen des redundanten Codes sind Toter Code (englisch dead code - ausgeführter Quelltext, dessen Ergebnis nie verwendet wird)[2] - und Unerreichbarer Code (englisch unreachable code, auch unerreichbarer Kode) - Quelltext, der aufgrund der Ablaufsteuerung des gesamten Programms in keinem möglichen Programmablauf erreicht und darum nie ausgeführt werden kann. Oft wird auch die Bezeichnung toter Code synonym mit redundanter Code verwendet.

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. Ob es sich in diesen Fällen noch um toten Code handelt, ist umstritten.

Alle Formen des Redundanten Codes werden aus einigen Gründen als unerwünscht gesehen. So vergeudet toter und redundanter Code CPU-Zeit, die anderen Threads fehlt. Darüber hinaus vergeuden alle Arten redundanten Codes Speicherplatz und können zu unnötigem Caching von Befehlen im Befehlscache der CPU führen. Redundanter Code reduziert die Wartbarkeit, erhöht den Aufwand, Code zu dokumentieren und führt zu mehr Fehlern. [3]. Er wirkt sich auch oft negativ auf Metriken aus und erschwert Programmverifikation.

Beispiel[Bearbeiten]

  1. int foo (int x) {
    
  2.   int y = 100/x; //toter Code, da y nicht verwendet wird
    
  3.   int z = x*x; //redundanter Code zu 2 Zeilen weiter unten
    
  4.   if (z >= 0) {
    
  5.     return x*x; //redundanter Code zu 2 Zeilen weiter oben
    
  6.   }
    
  7.   return -1; //unerreichbarer Code, da z immer >= 0 ist
    
  8. }
    
  • 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.

Gründe[Bearbeiten]

Redundanter Code entsteht unter anderem durch

  • Programmierfehler in komplexen bedingten Verzweigungen;
  • eine Folge von internen Umwandlungen, die von einem optimierenden Übersetzer durchgeführt werden;
  • unvollständiges 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 vom Entwicklungscode, der noch vom 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.

Analyse[Bearbeiten]

Redundanten 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 des redundanten Codes (insbesondere toter und unerreichbarer Code) gefunden werden. In einigen Sprachen (wie z.B. Java) sind einige Formen von unerreichbarem Code ausdrücklich verboten und führen zu Compilierungsfehlern.

In großen Softwareprojekten ist es manchmal schwierig, redundanten 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.[4]

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]

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

  1. int foo (int x) {
    
  2.     return x*x;
    
  3. }
    

Die Codeoptimierung redundanten Codes funktioniert bei allen Arten redundanten Codes ähnlich, meist durch einfaches Löschen der betreffenden Codestellen. Einzig 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 Compilierung wird Dead code elimination genannt. Sie kann für toten Code durch Variablenanalyse, für unerreichbaren Kode mit Datenflusskontrolle gemacht werden.

Siehe auch[Bearbeiten]

Weblinks[Bearbeiten]

  • Dead Code Detector (DCD) - Applikation für das Auffinden toten und unerreichbaren Codes
  • DCD Alternatives Vergleich unterschiedlicher Werkzeuge zur statischen Code Analyse mit Funktionalitäten zum Auffinden redundanten Codes
  • UCDetector Eclipse PlugIn für das Auffinden toten und unerreichbaren Codes

Einzelnachweise[Bearbeiten]

  1.  Saumya K. Debray, William Evans, Robert Muth, Bjorn De Sutter, ACM Transactions on Programming Languages and Systems (TOPLAS) (Hrsg.): Compiler techniques for code compaction. 22, Nr. 2, März 2000, S. 378–415 (http://doi.acm.org/10.1145/349214.349233, abgerufen am 26. August 2012).
  2. Appel, A. W. 1998 Modern Compiler Implementation in Java. Cambridge University Press.
  3.  David Noel Card, Victor E Church, William W. Agresti: An empirical study of software design practices. In: IEEE Press (Hrsg.): IEEE Transactions on Software Engineering. 12, Nr. 2, Februar 1986, ISSN 0098-5589, S. 264-271.
  4. Douglas W. Jones Dead Code Maintenance, Risks 8.19 (Feb. 1, 1989)
  • Muchnick S. S. 1997 Advanced Compiler Design and Implementation. Morgan Kaufmann.