Fünf Hinweise, dass Code überarbeitet werden sollte

Im Moment sitze ich gerade vor einer größeren Refactoring Aufgabe. In unserer Software gibt es einige Klassen die sich sehr ähnlich sind, die aber alle durch Kopieren und Anpassen erstellt wurden. Bei ein paar Tests stellte sich dann heraus, dass es einen kleinen wenn auch grundlegenden Fehler gab – nichts Dramatisches. Es muss eben geändert werden – in ca. 20 Klassen und dazugehörigen Unit-Tests. Weil das zeitaufwändig und anfällig für Fehler ist und gerade etwas Zeit übrig war, haben wir uns dann für ein Refactoring der Klassen entschieden um gemeinsamen Code zusammenzufassen.

Dieses Refactoring wollte ich schon eine ganze Weile lang machen, aber es ist natürlich auch mit gewissen Risiken verbunden. Nach jeder Änderung muss die Software getestet werden um sicher zu stellen, dass sich keine neuen Fehler einschleichen. Deshalb beschäftigt mich praktisch ständig die Frage: Wann ist also der Punkt erreicht an dem man funktionierenden Code überarbeiten sollte?

Folgende Aussagen habe ich mal zusammengetragen, die für mich darauf hinweisen, dass ein Refactoring sinnvoll wäre:

1. Es gibt viele Codeteile die sich ähneln und sich nur in wenigen Punkten unterscheiden.

Ein Beispiel für dieses Problem habe ich oben in der Einleitung beschrieben. In solchen Fällen lassen sich oft Basisklassen erstellen, welche den gemeinsamen Code bündeln. Durch Vererbung behalten alle vorhandenen Klassen ihren Funktionsumfang aber das Testen wird stark vereinfacht und auch die Fehleranfälligkeit reduziert sich.

2. Eine Klasse hat mehrere unterschiedliche Aufgaben.

Ich mag kleine Klassen / Module. Jedes Codefragment sollte eine klar definierte Aufgabe haben. Dadurch lässt sich leichter prüfen ob das Codeteil tut was es soll und wie verschiedene Teile zusammen spielen.

Beispiel: Eine Klasse, die Daten ändert, auf der Oberfläche anzeigt und nebenbei auch noch Berechnungen vornimmt, lässt sich schwer “greifen” und testen. Es ist übersichtlicher wenn es für jede dieser drei Aufgaben eine eigene Klasse gibt.

3. Es gibt viele implizite Abhängigkeiten zwischen den Codeteilen.

Jede Abhängigkeit zwischen Codeteilen sollte über Schnittstellen sichtbar sein.

Beispiel: Eine Klasse verwendet einen Logger um Informationen in einer Logdatei zu speichern. Weil man den Logger nicht jedes Mal neu initialisieren möchte wird er in einer statischen Utility-Klasse (MeinLogger) angelegt:

public class Beispiel1
{
   /**
    * Berechne eine Summe und logge das Ergebnis.
    */
   public void logSumme(int 1, int 2)
   {
       String infoText = String.format("%d + %d = %d", 
          zahl1,
          zahl2,
          zahl1+zahl2);

       // Nutze den statischen MeinLogger
       MeinLogger.info(infoText);
   }
}

Wenn jemand diese Klasse verwendet würde ihm nicht auffallen, dass hier ein Logger verwendet wird, geschweige denn wo man dannach das Ergebnis finden könnte.

Besser wäre es, dem Nutzer die Möglichkeit zu geben, den Logger selbst festzulegen:

public class Beispiel2
{
   private Logger logger;

   /**
    * Konstruktor der MeinLogger als Standard verwendet.
    */
   public Beispiel2()
   {
     this(MeinLogger);
   }

   /**
    * Konstruktor in dem der Logger konfiguriert werden kann.
    */
   public Beispiel2(Logger neuerLogger)
   {
     this.logger = neuerLogger;
   }

   /**
    * Berechne eine Summe und logge das Ergebnis.
    */
   public void logSumme(int 1, int 2)
   {
       String infoText = String.format("%d + %d = %d", 
          zahl1,
          zahl2,
          zahl1+zahl2);

       // nutze den Logger, der im Konstruktor
       // gesetzt wurde.
       this.logger.info(infoText);
   }
}

Diese Klasse lässt sich genauso einfach aufrufen wie die erste, aber durch den zweiten Konstruktor wird deutlich, dass intern ein Logger verwendet wird. Wenn man möchte, kann man diesen selber festlegen. Tut man das nicht, wird der Standard (MeinLogger) verwendet und verhält sich so wie Beispiel1.

4. Der Code bildet das System nicht so ab wie es vom Anwender / Kunden verstanden wird.

Das ist auf den ersten Blick ein eher kleines Problem, kann sich aber im Verlauf eines größeren Projektes stark auf die Wartbarkeit auswirken. Die Kommunikation zwischen Entwicklern und Anwendern wird sehr erschwert wenn beide Seiten von unterschiedlichen Dingen sprechen.

5. Der Code bildet das System nicht so ab wie es vom Entwickler verstanden wird.

Dieser Punkt ist kritisch. Wenn nicht alle Entwickler das gleiche Verständnis davon haben wie das System funktionieren soll kommt es oft dazu, dass es Unklarheiten gibt. Wichtig ist es, diese Unklarheiten zu erkennen und zu beseitigen. Sobald ein gemeinsames Verständnis des Systems vorliegt sollte der Code entsprechend angepasst werden.

social