Bessere Codewartbarkeit durch Refaktorisierung

Code Smells – wenn es stinkt, wechsle es!
Kommentare

Es gibt in der Informatik einen schlauen Satz, der immer wieder gerne zitiert wird: Alle Programmierer machen Fehler, gute Programmierer schreiben Tests, um sie zu finden. Wenn wir von Fehlern reden, dann meinen wir meistens inkorrekte Stellen im Code. Also zum Beispiel eine Methode, die nicht das macht, was sie unter bestimmten Umständen soll. Unit-Tests wären hier das Mittel der Wahl, um diese zu finden und nachhaltig zu korrigieren.

Es gibt aber noch eine zweite Klasse von Fehlern, die nicht immer gesehen wird. Code sollte zukünftig wartbar und verständlich sein, denn sonst kann das beste Programm nicht so lange leben, wie es das vielleicht verdient hätte. Man schleppt sich in den meisten Fällen von Workaround zu Workaround. Irgendwann kommt man somit an die Stelle, an der man sich fragen muss, ob es noch wirtschaftlich ist, das Projekt weiter zu pflegen.

Unschöne Stellen im Code, die die Wartbarkeit einschränken, nennen wir „Code Smells“ oder „übelriechenden Code“. Geprägt haben diesen Ausdruck Kent Beck und Martin Fowler. Die Definitionen eines Code Smells zu kennen, erlaubt die Identifikation von bekannten Unsauberkeiten. Ähnlich wie die Verwendung von Entwurfsmustern zur Lösung wiederkehrender Anforderungen, erlauben dann Refaktorisierungen, also das Verbessern des Codes ohne Veränderung der Funktionalität, die Beseitigung der Smells. Mit der Kenntnis um Code Smells können potenzielle Problemherde frühzeitig erkannt und strukturiert beseitigt werden. Einige Code Smells sind dabei typisch für eine Programmiersprache, einige sind sprachunabhängig. Es ist wichtig, seinen Code sauber zu halten, denn zwischen 50 und 70 Prozent (siehe Software Wartung und Evolution [PDF] und Estimating Software Maintenance [PDF]) der Kosten eines Softwareprojekts entfallen auf die Wartung und sind somit entscheidend für den mittel- bis langfristigen Erfolg einer Anwendung. Im Folgenden sollen gängige Code Smells aus dem PHP-Umfeld erläutert werden. Beginnen möchten wir bei der Vorstellung der übelriechenden Codefragmente mit einer kurzen Erläuterung, gefolgt von einem einfachen Beispiel, um abschließend Regeln und Werkzeuge zur Beseitigung vorzustellen.

„Duplizierter Code“-Smell

Don’t repeat yourself (DRY) ist ein Paradigma objektorientierter Softwareentwicklung: Wiederhole dich nicht. Wiederholung bedeutet einen enormen Mehraufwand bei Änderungen der Anforderungen. Bei der Umsetzung neuer Anforderungen muss im gesamten Projekt nach den zu ändernden Stellen gesucht werden. Das benötigt Zeit und führt mittelfristig zu großen Unterschieden in den Teilen des Projekts, die ursprünglich den gleichen Code und damit die gleiche Funktionalität beherbergen sollten. Im Projektalltag wird es schwierig, alle Stellen im Auge zu behalten, und Änderungen werden sehr bald nur noch an den Stellen des Projekts ausgeführt, an denen sie gerade notwendig sind. Die Unterschiede sind bald so groß, dass sie nicht mehr überblickt werden können und Änderungen somit sehr schwierig und irgendwann unmöglich werden. Um das zu verhindern und das Projekt auch bei großen Änderungen der Anforderungen und beim Ausbau flexibel und wartbar zu halten, sollte man bei der Vermeidung von dupliziertem Code anfangen. „Don’t repeat yourself“ ist eines der wichtigsten Paradigmen, um das zu erreichen. Duplizierter Code kann an verschiedenen Stellen eines Projekts vorkommen. Ein Beispiel ist die Verwendung eines Debug Outputs in mehreren Methoden einer Klasse.

Listing 1: Beispiel für „Duplizierter Code“-Smell

class User
{
    ...
    public function getUserName()
    {
        if ( DEBUG )
        {
            echo( "UserName: " . $this->userName . "n" );
        }
        return $this->userName;
    }

    public function getUserAdress()
    {
         if ( DEBUG )
        {
            echo( "UserAdress: " . $this->userAdress . "n" );
        }
        return $this->userAdress;
    }
    ...
}

Diesen Smell zu erkennen, ist sehr einfach. Er fällt ins Auge und kann über die Refactoring-Regel Methode extrahieren (nach Fowler) einfach eliminiert werden.

Listing 2: Listing 1 mit „Methode extrahieren“ aufgeräumt

class User
{
    ...
    public function getUserName()
    {
        $this->printDebug( "UserName: ", $this->userName );
        return $this->userName;
    }

    public function getUserAdress()
    {
        $this->printDebug( "UserAdress: ", $this->userAdress );
        return $this->userAdress;
    }

    private function printDebug( $outputDescriptionValue, $outputValue )
    {
        if ( DEBUG )
        {
            echo( $outputDescriptionValue . $outputValue . "n" );
        }
    }
    ...
}

Ein anderes Beispiel für diesen Smell ist die Verwendung gleicher Funktionalität in zwei Klassen, die von einer Basisklasse erben.

Listing 3: Smell bei gleicher Funktionalität in zwei Klassen, die von einer Basisklasse erben

class InternetUser extends User
{
    private $userName;
    ...

    public getInternetUserName()
    {
      return $this->userName;
    }
}

class PrintUser extends User
{
    private $userName;
    ...

    public getPrintUserName()
    {
      return $this->userName;
    }
    ...
}

Hier kann man nun (nach Fowler) die Refactoring-Regel Feld nach oben verschieben anwenden und einfach $userName nach User auslagern.

Listing 4: Listing 3 mittels „Feld nach oben verschieben“ refaktorisieren

class InternetUser extends User implements InterfaceInternetUser
{
    ...
    public function getInternetUserName()
    {
      return $this->getUserName();
    }
    ...
}

class PrintUser extends User implements InterfacePrintUser
{
    ...
    public function getPrintUserName()
    {
      return $this->getUserName();
    }
    ...
}

class User
{
    private $userName;
    ...
    public function getUserName()
    {
      return $this->userName;
    }
    ...
}

Die beiden abgeleiteten Klassen nutzen nun getUserName() von der Basisklasse. Wegen der verwendeten Interfaces bleiben die beiden Methoden getPrintUserName() und getInternetUserName() bestehen.

Wie lokalisiert man denn nun duplizierten Code in einem oder mehreren Projekten? Duplizierten Code in großen Projekten ohne Toolunterstützung zu finden, ist nicht so einfach. Zuerst einmal muss man die Stellen identifizieren, die ähnliche Aufgaben übernehmen. Es gibt in allen größeren PHP-Webprojekten Bereiche, die die gleichen Aufgaben in unterschiedlichen Ausprägungen ausführen. Ein Bereich ist in einem MVC-Projekt die View. Hier findet man oft duplizierten Code, da ähnliche Ausgaben zum Kopieren verführen. Aber genau hier muss DRY oberstes Prinzip sein. Einige Frameworks wie Symfony helfen bei der Vermeidung von dupliziertem Code in der View mit Komponenten, Layouts und Templates.

Ein Tool zur Identifikation von dupliziertem PHP-Code ist phpcpd (PHP Copy Paste Detection). Dieses Tool von Sebastian Bergmann wird auf der Kommandozeile ausgeführt und man bekommt eine Idee von den Stellen im Projekt, die redundant sein könnten. phpcpd arbeitet auf der Ebene der Tokens und erkennt (per Default) Codestellen mit 70 gleichen Tokens oder fünf gleichen Zeilen als Duplikate. Diese Parameter können konfiguriert werden. Leider kann das Programm momentan nur exakte Kopien von Codestellen erkennen. Die Änderung von Variablennamen hebelt somit das Erkennen aus.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -