Mittwoch, 2. Juli 2014

Zyklische Abhängigkeiten - Terminologie

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 führe ich einige Grundbegriffe und Konzepte ein, die bei der weiteren Besprechnung zyklischer Abhängigkeiten hilfreich sein werden.

Die Serie

Einführung
Terminologie (dieser Artikel)
Werkzeugunterstützung für Java
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


Die vorgestellten Begriffe gruppieren sich um die folgenden Themen:
  • Entitäten: Zwischen welchen Entitäten können zyklische Abhängigkeiten auftreten und wie heißen sie?
  • Abhängigkeiten: Was genau ist eine Abhängigkeit und in welchen Formen tritt sie auf?
  • Zyklische Abhängigkeit: Wann ist eine Abhängigkeit zyklisch?

Entitäten

Zyklen können zwischen sehr unterschiedlichen Software-Elementen auftreten. Im Rahmen der Behandlung von Entwurfs-Prinzipien können dabei Zyklen auf der Ebene von Funktionen/Methoden außer Acht gelassen werden. Es können dann mindestens drei Entitäten unterschieden werden:
  • Typen: Typen sind die kleinsten Bausteine eines Entwurfs. Im objektorientierten Kontext zählen Klassen, Interfaces, Traits oder Aufzählungstypen zu den Typen. Grundsätzlich sind Zyklen jedoch auch im Bereich der Strukturierten Programmierung thematisiert worden (u. a. in [Parnas1978]), so dass auch Module im Sinne der Strukturierten Programmierung zu den Typen gezählt werden können.
  • Pakete: Die nächst höhere Entität ist eine Gruppierung von Typen innerhalb der verwendeten Programmiersprache, die insbesondere eine Bildung von Namensräumen ermöglicht. Diese Namensräume haben keine eigenständige Modul-Semantik, d. h. sie verfügen nicht notwendigerweise über eine von den Typen unabhängige Schnittstelle. Pakete bilden eine Hierarchie (engl. containment tree), die aber lediglich zur Trennung der Namensräume dient. Es existiert also in der jeweiligen Sprache normalerweise keine Unterpaket-Semantik. Alle Pakete, auch Unterpakete, sind gleichwertige und eigenständige Namensräume. In vielen Programmiersprachen heißen Pakete Package, in anderen Namespace.
  • Physische Einheiten: Während Typen und Pakete innerhalb der jeweiligen Programmiersprache ausgedrückt werden, entstehen physische Einheiten meist erst zur Build-Zeit mit Mitteln, die außerhalb der Programmiersprache liegen. Physische Einheiten sind verteilbar (deployable), zusammensetzbar (composable), zustandslos (stateless) und nativ verwendbar (natively reusable). Weitere Details zu phyischen Einheiten habe ich im Artikel Was ist eine physische Einheit? erläutert.
Über diese Entitäten hinaus wäre es möglich, zusätzlich Gruppierungen von Typen zu betrachten, die über eine eigene Modul-Semantik verfügen. Derartige "Module" treten meist im Kontext spezieller Modul-Systeme (wie OSGi, JBoss Modules, ...) auf und könnten allgemein an Bedeutung gewinnen, wenn mit Java 9 das Projekt Jigsaw eingeführt wird. Da sich aktuelle Modul-Systeme hinsichtlich ihres Modul-Konzepts jedoch noch stark unterscheiden und die Forschung hierzu noch sehr spärlich oder jeweils spezifisch für einzelne Modul-Systeme ist, werde ich auf eine Behandlung solcher Module vorläufig verzichten.

Abhängigkeiten

Zwischen Typen
Im objektorientierten Kontext werden gewöhnlich mindestens zwei Abhängigkeitsformen zwischen Typen unterschieden: Nutzungsbeziehungen und Vererbungsbeziehungen.
  • Als Nutzungsbeziehung gilt in statisch typisierten Programmiersprachen die textuelle Referenz auf einen Typ, ausgenommen Referenzen zur Kennzeichnung von Vererbungsbeziehungen. In dynamisch typisierten Programmiersprachen können vorhandene Nutzungsbeziehungen z. T. über Typinferenz ermittelt werden. Um nicht permanent Differenzierungen für verschiedene Formen von Typsystemen machen zu müssen, gehe ich der Einfachheit halber von statisch typisierten Programmiersprachen aus.
  • Als Vererbungsbeziehung steht in allen objektorientierten Programmiersprachen mindestens die Subklassenbildung zur Verfügung. Zusätzlich bieten Sprachen, die ein Interface-als-Typ-Konzept unterstützen (siehe hierzu auch diesen Vergleich einiger Programmiersprachen), auch noch eine Implementierungs-Beziehung zu Interfaces an. Diese Beziehungen werden i. d. R. durch ein entsprechendes Schlüsselwort in der jeweiligen Programmiersprache kenntlich gemacht. 
Die Unterscheidung von Nutzungs- und Vererbungsbeziehungen kann bei der Analyse von Abhängigkeiten von Bedeutung sein, da Vererbungsbeziehungen meist sehr viel schwerer zu beseitigen sind als Nutzungsbeziehungen.

Zwischen Paketen
Da Pakete, wie oben erwähnt, keine eigenständige Modul-Semantik aufweisen, entstehen Abhängigkeiten zwischen Paketen implizit auf Grund der Abhängigkeiten zwischen Typen. Dabei ist allerdings zu beachten, dass die Einzelbeziehungen der Typ-Ebene auf der Ebene der Pakete verloren gehen. Das zeigt das folgende Beispiel aus [Dietrich2014]:
Auf der Typ-Ebene bilden hier die Typen A, B, C und D einen Zyklus (siehe Definition zyklischer Abhängigkeiten unten). Da von E keine Abhängigkeit zurückführt, gehört E diesem Zyklus nicht an. Ebensowenig gehört F dem Zyklus an, da kein anderer Typ F referenziert. Auf Paket-Ebene bilden jedoch die Pakete P1, P2 und P3 einen Zyklus. Es können also implizit mehr Typen in Paket-Zyklen involviert sein als in Typ-Zyklen.

Zudem weisen die Abhängigkeiten auf Paket-Ebene keine Differenzierung mehr nach Nutzungs- oder Vererbungsbeziehung auf, da gleichzeitig sowohl Nutzungs- als auch Vererbungsbeziehungen zwischen den Typen der Pakete existieren können. 

Zwischen physischen Einheiten
Abhängigkeiten zwischen physischen Einheiten entstehen analog zu denen in Paketen implizit durch die Abhängigkeiten zwischen Typen. Zwar existieren Modulsysteme, die physischen Einheiten eine Modul-Semantik und damit die Spezifikation öffentlicher Schnittstellen auf der Ebene der physischen Einheit erlauben (wie z. B. OSGi), diese Möglichkeiten sind aber keine zwingende Voraussetzung für physische Einheiten und werden beispielsweise von Jarfiles in Java oder Assemblies in C# nicht erfüllt.

Es gelten grundsätzlich dieselben Einschränkungen hinsichtlich des Informationsverlusts der Einzelbeziehungen der Typ-Ebene wie für Abhängigkeiten zwischen Paketen.

Abhängigkeiten zwischen physischen Einheiten existieren oftmals auch außerhalb des Quellcodes, z. B. in Build-Skripten oder Abhängigkeits-Konfigurationen von Modul-Systemen. Bei der statischen Analyse von Abhängigkeiten werden diese häufig ignoriert. Der Einfachheit halber werde ich diese für physische Einheiten ebenfalls ignorieren, um nicht in Gefahr zu geraden, eine Modul-Semantik auf phyische Einheiten zu projizieren. Es ist aber klar, dass diese zusätzlichen Abhängigkeiten in der Praxis Restrukturierungen noch erschweren.

Zyklische Abhängigkeiten

Gemäß den oben beschriebenen Regeln wird ein gerichteter Abhängigkeitsgraph erzeugt, dessen Knoten die einzelnen Entitäten sind, während die Kanten die Abhängigkeiten repräsentieren. Die meisten Publikationen sprechen dann von einer zyklischen Abhängigkeit innerhalb einer Teilmenge des Abhängigkeitsgraphen, wenn innerhalb dieser Teilmenge jeder Knoten von jedem anderen Knoten aus erreicht werden kann. In der Graphentheorie wird eine solche Struktur als Strongly Connected Component (SCC - im Deutschen existieren hierfür die Bezeichnungen "starke Zusammengehörigkeitskomponente" oder kurz "starke Komponente" sowie der in [Savernik2007] verwendete Begriff der "Zyklengruppe") bezeichnet. Ich werde der Einfachheit halber weiterhin einfach von "Zyklen" sprechen und meine damit starke Zusammengehörigkeitskomponenten.

Aus der oben hervorgehobenen Definition ergibt sich, dass zyklische Abhängigkeiten nicht nur dann existieren, wenn sich einfache "Kreise" innerhalb des Graphen bilden, sondern dass es sehr unterschiedliche Formen zyklischer Abhängigkeiten geben kann. Um einen ersten Eindruck zu vermitteln, zeige ich hier einige Topologien, die in [Dietrich2014] vorgeschlagen werden:
Damit stehen uns nun alle Grundbegriffe zur Diskussion zyklischer Abhängigkeiten zur Verfügung.

Quellen

[Dietrich2014] - On The Shape of Circular Dependencies in Java Programs, Al-Mutawa H., Dietrich J., Marsland S., McCartin, C., Proceedings ASWEC'14, Preprint (2014)

[Parnas1978] - Designing Software for Ease of Extension and Contraction, siehe Abschnitt "D. Loops in the 'Uses' Relation", David L. Parnas (1978), siehe http://www.cs.umd.edu/class/spring2003/cmsc838p/Design/family.pdf

[Savernik2007] - Anatomie zyklischer Abhängigkeiten in Softwaresystemen,   Savernik, Leo (2007), siehe http://ssw.jku.at/Research/Reports/Report07-01/Report07-01.pdf