Donnerstag, 3. Juli 2014

Zyklische Abhängigkeiten - Werkzeugunterstützung für Java


Dieser Artikel ist Teil der folgenden Serie über zyklische Abhängigkeiten. Zahlreiche Grundbegriffe, Konzepte und empirische Befunde wurden im Rahmen dieser Serie detailliert dargestellt. Im vorliegenden Artikel werde ich einige Werkzeuge vorstellen, welche die Analyse von Abhängigkeiten und Zyklen in Java-Software ermöglichen.

Die Serie

Einführung
Terminologie
Werkzeugunterstützung für Java (dieser Artikel)
Wo liegt eigentlich das Problem?
Einfluss auf Qualitätsmerkmale
Die Praxis
Verschiedene Erscheinungsformen
Zusammenhang mit der Pakethierarchie
Zusammenhang mit der Domäne
Einige Metriken
Durchbrechung von Zyklen
Das Prinzip

Derzeit werden die folgenden Werkzeuge präsentiert:
Als Analysegegenstand verwende ich das bereits im vorherigen Artikel gezeigte Musterprojekt aus drei Paketen und sechs Klassen, das wie folgt aussieht:
Die vorgestellten Werkzeuge dienen lediglich einer ersten Übersicht der Möglichkeiten, die dem Entwickler zur Untersuchung von Zyklen in Java-Software zur Verfügung stehen. Die meisten dieser Werkzeuge bieten einen Funktionsumfang, der eine eingehendere Behandlung verdienen würde. Zudem ist die vorgestellte Auswahl nur vorläufig - ich bin mir sicher, dass weitere Werkzeuge existieren, die ggf. leistungsfähiger sind. Falls mir solche Werkzeuge begegnen, werde ich sie dem Artikel hinzufügen.

JDepend

JDepend (von mir als Eclipse-Plugin verwendet, siehe http://andrei.gmxhome.de/jdepend4eclipse/) zeigt erkannte Zyklen je Paket an. Dies sieht wie folgt aus:
Leider existiert weder eine Visualisierung der gefundenen Zyklen, noch lassen sich die an einem einzelnen Zyklus beteiligten Typen separat anzeigen. Die Zyklus-Elemente müssen durch Ansicht der eingehenden und ausgehenden Abhängigkeiten vom Benutzer selbst ermittelt werden.

Classycle

Classycle (als Eclipse-Plugin unter http://classycleplugin.graf-tec.ch/ verfügbar) erkennt Zyklen auf Package- und Typ-Ebene. Der unter Eclipse installierte Classycle-View bietet je einen Reiter für die verschiedenen Zyklus-Ebenen. Der vierelementige Klassen-Zyklus wird wie folgt angezeigt:
Wie zu sehen, erhält der Zyklus einen Namen ("p2.C et. al") sowie einige Zyklus-spezifische Metriken (Girth, Radius, Diameter, ...), die ich in späteren Blogartikeln noch behandeln werde. Auch die Erkennung des Paket-Zyklus erfolgt korrekt:
Eine Visualisierung der Zyklen erfolgt nicht.

UCDetector

Ebenfalls als Eclipse-Plugin ist das Werkzeug UCDetector ("You See Detector", siehe http://www.ucdetector.org) verfügbar. Neben diversen Funktionen zum Aufspüren ungenutzten Codes bietet UCDetector auch eine Zyklenerkennung auf Klassenebene an. Für unser Beispielprojekt werden zwei Zyklen erkannt:
UCDetector erkennt hier also sowohl den "großen" Zyklus zwischen den Klassen A, B, C und D als auch den kleinen Zyklus zwischen C und D. Die Darstellung erfolgt als Baum und lässt sich bis zur gefundenen Quellcode-Referenz nachverfolgen. Die Zyklusgröße, die Anzahl der gefundenen Referenzen und die beteiligten Klassen sind jeweils in der ersten Zeile eines dargestellten Zyklus' mit angegeben. Durch Klick auf den Baum öffnet sich der entsprechende Quellcode im Editor. Auch wenn hier eine Visualisierung fehlt, so lassen sich auf diese Weise Klassenzyklen doch sehr komfortabel aufspüren und analysieren. Die übersichtlich angegebenen Zyklusinformationen erlauben eine vergleichsweise systematische Abarbeitung durch den Entwickler.

inFusion

Das kommerzielle Werkzeug inFusion (http://www.intooitus.com/products/infusion) bietet eine metrikbasierte Erkennung von Design-Problemen. Eines der erkannten Muster heißt "Cyclic Dependencies". Dieses arbeitet ausschließlich auf der Paket-Ebene, die inFusion als "Subsytem" bezeichnet. Die Analyse des Projekts ergibt insgesamt drei "Design-Flaws", je einen pro Paket:
inFusion bietet einen View namens "Cyclic Dependencies Graph" an, der für den ersten der oben angezeigten Design-Flaws wie folgt aussieht:
inFusion erkennt hier also nicht den den Gesamt-Zyklus, der von den drei Paketen gebildet wird, sondern die kleineren Teilzyklen zwischen den Paketen. Dies legt die Vermutung nahe, dass inFusion keine Erkennung von Strongly Connected Components am Abhängigkeitsgraphen anbietet, sondern lediglich nach echten "Kreisen" sucht.

Eine Besonderheit bietet inFusion mit dem "Weakest Dependencies"-View an:
Für symmetrische Zyklen aus nur zwei Paketen ist der Nutzen zwar gering, bei größeren Zyklen liefert dieser View aber gute Hilfe dabei, welche Abhängigkeiten am einfachsten durchbrochen werden können.

STAN

Das Werkzeug STAN (Structure Analysis for Java, siehe http://stan4j.com/) bietet diverse Analysemöglichkeiten für Zyklen auf allen Ebenen (Klassen, Pakete und physische Einheiten) an. Zum einen steht eine Visualisierung zur Verfügung, welche die Zyklen des Systems (bei STAN "Tangles" genannt) auf Klassen- und Paket-Ebene separat anzeigen kann, indem sie im Abhängigkeitsgraphen entsprechend umrahmt werden. Zum anderen wird eine Metrik namens "Tangled" angeboten, die ich unten noch besprechen werde. Hier zunächst die Visualisierung unserer Beispiel-Anwendung:
Im sogenannten Composition-View (links) werden zwei Zyklen gesondert ausgewiesen. Rot umrahmt mit der Beschriftung "Tangle of 3" wird der Zyklus auf Paket-Ebene angezeigt. Grau umrahmt mit der Beschriftung "Tangle of 2" wird der direkte Klassenzyklus zwischen C und D im Paket p2 angezeigt. Der Klassenzyklus zwischen den Klassen A, B, C und D wird nicht visualisiert. Anscheinend visualisiert STAN die Zyklen auf Klassenebene nur dann, wenn sie sich innerhalb eines Pakets befinden und erreicht auf diese Weise (auf Kosten von Information) eine bessere Übersichtlichkeit.

Die Tangled-Metrik basiert auf dem Konzept des minimum feedback arc set (auch: minimum edge feedback set, MEFS, siehe [Melton2006]). Dies ist diejenige kleinstmögliche Menge an Kanten, die aus dem System entfernt werden müsste, um alle Zyklen zu beseitigen. Die Kanten des MEFS sind im Composition-View rot eingefärbt. Die Tangled-Metrik ist das Verhältnis der (gewichteten) Kanten des MEFS zu allen (gewichteten) Kanten des Abhängigkeitsgraphen. Die Gewichtung erfolgt dabei auf Grundlage der gezählten Einzelabhängigkeiten. Wie zu sehen, erfolgt die Berechnung auf Paket-Ebene. Es existiert eine entsprechende Metrik namens "Tangled - Libraries", welche die Ebene physischer Einheiten erfasst. Die Ermittlung des MEFS ist NP-vollständig, es existieren aber Heuristiken mit besserer Laufzeit. Ich habe nicht ausprobiert, wie sich STAN bei größeren Zyklenmengen verhält.

Es existiert ein spezieller Query-Dialog, der die Tangled-Metriken z. B. differenziert nach Paketen ausweisen kann. Im Whitepaper von STAN wird bspw. das folgende Query-Ergebnis präsentiert:
In einem sogenannten Pollution-View wird versucht, diverse problematische Metriken anschaulich als Gesamtbild zu präsentieren. Auch hier taucht die Tangled-Metrik auf. Für unsere Beispielanwendung sieht dieser View so aus:
STAN fokussiert also insgesamt auf die Darstellung von Metriken und die Visualisierung der Zusammenhänge. Die Nutzung und Visualisierung des MEFS kann als Alleinstellungsmerkmal unter den hier betrachteten Werkzeugen angesehen werden und liefert wertvolle Hinweise zur möglichen Durchbrechnung von Zyklen. Ein direkter Drill-Down zur Quellcode-Ebene ist in STAN allerdings nicht vorgesehen.

Weitere Informationen zur Behandlung zyklischer Abhängigkeiten in STAN finden sich unter http://stan4j.com/advanced/acyclic-dependencies-principle.html.

Dependometer

Dependometer (siehe http://source.valtech.com/display/dpm/Dependometer) ist Analysewerkzeug, das per Kommandozeile oder Maven-Plugin ausgeführt werden kann. Neben der Ermittlung von Abhängigkeits-Verstößen gegen vordefinierte Schichten und Subsysteme sowie der Bereitstellung einiger Metriken bietet Dependometer auch eine Zyklen-Erkennung auf diversen Ebenen an. Es erfolgt eine Ausgabe im HTML-Format, die auf weitergehende Visualisierungen verzichtet. Ich notiere hier einige Zyklen-bezogene Ergebnisse der Analyse des Beispiel-Projekts.

In der Analyseübersicht wird zunächst das Vorhandensein von Zyklen wie folgt dargestellt:
Die mit "! not analyzed !" gekennzeichneten Zyklentypen konnten nicht analysiert werden, da für das Beispiel-Projekt keine Konfiguration von Schichten, Subsystemen und "vertical slices" vorgenommen wurde. Diese Konfiguration muss Dependometer per XML-Datei übergeben werden.

Auf drei separaten Seiten namens "cycle participation" lassen sich noch die beteiligten Instanzen der Zyklen auf der jeweiligen Ebene (compilation unit, package und type) anzeigen. Ich zeige diese drei Seiten hier in einer zusammengefassten Abbildung:
Es existiert zudem noch eine Metrik namens "Tangle score", die aber im erzeugten Analyse-Ergebnis auf 0 steht. Ich konnte der Dokumentation nicht entnehmen, wie diese Metrik erzeugt werden kann.

Insgesamt bietet Dependometer eine Zyklen-Erkennung auf zahlreichen Ebenen an, die zumindest für das überschaubare Beispiel auch korrekt arbeitet. Die Präsentation der gefundenen Zyklen fällt vergleichsweise dürftig aus. Das eigentliche Alleinstellungsmerkmal von Dependometer besteht in der Möglichkeit, Schichten, Subsyteme und vertical slices zu spezifizieren und den Quellcode auf Einhaltung dieser Spezifikation zu überprüfen. Durch die Möglichkeit zur Verwendung als Maven-Plugin bietet sich zudem eine automatisierte Zyklenerkennung an, die innerhalb von Continous Integration-Systemen zeitnahe Warnungen bei Verstößen erzeugen kann.

JarAnalyzer

Das Werkzeug JarAnalyzer (http://www.kirkk.com/main/Main/JarAnalyzer) von Kirk Knoernschild analysiert Abhängigkeiten auf der Ebene von Jarfiles. Die Ausgabe kann im .dot-Format erfolgen, so dass eine Visualisierung mittels Graphviz möglich ist. Ich habe zu Testzwecken aus den drei Packages drei korrespondierende Jarfiles erzeugt:
JarAnalyzer erzeugt zu diesen Jarfiles den folgenden Abhängigkeitsgraphen:
Die Erkennung von Zyklen muss hier wieder manuell erfolgen. Es existiert allerdings auch ein XML-Ausgabeformat, das eine Zyklen-Erkennung beinhaltet. Die Ausgabe erfolgt jeweils aus der Sicht des einzelnen Jarfiles:
Im unteren Bereich findet sich hier der Tag <Cycles></Cycles>, der den Zyklus enthält. Eine übersichtliche Erkennung von Gesamtzyklen ist hier, ähnlich wie schon bei den zuvor besprochenen Werkzeugen, nicht möglich. Eine Visualisierung dieses Ausgabeformats existiert ebenfalls nicht.

Werkzeuge ohne Zyklus-Erkennung

Dependency Finder
Das Werkzeug Dependency Finder (http://depfind.sourceforge.net/) bietet verschiedene Extraktions-Schnittstellen an, darunter per Kommandozeile, über ein einfaches GUI, innerhalb einer Webanwendung oder im Rahmen eines Ant-Builds. Die Analysemöglichkeiten sind sehr vielfältig, werden aber nicht in einer GUI visualisiert, sondern in einem einfachen Textformat wie dem folgenden ausgegeben:
Interessant ist hier, dass die einbezogenen Ebenen des Abhängigkeitsgraphen separat aktiviert und deaktiviert werden können. Es stehen die drei Ebenen packages, classes und features zur Verfügung. Die obige Ausgabe erfolgte bei ausschließlicher Aktivierung von packages. Fügt man classes hinzu, so beginnt die Ausgabe wie folgt:
Ein Visualisierungswerkzeug ist ebensowenig Teil des Dependency Finders wie eine explizite Zyklen-Erkennung. Insbesondere die XML-Ausgabe von Dependency Finder kann allerdings eine gute Grundlage für andere Werkzeuge liefern, die auf die Analyse von Zyklen spezialisiert sind. Dieser Ansatz wird beispielsweise in der Studie [Dietrich2010] verfolgt.

Java Dependency Viewer
Das Eclipse-Plugin "Java Dependency Viewer" (siehe http://marketplace.eclipse.org/content/java-dependency-viewer), das über den Eclipse Marketplace installiert werden kann, bietet keine Zyklen-Erkennung, visualisiert aber die Abhängigkeiten auf Package- und Typ-Ebene. Nach einiger zusätzlicher Handarbeit zeigt sich die Struktur des Beispiel-Projekts recht gut. Hier die Ansicht der Klassen-Beziehungen:
Und hier die Paket-Ebene:
Für größere Projekte dürften die simplen Views allerdings schnell unübersichtlich werden.

Zusammenfassung

Die verschiedenen Werkzeuge unterscheiden sich hinsichtlich der Analyse-Ebenen, der Möglichkeit zur Visualisierung sowie darin, ob sie in der Lage sind, eigenständig Zyklen zu erkennen. Die folgende Tabelle gibt eine Übersicht dieser Unterschiede:

Werkzeug Zyklus-Erkennung Klassen Pakete Physische Einheiten Visualisierung
JDepend ja nein ja nein nein
Classcycle ja ja ja nein nein
UCDetector ja ja nein nein nein
inFusion ja nein ja nein ja
STAN ja ja ja ja ja
Dependometer ja ja ja nein nein
JarAnalyzer ja nein nein ja indirekt (.dot-Export)
Dependency Finder nein ja ja nein nein
Java Dependency Viewer nein ja ja nein ja

Weitere Unterschiede zwischen den Werkzeugen betreffen die verfügbaren Analyse-Optionen und Ausgabe-Formate.

Die Erkennung von Zyklen erfolgt zudem bei JDepend, inFusion und JarAnalyzer aus der Sicht einzelner Entitäten. Beispielsweise erkennt inFusion, dass sich ein Paket in einem Zyklus mit einem zweiten Paket befindet und meldet diesen Zyklus isoliert. Der Zyklus P1 <-> P2 <-> P3 wird also von inFusion in mehrere Teilzyklen aufgespalten. Classcycle geht hier anders vor und präsentiert den Zyklus (im Sinne einer Strongly Connected Component, siehe Terminologie) als Ganzes. UCDetector agiert ähnlich, stellt aber sowohl Gesamtzyklen als auch enthaltene Teilzyklen dar.

STAN bietet mit der Tangled-Metrik eine wertvolle Zusatzinformation an, welche eine grobe Abschätzung der Aufwände zur Zyklus-Beseitigung ermöglicht.

Dependometer zeichnet sich dadurch aus, dass Zyklen auch zwischen benutzerdefinierten Schichten, Subsystemen und vertical slices erkannt werden können.

Zusammenfassend kann daher sicher festgehalten werden, dass bis dato kein (mir bekanntes) Werkzeug existiert, das alle Analyse-Ebenen in zufriedenstellender Weise analysieren und darstellen kann.

Quellen

[Dietrich2010] - Barriers to Modularity - An Empirical Study to Assess the Potential for Modularisation of Java Programs, Jens Dietrich, Catherine McCartin, Ewan Tempero, Syed M. Ali Shah (2010)

[Melton2006] - An Empirical Study of Cycles among Classes in Java,  H. Melton, E. Tempero (2006)