Designprinzipien sichtbar machen
Unser Hauptprodukt onOffice enterprise wird seit vielen Jahren stetig weiterentwickelt. Der Code muss dementsprechend bei veränderten Anforderungen oder Fehlern zum Teil nach Jahren angepasst werden. Gleichzeitig wächst unser Team immer weiter und neue Entwickler müssen sich in ein komplexes, schon lange bestehendes System einarbeiten. Um diesen Anforderungen gerecht zu werden, legen wir einen besonderen Fokus auf Code-Qualität.
Unser Hauptprodukt onOffice enterprise wird seit vielen Jahren stetig weiterentwickelt. Der Code muss dementsprechend bei veränderten Anforderungen oder Fehlern zum Teil nach Jahren angepasst werden. Gleichzeitig wächst unser Team immer weiter und neue Entwickler müssen sich in ein komplexes, schon lange bestehendes System einarbeiten. Um diesen Anforderungen gerecht zu werden, legen wir einen besonderen Fokus auf Code-Qualität.
Die Bewertung der Code-Qualität ist fest in unseren alltäglichen Arbeitsablauf integriert. Dazu gehört auch die fortlaufende Optimierung und Erweiterung unserer Regeln. Dennoch entstehen vereinzelt Codestrukturen, die schwer nachvollziehbar sind und somit nur mit Mehraufwand verstanden, angepasst oder erweitert werden können.
Bei der Suche nach Lösungsansätzen haben sich zwei Erkenntnisse herauskristallisiert:
- Die Entstehung von technischen Schulden, also der Mehraufwand durch nicht optimal strukturierten Code, lässt sich nie vollständig vermeiden. Selbst in den fähigsten Teams mit klarem Fokus auf Code-Qualität entsteht oft erst während der Projektumsetzung ein so tiefes Problemverständnis, dass man erst im Nachhinein sagen kann, wie man es eigentlich hätte lösen sollen. Umso wichtiger ist es dementsprechend, dass technische Schulden nicht unkontrolliert ins System eingebracht, sondern mit klaren Richtlinien und Regeln gezielt gesteuert werden.
- Für die Nachvollziehbarkeit in objektorientierten Strukturen sind nicht nur die einzelnen Bestandteile (Klassen, Methoden, …) relevant, sondern auch die Art und Weise, wie sie zusammenarbeiten.
Unsere Regeln beziehen sich oft auf isoliert betrachtete Klassen oder Methoden, wie zum Beispiel konkrete Grenzwerte für die Anzahl an Codezeilen oder Parameter. Das Messaging, also die Kommunikation der Bestandteile untereinander, wird kaum bewertet. Die Herausforderung ist jetzt, neue Richtlinien aufzustellen, die das nachvollziehbare Messaging in unserem Code fördern.
Wie definiert man solche Regeln?
Einen möglichen Ansatz bietet das „factor-criteria-metrics”-Modell. Der abstrakte Begriff der Software-Qualität (mit Code-Qualität als Bestandteil) wird bedingt durch übergeordnete Qualitätseigenschaften wie Wartbarkeit, Sicherheit, Zuverlässigkeit. Diese wiederum setzen sich aus Teilmerkmalen zusammen wie Testbarkeit oder Wiederverwendbarkeit. Diese Konkretisierung lässt sich so weit fortführen, bis man bei messbaren Eigenschaften ankommt, den sogenannten Qualitätsindikatoren.
Einfache Metriken wie Lines of Code lassen sich dann mit Grenzwerten belegen. Schreibt ein Entwickler nun eine Methode, die fünf Zeilen zu lang ist, kann das Problem gemeldet und behoben werden. Aber wie zielführend ist das zur Verbesserung der Code-Qualität? Konfrontiert mit dem Problem, fünf Codezeilen loszuwerden, wird man vielleicht verleitet, die dafür einfachste Lösung zu wählen, statt die sinnvolle Trennung in einzelne Aufgaben im Gesamtbild zu hinterfragen.
Welches Problem liegt vor?
Dabei könnte ein simpler Verstoß auf dieser Ebene der Qualitätsindikatoren dafür sprechen, dass ein komplexeres Problem vorliegt. Denn all die definierten Regeln, Richtlinien und Grenzwerte entstehen nicht willkürlich, sie orientieren sich an etablierten Konzepten, Designprinzipien oder Best Practices. Dieser Zusammenhang geht allerdings im Regelwerk vollständig verloren. Der Entwickler kann aus einer Meldung “Die Methode hat zwei Parameter zu viel!” nicht erkennen, warum es diesen Grenzwert überhaupt gibt.
Von „was?” zu „warum?”
Diesen versteckten Zusammenhang wollen wir sichtbar machen. Es soll ein Weg gefunden werden, Abstand zu gewinnen von der Fragestellung „Was muss mein Code erfüllen?” hin zum Bewusstsein und einer alltäglichen Auseinandersetzung mit der Frage „Warum soll der Code bestimmte Eigenschaften erfüllen?”. Die neuen Richtlinien sollen so gewählt werden, dass der Fokus auf den Designprinzipien liegt, die wir verfolgen.
Als Basis dient hier das „factor-strategy”-Modell, eine Weiterentwicklung des „factor-criteria-metrics”-Modells. Statt eine direkte Verbindung zwischen Qualitätseigenschaften und -indikatoren herzustellen, wird der Zusammenhang zwischen Qualitätseigenschaften und konkreten Designprinzipien dargestellt.
Auch in diesem Modell soll die Erfüllung der definierten Anforderungen natürlich messbar sein. Zu diesem Zweck werden individuelle Erkennungsstrategien entwickelt, die eine mögliche Verletzung der konkreten Designprinzipien identifizieren sollen. Die Strategien setzen sich dabei aus Qualitätsindikatoren zusammen, die mit Grenzwerten versehen und miteinander kombiniert werden. So kann man Probleme mit direktem Bezug zu den Designprinzipien melden. Der Entwickler hat so die Möglichkeit, sich auf einer komplexeren Ebene mit der Code-Qualität auseinanderzusetzen. Eine „symptomatische” Fehlerbehandlung ist nicht mehr möglich.
Umsetzung bei onOffice
Genau diesen Ansatz verfolgen wir für die neuen Richtlinien. Dabei werden die Designprinzipien gewählt, um
- das nachvollziehbare Zusammenspiel der Code-Komponenten,
- die sinnvolle Modellierung von trennbaren Verantwortlichkeiten und
- transparente Abhängigkeiten
zu fördern.
Um das Konzept in der Praxis zu testen, wurde für vier Designprinzipien eine Erkennungsstrategie implementiert und in das intern entwickelte Script eingebunden, das wir zur Überprüfung der Code-Qualität vor jedem Commit ausführen. Zur Berechnung der Metriken werden pDepend und phpMetrics verwendet. Die Strategien basieren auf Annahmen und Erfahrungswerten, welche Merkmale für ein mögliches Problem sprechen. Bei den Meldungen, die der Entwickler auf Basis der Erkennungstrategien bekommt, wird bewusst auf konkrete Zahlen einzelner Metriken verzichtet. Der Fokus liegt klar auf den Designprinzipien, zusätzlich wird ein Hinweis darauf gegeben, warum die Meldung angezeigt wird. Der Entwickler soll die Meldungen zum Anlass nehmen, den Code gezielt zu prüfen und gegebenenfalls vorliegende strukturelle Probleme zu beheben, bevor sie sich durch Erweiterungen und Anpassungen vergrößern.
Single Responsibility Principle
Beim SRP geht es um die klare Trennung von Verantwortlichkeiten im Code. Das Prinzip hat das Ziel, mögliche Seiteneffekte in Folge einer Codeänderung zu minimieren. Es ist zwar quasi unmöglich zu bestimmen, ob das Prinzip erfüllt wird. Für die mögliche Verletzung lassen sich allerdings einige Indikatoren finden:
- LOC Lines of Code – eine zu lange Klasse oder Methode ist ein erster Anhaltspunkt, dass der Code mehrere Verantwortlichkeiten hat
- LCOM Lack of Cohesion in Methods – die Metrik berechnet, wie viele voneinander unabhängige Teile in einer Klasse zusammengefasst sind
- CCN – cyclomatic complexity number – die Komplexität nach McCabe bestimmt, wie viele mögliche Pfade es für den Programmablauf gibt
- boolean flag argument – ebenfalls ein Hinweis für mindestens zwei Verantwortungsbereiche, am besten an einem Beispiel verdeutlicht:
public function doExport(bool $isTestExport)
Wird ein Problem vermutet, erhält der Entwickler beispielsweise die Meldung:
SINGLE RESPONSIBILITY PRINCIPLE - suspected violation based on a lack of cohesion in methods
Interface Segregation Principle
Das ISP – ebenfalls eines der SOLID-Prinzipien – besagt, dass eine Klasse niemals gezwungen werden darf, eine Schnittstelle bereitzustellen, die sie nicht haben sollte oder sinnvoll implementieren kann.
Als mögliche Probleme werden hier Methoden gemeldet, die von einem Interface oder einer Elternklasse vorgegeben, aber leer implementiert werden. Zusätzlich wird bei Interfaces die Anzahl vorgegebener Methoden geprüft. Eine hohe Anzahl kann dafür sprechen, dass die Definition zu konkret gewählt ist und sich nicht auf andere Strukturen übertragen lässt.
Eine entsprechende Meldung ist dann z.B.:
INTERFACE SEGREGATION PRINCIPLE - suspected violation based on an empty public method implementation of methodName declared by an interface or parent
Law of Demeter
Das Gesetz von Demeter stellt Regeln für die Interaktion von Objekten auf.
So dürfen innerhalb einer Instanz der Klasse A nur
- eigene Methoden der Klasse A,
- Methoden von Instanzvariablen von A,
- Methoden von Objekten, die als Parameter an Methoden der Klasse A gegeben werden oder
- Methoden von Objekten, die die Klasse A selbst erzeugt
aufgerufen werden.
Als Verstoß werden Methodenaufrufe gemeldet, die nicht unter die oben genannten Kategorien fallen. Dazu werden alle zulässigen und tatsächlichen Methodenaufrufe gesammelt. Aus der Differenzmenge ergeben sich vermutete Verstöße. Gemeldet wird dann:
LAW OF DEMETER - suspected illegal method calls: methodOne, methodTwo
Yo-Yo-Problem
Beim letzten der ausgewählten Konzepte handelt es sich um ein Anti-Pattern: das Yo-Yo-Problem, benannt nach dem Spielzeug. Es tritt auf, wenn in tiefen Vererbungsstrukturen sehr viel Logik über die Ebenen verteilt ist. Beim Versuch, das Programmablauf nachzuvollziehen, muss der Entwickler immer wieder zwischen den Ebenen hin- und herspringen und verliert schnell den Überblick.
Um solche Strukturen zu erkennen, werden drei Metriken verwendet:
- DIT Depth of Inheritance Tree – beschreibt die Vererbungstiefe der untersuchten Klasse
- WMC Weighted Method Count – berechnet die Gesamtkomplexität der Klasse
- WMCI Weighted Method Count Inherited – berücksichtigt zusätzlich die Komplexität der Logik, die an die Klasse vererbt wird
Wird ein Yo-Yo-Problem vermutet, meldet die Analyse:
YOYO PROBLEM - suspected problem based on the deep inheritance level and high complexity of inherited logic
Fazit
Das Konzept wird seit Kurzem in der Praxis angewendet. Nach und nach sollen die Erkennungsstrategien und die ausgewählten Designprinzipien an unsere Bedürfnisse angepasst und optimiert werden. Trotzdem haben wir schon jetzt einen Weg gefunden, eine alltägliche Auseinandersetzung mit ausgewählten Prinzipien zu fördern und so das Bewusstsein dafür in der Entwicklung zu stärken.