Artikel

Mai 2010 | Artikel

Code Smells - wenn es stinkt, wechsle es!

(Link zum Artikel: http://www.it-republik.de/php/artikel/3097)

Bessere Codewartbarkeit durch Refaktorisierung

Text: Nils Langner und Mike Lohmann
  • Teilen
  • kommentieren
  • empfehlen
  • Bookmark and Share
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.
Teil 1   Teil 2   Teil 3   

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

  1. class User
  2. {
  3. ...
  4. public function getUserName()
  5. {
  6. if ( DEBUG )
  7. {
  8. echo( "UserName: " . $this->userName . "\n" );
  9. }
  10. return $this->userName;
  11. }
  12. public function getUserAdress()
  13. {
  14. if ( DEBUG )
  15. {
  16. echo( "UserAdress: " . $this->userAdress . "\n" );
  17. }
  18. return $this->userAdress;
  19. }
  20. ...
  21. }

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

  1. class User
  2. {
  3. ...
  4. public function getUserName()
  5. {
  6. $this->printDebug( "UserName: ", $this->userName );
  7. return $this->userName;
  8. }
  9. public function getUserAdress()
  10. {
  11. $this->printDebug( "UserAdress: ", $this->userAdress );
  12. return $this->userAdress;
  13. }
  14. private function printDebug( $outputDescriptionValue, $outputValue )
  15. {
  16. if ( DEBUG )
  17. {
  18. echo( $outputDescriptionValue . $outputValue . "\n" );
  19. }
  20. }
  21. ...
  22. }

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

  1. class InternetUser extends User
  2. {
  3. private $userName;
  4. ...
  5. public getInternetUserName()
  6. {
  7. return $this->userName;
  8. }
  9. }
  10. class PrintUser extends User
  11. {
  12. private $userName;
  13. ...
  14. public getPrintUserName()
  15. {
  16. return $this->userName;
  17. }
  18. ...
  19. }

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

  1. class InternetUser extends User implements InterfaceInternetUser
  2. {
  3. ...
  4. public function getInternetUserName()
  5. {
  6. return $this->getUserName();
  7. }
  8. ...
  9. }
  10. class PrintUser extends User implements InterfacePrintUser
  11. {
  12. ...
  13. public function getPrintUserName()
  14. {
  15. return $this->getUserName();
  16. }
  17. ...
  18. }
  19. class User
  20. {
  21. private $userName;
  22. ...
  23. public function getUserName()
  24. {
  25. return $this->userName;
  26. }
  27. ...
  28. }

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.

Teil 1   Teil 2   Teil 3   

Kommentare

Gravatar Jonathan Schmid 20.05.2010
um 14:34 Uhr
Den ganzen Artikel schon auf der Website? Wenn das so weiter geht, kann ich ja beruhigt die Print-Version abbestellen ;) #zitieren
Gravatar Robert 20.05.2010
um 15:03 Uhr
Die IPC kommt ja bald, und da wollte wir nicht mehr länger mit dem Artikel warten - in Berlin können Mike und Nils für Kommentare dann gleich persönlich Rede und Antwort stehen:-) Und du willst doch nicht wirklich unser kostbares Hochglanz-Magazin abbestellen?


Every time you do that, Domo Con kills a kitten!
#zitieren
Gravatar Markus Möller 25.05.2010
um 09:31 Uhr
Sehr guter Artikel. Er hält mir wieder mal den Spiegel vor und ermahnt mich, sauber zu programmieren. Neben den beiden erwähnten Bücher möchte ich auch "Design Patterns. Elements of Reusable Object-Oriented Software" empfehlen. #zitieren
Gravatar Robert Hartung 06.06.2010
um 10:15 Uhr
Toller Artikel, kannte schon einiges, anderes aber auch nicht. Danke für das Teilen dieser Gedankengänge und Ideen! #zitieren