Aus Prinzip nur eine Verantwortlichkeit

Hallo Spaß-Coder.

Wie viele Verantwortlichkeiten haben eure Klassen? Das Eine-Verantwortlichkeit-Prinzip (Single-Responsibility-Principle [SRP]) spricht bei Verantwortlichkeiten bzw. Aufgaben von Klassen darüber, wie viele Gründe es gibt, eine Klasse zu ändern. Das mag dann etwas seltsam anmuten, wenn ich die Aussage treffe: „Ich ändere die Klasse weil ich den Code in der Klasse ändern muss/will.“. Schauen wir uns genauer an, was hinter diesem Design Prinzip steckt.

Single-Responsibility-Principle

Das SRP ist das erste Design Prinzip von fünf zusammengefassten Prinzipien, die wir in nachfolgenden Artikel gerne vorstellen werden (SOLID). Eine Definition findet ihr z.B. unter http://de.wikipedia.org/wiki/Single-Responsibility-Prinzip.

Erläuterung des Prinzips

Habt ihr euch beim Lesen von Quellcode einer Klasse schonmal die Frage gestellt „Was macht diese Klasse eigentlich?“ und die Frage nicht innerhalb von 2 Minuten beantworten können? Habt ihr euch schonmal von einem Klassennamen in die Irre führen lassen, da die Klasse etwas ganz anderes macht, als dran steht bzw. die Klasse deutlich mehr leistet?

Zunächst Frage ich mich, warum es überhaupt ein Problem sein soll, dass eine Klasse mehrere Dinge macht. Monolithen und God-Classes gibt es doch viele in der Softwareentwicklung. Immer wieder laufen mir solche Klassen über den Weg, sei es bei Open-Source-Code, in Legacy-Code und natürlich auch in unserem eigenen Code.

Wenn wir „Einmal-Code“ oder „Wegwerf-Code“ schreiben, spielt es keine Rolle, ob die Klasse nur eine Aufgabe erfüllt, oder wir für alle Anforderungen nur eine Klasse verwenden. Wir entwicklen diese Klasse einmal und fassen sie nie wieder an (besser ist das). Leider ist dies nur selten Realität. Sogar sehr häufig ändern sich Anforderungen an bestehende Software und eine God-Class mal eben wegzuwerfen und komplett neuzuschreiben wird wohl in den seltesten Fällen möglich sein – auch wenn das sicherlich manchmal die bessere Alternative wäre.

Somit schlagen wir uns mit der Erweiterung oder Änderung dieser Klasse rum und sind dabei erstmal damit beschäftigt zu verstehen, was eigentlich wo genau passiert und warum macht die Methode ganz was anderes als dran steht und wer benutzt eigentlich diese Methode noch im Projekt oder darüber hinaus!?! Fragen über Fragen.

Genau der letzte Punkt wird häufig zum Problem, insbesondere bei nicht automatisert abgesichertem Code durch Unit Tests. Eine Klasse, deren Methoden niemand nutzt, kann ich mit gutem Gewissen ändern, ohne dass etwas anderes dabei kaputt geht – allerdings würde ich diese Klasse dann besser einfach löschen (siehe YAGNI).

Wer hat schonmal eine umfangreiche Klasse geändert und an verschiedenen anderen Stellen in der Software hat etwas nicht mehr funktioniert wie vor der durchgeführten Änderung? Je umfangreicher die Klasse, je mehr Aufgaben und Verantwortlichkeiten eine Klasse hat, desto stärker sind Abhängigkeiten zu anderen Klasse und desto höher ist die Wahrscheinlichkeit, dass bei einer Änderung irgendeine der Abhängkeiten kaputt geht.

So eine God-Class entsteht bei Neuentwicklungen z.B. dadurch, dass der Name der Klasse sehr generisch ist und damit mir selbst gar nicht so klar ist, was die Verantwortlichkeit dieser Klasse genau ist. So sind Klassen mit den Appendizes „Manager“ oder „Service“ von vornherein dazu verdonnert, alles mögliche zu verwalten und anzubieten. Alleine diese Klassennamen laden im Weiteren dazu ein, hier Code zu platzieren, weil ja noch Platz am Ende der Klasse ist. Beim Einfügen von neuem Verhalten in bestehende Software stellt sich oft die Frage, wohin denn mit diesem Verhalten. Wenn vorhandene Klassen unspezifisch sind, findet sich leicht eine (un)geeignete Stelle zum Einfügen des neuen Codes.

Eine Unterstützung beim Schneiden von Klassen, also der Separation von Verantwortlichkeiten in entsprechende Häppchen, gibt die Kohäsion einer Klasse. Eine starke Kohäsion einer Klasse entsteht dann, wenn alle in der Klasse enthaltenen Methoden möglichst alle Klassenvariablen verwenden und/oder verändern. Dies zeigt auf, dass alle Methoden an einer Aufgabe arbeiten – an einem Strang ziehen. Wird beispielsweise eine kleine Hilfsmethode einer bestehenden Klasse hinzugefügt, welche nur mit übergebenen Parametern Arbeitet, so hat diese Methode sehr wahrscheinlich nichts mit der eigentlichen Aufgabe zu tun und kann ausgelagert werden. Wenn andererseits nur eine einzige Methode eine bestimmte Klassenvariable nutzt, können Methode mit samt Variable in eine neue Klasse extrahiert werden.

Manche Leser mögen hier das Argument anbringen, „Ja, aber dann entstehen ja ganz viele kleine Klassen und ich verliere den Überblick.“. Ersteres ist erstrebenswert, zweiteres durch organisatorische Mittel zu beheben. Hierauf werden wir in einem späteren Artikel noch genauer eingehen. Wie klein eine Klasse sein solte, lässt sich kaum in Form von Anzahl Codezeilen festlegen und ist abgängig von der Komplexität der einen Aufgabe.

 

Unsere Erfahrung mit dem SRP

Unser „running gag“ ist die Klasse „Playfield“ aus unserem Projekt „Jewels“ (was wir zur eigenen Überraschung tatsächlich fertiggestellt haben). Es handelt sich dabei um eine Implementierung eines Spielklassikers, bei dem auf dem Spielfeld Steine in derselben Farbe horizontal oder vertikal durch anklicken entfernt werden. Je mehr Steine derselben Farben, desto mehr Punkte. Zurück zur Klasse Playfield.

Ein Code-Smell, also ein Indikator für schlechten Code, ist hier bereits die Länge der Klasse. Mit 987 Zeilen sicherlich nicht die längste Klasse die ich kenne, aber möglicherweise hat sie mehr als eine Verantwortlichkeit. Nur die Methoden Signaturen hier zu posten würde den Rahmen des Artikels sprengen.

Für was ist bei einem Spiel eigentlich das Spielfeld zuständig? Ich weiß es nicht genau und genau so hat sich eben auch diese Klasse entwickelt. „Irgendwie passt es ja hier rein bzw. wir haben keine andere Klasse, in die der neue Code gut reingehört…(oje)“. Dies beginnt beim Verwalten der Steine bis hin zu den Spielzügen und Benutzereingaben.

Die Anwendung des SRP ist nicht immer einfach, aber die Änderung unserer Klasse Playfield ist noch viel schwieriger!

Insbesondere die Anwendung des Flow-Patterns untersützt sehr dabei, des Fokus auf die eigentliche Aufgabe und Verantwortlichkeiten zu behalten. Darüber hinaus beitet ein gut strukturiertes Projekt (verschiedene Bibliotheken, JARs, Namensräume, Pakete) auf grober Ebene eine Richtlinie, welche Verantwortlich im Projekt benötigt werden und damit gute Anhaltspunkte für die Definition von Klassen.

 

Zusammenfassung

Wenn es tatsächlich nur einen Grund gibt, eine Klasse ändern zu müssen, wirkt dies ungemein befreiend, gibt Sicherheit bei der Durchführung der Änderung und untersützt die Fokussierung auf das Wesentliche. Das SRP anzuwenden mag zunächst ungewohnt sein, aber auf lange Sicht gewinnen alle Beteiligten – und insbesondere die Kunden durch weniger Fehler in der Software auf Grund von Abhägigkeiten im Code.

 

Viel Spaß beim Anwenden.

Eure Spaß-Coder

 

Dieser Artikel basiert neben unseren Erfahrungen auf den Ausführungen aus:

  • Martin, Robert C. Clean Code-Refactoring, Patterns, Testen und Techniken für sauberen Code: Deutsche Ausgabe. MITP-Verlags GmbH & Co. KG, 2013.
  • Feathers, Michael C. Effektives Arbeiten mit Legacy Code: Refactoring und Testen bestehender Software. mitp Verlags GmbH & Co. KG, 2011.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert