Deptrac zur Verwaltung der Abhängigkeiten zwischen Software-Komponenten


Deptrac ist ein neues Tool, das uns dabei hilft, die Abhängigkeiten zwischen einzelnen Komponenten unserer Software zu verwalten. Sinnvolle Regeln für diese Abhängigkeiten zu definieren ist eine wesentliche Aufgabe des Software-Designs.

Ich glaube, dass Deptrac eine große (und positive) Auswirkung auf die Entwicklung der PHP-Community haben kann. Um das zu Begründen, werde ich in diesem Artikel erst einige Worte über Software-Design, insbesondere Software-Komponenten schreiben, um dann zu versuchen, den Nutzen zu zeigen, den ich mir von diesem Tool für die Qualität von PHP Projekten erhoffe.

Über Software Design und Abhängigkeiten

Software Design Prinzipien wie SOLID regeln im Wesentlichen die Beziehungen (oder "Abhängigkeiten") zwischen verschiedenen Teilen einer Software. In tausend Tutorials werden diese Prinzipien anhand von Klassen und deren Beziehungen zueinander behandelt. Dies ist gut und richtig, dennoch sollte man sich auch auf einer etwas höheren Ebene um Kapselung kümmern. Diese höhere Ebene ist die Ebene von Software-Komponenten. (Es gibt dafür auch andere Namen: Packages, Module, Layer, ...).

Software Komponenten

Eine Komponente besteht aus einer Anzahl von Klassen, die untereinander relativ eng gekoppelt sein dürfen, und die gemeinsam eine bestimmte Aufgabe zu erfüllen haben. Die Komponente sollte ein Interface haben, über das andere Komponenten der Gesamt-Applikation, oder sogar komplett getrennte Systeme (z.B. über eine REST Api) mit der Komponente kommunizieren können. Mit Interface ist hier explizit nicht ein einzelnes PHP Interface gemeint, sondern eine API, bestehend aus mehreren Interfaces und auch Klassen. Es gibt natürlich unterschiedliche Arten von solchen Komponenten. Oftmals versucht man, die Kern-Komponenten so zu gestalten, dass sie keinerlei Abhängigkeiten zu anderen Komponenten haben, sondern nur Interfaces definieren, mit denen andere Komponenten die angebotenen Services nutzen können. Für eine vollwertige Applikation benötigt man jedoch auch eine andere Art von Komponenten: Diese implementieren die Interfaces der Kern-Komponenten und nutzen dessen Services, um Eingaben zu verarbeiten bzw. Ausgaben zu erzeugen. Die verschiedenen Arten von Komponenten sind also ein Grundbaustein des Software Designs. Architektur-Muster wie Hexagonal architecture, oder Onion architecture befassen sich mit den Beziehungen zwischen Komponenten.

Warum PHP namespaces keine Software Komponenten sind

Im Prinzip könnte man Software Komponenten in Namespaces verwalten. Dies ist aus meiner Sicht aber nicht sinnvoll, zur Hauptsache weil Hierarchien in Namespaces nützlich sind, in Komponenten jedoch nicht (mindestens nicht in gleichem Maße).

Wie der Name schon sagt, ist die Aufgabe von Namespaces, Namen in "Räume" aufzuteilen. Das geschieht in der Regel auf eine Weise, in der man die Klassen gut wiederfindet. Hierarchie ist hier sehr nützlich.

Software-Komponenten als einzelne, miteinander in Beziehung stehende Teile eines Gesamtsystems sind meiner Ansicht nach sinnvollerweise nicht hierarchisch. Man kann Komponenten durchaus nach der Art ihrer Aufgabe logisch gruppieren, was bei einer umfangreichen Applikation bestimmt sinnvoll ist. Unter "hierarchische Komponenten" verstehe ich hier jedoch, dass der Hierarchie eine Bedeutung in Bezug auf das System der Abhängigkeiten (also auf den Graph mit Komponenten als Knoten und Abhängigkeiten als Kanten) gegeben wird.

Hierzu ein Beispiel. Eine Anwendung enthält unter anderem folgende Komponenten:

  • Eine Datenbank Bibliothek
  • Ein Client für die Web-API eines entfernten Systems

Beide haben die Aufgabe, der Applikation Zugriff auf Daten eines externen Systems zu geben. Man kann diese nun beide als Infrastruktur Komponenten bezeichnen und gruppieren. Dafür kann ein gemeinsamer übergeordneter Namespace ggf. sinnvoll sein. Betrachtet man die Komponenten nun aber aus Sicht der Abhängigkeiten, dann muss man diese separat betrachten. Das Zusammenfassen in eine übergeordnete Komponente macht keinen Sinn. Eine dritte Komponente, welche Zugriff auf die Web-Api benötigt, hat vielleicht keinen Bedarf an der Datenbank-Funktionalität. Keinesfalls sollte eine Abhängigkeit zwischen den beiden Infrastruktur-Komponenten bestehen. Die übergeordnete Komponente hätte keine eigene Aufgabe, die über das Zusammenfassen der beiden untergeordneten Komponenten hinaus geht.

(Anmerkung: Ich denke man kann ein System durchaus mit hierarchischen Komponenten modellieren, allerdings würde die Definition der Abhängigkeiten zwischen den Komponenten komplexer, und ich bin mir nicht sicher, ob diese Komplexität einen Vorteil hätte...).

Analyse der Abhängigkeiten nach Robert C. Martin und PDepend

In dem Paper OO Design Quality Metrics - An Analysis of Dependencies hat Robert C. Martin die Art von Komponenten (er nennt sie "Categories") und deren Abhängigkeiten sehr schön analysiert. Er hat in dem Paper für verschiedene Arten von Kategorien (Komponenten) wünschenswerte und nicht wünschenswerte Abhängigkeiten beschrieben, und dafür Messgrößen eingeführt. Basis für diese Messgrößen sind die Anzahl eingehender und ausgehender Abhängigkeiten, sowie die Anzahl Klassen und Interfaces in einer Komponente. Eingehende Abhängigkeit bedeutet, dass eine Klasse aus einer anderen Komponente abhängig von einer Klasse aus der zu betrachtenden Komponente ist, und ausgehende Abhängigkeit bedeutet umgekehrt, dass eine Klasse aus der zu betrachtenden Komponente abhängig von einer Klasse (oder einem Interface) aus einer anderen Komponente ist. In PHP wird zur Erhebung dieser Kennzahlen vor allem PDepend verwendet. PDepend interpretiert jeden Namespace als Komponente ("Package" in der Terminologie von PDepend). Hierarchische Namespaces werden als "flache" Liste von Komponenten analysiert. Beispiel:

  • MyApp\Core
  • MyApp\Core\Domain
  • MyApp\Core\Controller

Dies ist in der Analyse von PDepend das gleiche wie:

  • MyApp\Filesystem
  • AnotherVendor\Library
  • Utils

Ich finde die Kennzahlen, die Robert C. Martin definierte, nach wie vor sinnvoll. Wenn PDepend erlauben würde, eine Gruppe von Klassen -- unabhängig von deren Namespace -- als package zu definieren, wäre es ein sehr nützliches Tool. Genau dies ist eines der wesentlichen Features, die Deptrac bietet. Wobei wir nun endlich bei dem eigentlichen Thema dieses Artikels wären...

Deptrac

Deptrac ist ein sehr neues Werkzeug zur statischen Code Analyse. Der Anwendungsfall ist ein etwas anderer als der von PDepend. Das Resultat der Analyse sind nicht eine Menge von Messwerten, sondern ein Graph, der die Abhängigkeiten zwischen den Komponenten darstellt. Außerdem noch die Information, ob diese Abhängigkeiten erlaubt sind oder nicht. Zu bestimmen, welche Abhängigkeiten erlaubt sind, ist Bestandteil des Software Designs. Diese Regeln werden, zusammen mit der Definition der Komponenten (in Deptrac "Layers" genannt), in eine Konfigurationsdatei geschrieben.

Zusammengefasst:

  • Im Design Prozess werden Komponenten und deren Abhängigkeiten spezifiziert.
  • Diese werden in einer Konfigurationsdatei festgehalten.
  • Das Tool überprüft die Einhaltung der Regeln.

Im Gegensatz zu PDepend und den meisten anderen Werkzeugen zur statischen Code Analyse ist also vorab etwas Arbeit notwendig. Es ist nach meiner Meinung aber genau diese Aufgabe, die einen wesentlichen Teil von Software Design ausmacht. Die Gedanken, die man sich hierzu macht, sind absolut notwendig, wenn man einen Wildwuchs an Abhängigkeiten verhindern will. Durch die Konfigurationsdatei, sowie durch den als Ausgabe erzeugten Graph sind sowohl Komponenten (Layers) als auch Abhängigkeiten (Rules) sehr gut dokumentiert. Dies kann neuen Entwicklern helfen, schnell einen Überblick über die Software zu gewinnen.

Die einzige Gefahr sehe ich im Moment darin, dass neue Abhängigkeiten nur dann sichtbar werden, wenn sie als Komponente deklariert werden. D.h. wenn ein Entwickler z.B. per composer eine neue Library einführt, und diese nicht als Komponente definiert (und sie auch nicht in eine bereits deklarierte Komponente fällt), wird eine Abhängigkeit zu dieser Library weder als Regelverletzung ausgegeben, noch taucht sie in dem generierten Abhängigkeitsgraph auf. (Vielleicht ließe sich das durch eine Art Fallback "Komponente" lösen, die alle Klassen im vendor Verzeichnis einschließt... habe ich noch nicht versucht).

Entwicklungsstand

Aktuell ist Deptrac noch "in einem frühen Entwicklungszustand" (Stand 2016-05-04, gemäß der readme). Aus meiner Sicht kann man aber nichts verlieren, wenn man für sein neues Projekt die Komponenten und deren Abhängigkeiten definiert, und in einer Konfigurationsdatei festlegt. Vielleicht würde es auch dem ein oder anderen bestehenden Projekt gut tun, durch dieses Tool eine neue Sicht auf die Architektur des Systems zu gewinnen. Allerdings ist dort natürlich der Aufwand je nach Umfang der Anwendung ziemlich groß.

Fazit

Ich hoffe, dass die Idee, eine Sammlung von Klassen als Komponente zu definieren, um dann Regeln für die Abhängigkeiten zwischen diesen Komponenten zu spezifizieren, die Entwicklung im PHP Umfeld nachhaltig verändern wird. Deptrac bietet hierzu zumindest mal einen Anstoß (wenn man sehr kritisch denkt). Dafür bin ich den Machern sehr dankbar :-)

Irgendwo habe ich in Bezug auf Software Metriken mal den Satz gelesen: "Messe nur, was du auch verstehst". Ich denke das, was Deptrac "misst", ist relativ einfach zu verstehen, und gleichzeitig wesentlich.

Ich empfehle jedem Entwickler (und Architekten), dieses Tool auszuprobieren. Und bei Bedarf natürlich auf github zur Weiterentwicklung beizutragen.

Author: Claudio Kressibucher
Tags: PHP, Tools, Qualität