Analyse und Verbesserung mithilfe kognitiver Psychologie

Wie Sie technische Schulden in Architekturen abbauen

Dr. Carola Lilienthal

@shutterstock/hatoriz

Die wenigsten Entwicklungsteams haben heute noch die Chance, ein komplett neues System „from scratch“ zu entwickeln. Meistens stehen wir vor der Herausforderung, ein bestehendes, über Jahre gewachsenes System zu warten und auszubauen. Damit das auf Dauer gelingen kann, brauchen wir eine qualitativ hochwertige und flexible Architektur mit möglichst wenig technischen Schulden.

Sind die technischen Schulden gering, dann finden sich die Wartungsentwickler gut im System zurecht. Sie können schnell und einfach Bugs fixen und haben keine Probleme, kostengünstig Erweiterungen zu machen. Wie kommen wir in dieses gelobte Land der Architekturen mit reduzierten Schulden?

Technische Schulden

Wurde zu Beginn eines Softwareentwicklungsprojekts eine gute Architektur entworfen, dann kann man davon ausgehen, dass das Softwaresystem sich am Anfang schnell warten lässt. In diesem Anfangsstadium befindet sich das Softwaresystem in einem Korridor hoher Architekturqualität ohne technische Schulden (Abb. 1). Die technischen Schulden, von denen ich in diesem Artikel spreche, sind Schulden, die die Entwicklungsteams daran hindern, Fehler schnell zu finden und Änderungen kostengünstig durchzuführen. Diese technischen Schulden finden sich in der statischen Struktur des Softwaresystems. Andere technische Schulden, wie Performance- oder Securityprobleme, werden hier nicht betrachtet.

Abb. 1: Entstehung von Architekturerosion

Abb. 1: Entstehung von Architekturerosion

Wird das System nun erweitert oder werden Fehler gefixt, so wird die Architekturqualität ein wenig schlechter (gelbe Pfeile in Abbildung 1). Diese Verschlechterung ist ganz normal. Softwareentwicklung ist ein ständiger Lernprozess, bei dem der erste Wurf einer Lösung in der Regel mehrfach überarbeitet werden muss, bis er sitzt. Diese Überarbeitung der Architektur (Architekturerneuerung, grüne Pfeile in Abbildung 1) muss in regelmäßigen Abständen durchgeführt werden, damit das System seinen hohen Qualitätsstandard beibehält. Es entsteht so eine stetige Folge von Erweiterung und Refactoring.

Behält man die Architekturqualität nicht ständig im Auge, so wird im Laufe der Zeit Architekturerosion einsetzen. Über kurz oder lang verlässt das Softwaresystem den Korridor guter Architekturqualität (rote Pfeile in Abbildung 1). Die Architektur erodiert, und es entstehen immer mehr technische Schulden. Die Ursachen dieses Erosionsprozesses sind vielfältig: Aufgrund von Zeitdruck müssen wir Hacks in unser System einbauen, obwohl wir wissen, dass die Architektur eigentlich ganz anders aussehen müsste. Die Komplexität und der Kopplungsgrad in der Software wachsen unbemerkt, weil wir beim Entwickeln nicht darauf achten (können). Wir haben keine Zeit für Architekturdiskussionen, und so fehlt uns das nötige Architekturverständnis.

Lesen Sie auch: Technische Schulden in der Softwareentwicklung: weniger ist mehr

Befinden wir uns erst einmal auf dem absteigenden Ast der technischen Schulden, so werden Wartung und Erweiterung der Software immer teurer bis zu dem Punkt, an dem jede Änderung zu einer schmerzhaften Anstrengung wird. Abbildung 1 macht diesen langsamen Verfall dadurch deutlich, dass die roten Pfeile immer kürzer werden. Pro Zeiteinheit kann man bei steigenden Schulden immer weniger Funktionalität umsetzen. Änderungen, die früher einmal an einem Personentage möglich waren, dauern jetzt doppelt bis dreimal so lange. Die Software wird fehleranfällig, schwer änderbar und teuer. Um aus diesem Dilemma der technischen Schulden herauszukommen, muss die Architekturqualität rückwirkend wieder verbessert werden. Auf diesem Weg muss das System Schritt für Schritt wieder in den Korridor hoher Architekturqualität zurückgebracht werden (s. rote aufsteigende Pfeile in Abbildung 1).

Natürlich kann es auch passieren, dass zu Beginn der Entwicklung kein fähiges Team vor Ort war und dass Softwaresystem ohne Architektur oder mit einer rudimentären Architekturvorstellung entwickelt wurde. In einem solchen Fall wächst die Architektur im Laufe der Zeit ohne Plan vor sich hin. Technische Schulden werden in diesem Fall gleich zu Beginn der Entwicklung aufgenommen und kontinuierlich erhöht. Über solche Softwaresysteme kann man wohl sagen: Sie sind unter schlechten Bedingungen aufgewachsen. Auch in diesem Fall ist es die Mühe wert, das Softwaresystem von technischen Schulden zu befreien. Die Alternative Neuimplementierung steht den meisten Firmen aufgrund der hohen Investitionen heute oft nicht zur Verfügung.

Architekturanalyse und -verbesserung

Um technische Schulden zu reduzieren, ist es sinnvoll, regelmäßig zu überprüfen, ob die geplante Architektur im Sourcecode tatsächlich umgesetzt worden ist. Für solche Soll-/Istvergleiche stehen heute eine Reihe guter Tools zur Verfügung: Sotograph, SonarQube, J/N/PHP_Depend, Axovion Bauhaus, Structure101, Lattix, TeamScale, SoftwareDiagnostics u. v. m. In meinen Analysen arbeite ich mit dem Sotographen, von dem sie in diesem Artikel einige Grafiken sehen werden.

Die Sollarchitektur ist der Plan für die Architektur, der auf Papier oder in den Köpfen der Architekten und Entwickler existiert (Abb. 2). Dieser Plan ist eine Abstraktion und Vereinfachung des Sourcecodes. Häufig wird er bereits vor Beginn der Implementierung erstellt und im Laufe der Zeit an die Gegebenheiten angepasst. In der Sollarchitektur werden die Klassen und Pakete zu Subsystemen, Komponenten, Modulen (je nachdem, welchen Begriff man wählt) und Schichten zusammengefasst. Im weiteren Artikel bezeichne ich alle diese Elemente als Bausteine.

Abb. 2: Soll-/Istvergleich der Architektur

Abb. 2: Soll-/Istvergleich der Architektur

Die Sollarchitektur wird bei der Architekturanalyse mit dem echten Sourcecode abgeglichen. Der Sourcecode enthält die implementierte Istarchitektur. In allen mir bekannten Fällen weicht die Istarchitektur von der Sollarchitektur ab. Die Ursachen dafür sind vielfältig und hängen oft auch mit Architekturerosion und technischen Schulden (s. o.) zusammen. Bei der Architekturanalyse und -verbesserung machen wir uns gemeinsam mit den Architekten und Entwicklern auf die Suche nach einfachen Lösungen, wie die Istarchitektur an die Sollarchitektur angeglichen werden kann. Oder aber wir diskutieren die geplante Sollarchitektur und stellen fest, dass die im Sourcecode gewählte Lösung besser ist. In diesem Fall muss der Plan, die Sollarchitektur angepasst werden.

Neben diesem Abgleich zwischen Soll- und Istarchitektur besteht ein wichtiger Teil der Architekturanalyse darin, dass das Entwicklungsteam oder auch das Management wissen will, ob die gewählte Architektur gut wartbar ist. Um diese Frage zu beantworten, bediene ich mich bei meinen Analysen eines Modells, das ich auf Basis von Erkenntnissen aus der kognitiven Psychologie entwickelt habe.

Kognitive Psychologie als Basis der Bewertung

Das menschliche Gehirn hat sich im Laufe der Evolution einige beeindruckende Mechanismen angeeignet, die uns beim Umgang mit komplexen Strukturen helfen. Diese Mechanismen gilt es in Softwaresystemen zu nutzen, damit Wartung und Erweiterung schnell und ohne viele Fehler von der Hand gehen. Das Ziel ist dabei, dass wir unsere Softwaresysteme auch mit sich verändernden Entwicklungsteams lange bei gleichbleibender Qualität weiterentwickeln können. Die drei Mechanismen, die unser Gehirn für komplexe Strukturen entwickelt hat, sind (Abb. 3): Chunking, Bildung von Hierarchien und Aufbau von Schemata. Diese Mechanismen haben direkte Abbilder in Kriterien für die Architektur.

Abb. 3: Kognitive Mechanismen und ihr Abbild in Architektur

Abb. 3: Kognitive Mechanismen und ihr Abbild in Architektur

Aufbau von Schemata und Musterkonsistenz

Der effizienteste Mechanismus, den Menschen einsetzen, um komplexe Zusammenhänge zu strukturieren, sind so genannte Schemata. Unter einem Schema werden Wissenseinheiten verstanden, die aus einer Kombination von abstraktem und konkretem Wissen bestehen. Ein Schema besteht auf der abstrakten Ebene aus den typischen Eigenschaften der von ihm schematisch abgebildeten Zusammenhänge. Auf der konkreten Ebene beinhaltet ein Schema eine Reihe von Exemplaren, die prototypische Ausprägungen des Schemas darstellen. Jeder von uns hat beispielsweise ein Lehrerschema, das abstrakte Eigenschaften von Lehrern beschreibt und als prototypische Ausprägungen Abbilder unserer eigenen Lehrer umfasst.

Haben wir für einen Zusammenhang in unserem Leben ein Schema, so können wir die Fragen und Probleme, mit denen wir uns gerade beschäftigen, sehr viel schneller verarbeiten als ohne Schemata. Schauen wir uns ein Beispiel an: Bei einem Experiment wurden Schachmeistern und Schachanfängern für ca. fünf Sekunden Spielstellungen auf einem Schachbrett gezeigt. Handelte es sich um eine sinnvolle Aufstellung der Figuren, so waren die Schachmeister in der Lage, die Positionen von mehr als zwanzig Figuren zu rekonstruieren. Sie sahen Muster von ihnen bekannten Aufstellungen und speicherten sie in ihrem Kurzzeitgedächtnis. Die schwächeren Spieler hingegen konnten nur die Position von vier oder fünf Figuren wiedergeben. Die Anfänger mussten sich die Position der Schachfiguren einzeln merken. Wurden die Figuren den Schachexperten und Schachlaien allerdings mit einer zufälligen Verteilung auf dem Schachbrett präsentiert, so waren die Schachmeister nicht mehr im Vorteil. Sie konnten keine Schemata einsetzen und sich so die für sie sinnlose Verteilung der Figuren nicht besser merken.

Lesen Sie auch: Technische Schulden als Entscheidungsgrundlage: Wie Product Owner und Entwickler für langlebige Architekturen sorgen

Die in der Softwareentwicklung vielfältig eingesetzten Entwurfsmuster nutzen die Stärke des menschlichen Gehirns, mit Schemata zu arbeiten. Haben Entwickler bereits mit einem Entwurfsmuster gearbeitet und daraus ein Schema gebildet, so können sie Programmtexte und Strukturen schneller erkennen und verstehen, die dieses Entwurfsmuster einsetzen. Der Aufbau von Schemata liefert für das Verständnis von komplexen Strukturen also entscheidende Geschwindigkeitsvorteile. Das ist auch der Grund, warum Muster in der Softwareentwicklung bereits vor Jahren Einzug gefunden haben.

Muster kann man bei der Architekturanalyse nicht messen, aber man kann sie sichtbar machen und ihre Umsetzung diskutieren. Ich untersuche einerseits die Muster auf der Architekturebene an und andererseits die Muster auf der Klassenebene. Auf beiden Ebenen ist für die Entwickler und Architekten wichtig, dass es Muster gibt, dass sie im Sourcecode wiederzufinden sind und dass sie einheitlich und durchgängig eingesetzt werden. Deshalb verwende ich für diesen Bereich den Begriff „Musterkonsistenz“.

Mangelnde Musterkonsistenz weist beispielweise Abbildung 4 auf. In der Abbildung sieht man den Package-Baum eines Java-Systems. Die Pfeile gehen jeweils vom übergeordneten Package zu seinen Kindern. Das System ist in vier Komponenten aufgeteilt, die im Package-Baum durch vier Farben markiert sind. An den gestrichelten Linien ist zu erkennen, dass zwei der Komponenten (orange und lila) über den Package-Baum verteilt sind. Diese Verteilung ist nicht konsistent zu dem von der Architektur vorgegebenen Muster und führt bei Entwicklern und Architekten zu Verwirrung. Das Einführen von jeweils einem Package-Root-Knoten für die orangene und die lila Komponente würde hier Abhilfe schaffen.

Abb. 4: Das Architekturmuster ist schlecht umgesetzt

Abb. 4: Das Architekturmuster ist schlecht umgesetzt

Auf der Klassenebene werden heute in vielen Systemen Entwurfsmuster eingesetzt. Sie leiten die Entwickler noch stärker als die Muster auf Architekturebene. In Abbildung 5 sieht man ein anonymisiertes Tafelbild, das ich mit einem Team entwickelt habe, um seine Muster aufzunehmen. Auf der rechten Seite von Abbildung 5 ist der Sourcecode in diese Musterkategorien eingeteilt, und man sieht sehr viele grüne und einige wenige rote Beziehungen. Die roten Beziehungen gehen von unten nach oben gegen die durch die Muster entstehende Schichtung. Die geringe Anzahl der roten Beziehungen ist ein sehr gutes Ergebnis und zeugt davon, dass das Entwicklungsteam seine Muster sehr konsistent einsetzt. Spannend ist bei der Analyse noch, welchen Anteil des Sourcecodes man Mustern zuordnen kann und wie viele Muster das System schlussendlich enthält. Lassen sich 80 Prozent oder mehr des Sourcecodes Mustern zuordnen, so spreche ich davon, dass dieses System eine Mustersprache hat. Für die „richtige“ Anzahl an Mustern habe ich keine exakte Zahl. Wichtig ist vielmehr, dass die vorhandenen Muster tatsächlich unterschiedliche Konzepte darstellen und nicht Varianten eines Konzepts sind. Beispiele für solche Varianten könnten zum Beispiel zwei Muster sein, die „Service“ und „Manager“ heißen. Hier wäre zu klären, was den Manager von einem Service unterscheidet und in welchem Verhältnis sie zueinander stehen.

Abb. 5: Muster auf Klassenebene = Mustersprache

Abb. 5: Muster auf Klassenebene = Mustersprache

Die Untersuchung der Muster im Sourcecode ist in der Regel der spannendste Teil einer Architekturanalyse. Hier hat man die Ebene zu fassen, auf der das Entwicklungsteam wirklich arbeitet. Die Klassen, die die einzelnen Muster umsetzen, liegen oft über die Packages oder Directories verteilt. Mit einer Modellierung der Muster wie in Abbildung 5 rechts kann man diese Ebene der Architektur sichtbar und analysierbar machen.

Chunking und Modularität

Damit Menschen in der Menge der Informationen, mit denen sie konfrontiert sind, zurechtkommen, müssen sie auswählen und Teilinformationen zu größeren Einheiten gruppieren. Dieses Bilden von höherwertigen Abstraktionen, die immer weiter zusammengefasst werden, nennt man Chunking. Dadurch, dass Teilinformationen als höherwertige Wissenseinheiten abgespeichert werden, wird das Kurzzeitgedächtnis entlastet und weitere Informationen können aufgenommen werden. Chunking kann unser Gehirn allerdings nur dann anwenden, wenn die Teilinformationen eine sinnvolle zusammenhänge Einheit bilden. Bei unzusammenhängenden Informationen gelingt uns Chunking nicht.

Entwickler wenden Chunking automatisch an, wenn sie sich unbekannte Programme erschließen müssen. Der Programmtext wird im Detail gelesen, und die gelesenen Zeilen werden zu Wissenseinheiten gruppiert und so behalten. Schritt für Schritt werden die Wissenseinheiten immer weiter zusammengefasst, bis ein Verständnis des benötigten Programmtexts erreicht ist. Allerdings funktioniert auch bei Softwaresystemen das Chunking nur dann, wenn die Struktur des Softwaresystems sinnvoll zusammenhängende Einheiten darstellt. Programmeinheiten, die beliebige Operationen zusammenfassen, sodass für die Entwickler nicht erkennbar ist, warum sie zusammengehören, lassen sich nicht in Wissenseinheiten codieren. Für unsere wartbaren Softwarearchitekturen ist es also essenziell, dass sie Bausteine, wie Klassen, Komponenten, Module, Schichten, enthalten, die sinnvoll zusammenhängende Elemente gruppieren.

Ob die Bausteine in einer Softwarearchitektur zusammenhängende Elemente darstellen, lässt sich leider nicht messen oder mit Analysewerkzeugen überprüfen. Um bei der Analyse von Architekturen trotzdem Aussagen über die Modularität machen zu können, untersuche ich die folgenden Aspekte:

  • Entwurf nach Zuständigkeit: Sind die Bausteine eines Systems modular gestaltet, so sollte man für jeden Baustein die Frage beantworten können: Was ist sein Aufgabe? Der entscheidende Punkt dabei ist, dass der Baustein wirklich eine Aufgabe hat und nicht mehrere. Diese Frage ist natürlich nur im fachlichen Kontext des jeweiligen Systems gemeinsam mit dem Entwicklerteam zu klären. Anhaltspunkte bei der Suche nach Bausteinen mit unklarer Zuständigkeit sind:
    • Der Name des Bausteins – der Name eines Bausteins sollte seine Aufgabe beschreiben. Ist der Name schwammig, so sollte man ihn sich ansehen.
    • Seine Größe (s. nächster Punkt)
    • Der Umfang seiner Kopplung mit anderen Bausteinen – wird ein Baustein sehr viel von allen möglichen anderen Bausteinen verwendet, so liegt die Vermutung nahe, dass er ein Sammelbecken von vielfältigen nicht unbedingt zusammenhängenden Funktionalitäten ist.
    • Seine mangelnde Musterkonsistenz (s. Abschnitt Musterkonsistenz).
  • Ausgewogene Größenverhältnisse: Bausteine, die auf einer Ebene liege, also die Schichten, die fachlichen Module, die Packages, die Klassen oder die Methoden, sollten untereinander ausgewogene Größenverhältnisse haben. Hier lohnt es sich, die sehr großen Bausteine zu untersuchen, um festzustellen, ob sie Kandidaten für eine Zerlegung sind.
  • Zusammengehörigkeit durch Kopplung untereinander: Bausteine sollten Subbausteine enthalten, die zusammengehören. Eine Klasse sollte beispielsweise Methoden enthalten, die gemeinsam ein Ganzes ergeben. Dasselbe gilt für größere Bausteine, wie Packages, Komponenten, Module und Schichten. Haben die Subbausteine mehr mit anderen Bausteinen zu tun, als mit ihren „Schwestern und Brüdern“, dann stellt sich die Frage, ob sie nicht eigentlich in einen anderen Baustein gehören.

Bildung von Hierarchien und Hierarchisierung

Hierarchien spielen beim Wahrnehmen und Verstehen von komplexen Strukturen und beim Abspeichern von Wissen eine wichtige Rolle. Menschen können Wissen dann gut aufnehmen, es wiedergeben und sich darin zurechtfinden, wenn es in hierarchischen Strukturen vorliegt. Untersuchungen zum Lernen von zusammengehörigen Wortkategorien, zur Organisation von Lernmaterialien, zum Textverstehen, zur Textanalyse und zur Textwiedergabe haben gezeigt, dass Hierarchien vorteilhaft sind. Bei der Reproduktion von Begriffslisten und Texten war die Gedächtnisleistung der Versuchspersonen deutlich höher, wenn ihnen Entscheidungsbäume mit kategorialer Unterordnung angeboten wurden. Lerninhalte wurden von den Versuchspersonen mithilfe von hierarchischen Kapitelstrukturen oder Gedankenkarten deutlich schneller gelernt. Lag keine hierarchische Struktur vor, so bemühten sich die Versuchspersonen, den Text selbstständig hierarchisch anzuordnen. Die kognitive Psychologie zieht aus diesen Untersuchungen die Konsequenz, dass hierarchisch geordnete Inhalte für Menschen leichter zu erlernen und zu verarbeiten sind und dass aus einer hierarchischen Struktur effizienter Inhalte abgerufen werden können.

Die Bildung von Hierarchien wird in Programmiersprachen bei den Enthalten-Seins-Beziehungen unterstützt: Klassen sind in Packages, Packages wiederum in Packages und schließlich in Projekten bzw. Build-Artefakten. Diese Hierarchien passen zu unseren kognitiven Mechanismen. Sind die Hierarchien an die Muster der Architektur angelehnt, so unterstützen sie uns nicht nur durch ihre hierarchische Strukturierung, sondern sogar noch durch Architekturmuster.

Lesen Sie auch: Softwarearchitektur: Wie man komplexe Architekturen schuldenfrei entwickelt

Für alle anderen Arten von Beziehungen gilt das nicht: Wir können beliebige Klassen und Interfaces in einer Sourcecode-Basis per Benutzt-Beziehung oder/und per Vererbungsbeziehung miteinander verknüpfen. Dadurch erschaffen wir verflochtene Strukturen (Zyklen), die in keiner Weise hierarchisch sind. Es bedarf einiges an Disziplin und Anstrengung, Benutzt-Beziehung und Vererbungsbeziehung hierarchisch zu verwenden. Verfolgen die Entwickler und Architekten von Anfang an dieses Ziel, so sind die Ergebnisse in der Regel nahezu zyklenfrei.

In meinen Analysen bekomme ich die ganze Bandbreite von sehr wenigen zyklischen Strukturen bis zu großen zyklischen Monstern zu Gesicht. Ähnlich wie bei den Mustern und der Modularität kann man Zyklen auf Architekturebene und auch Klassenebene untersuchen.

In Abbildung 6 sieht man vier technische Schichten eines kleinen Anwendungssystem (80 000 LOC). Zwischen den Schichten haben sich einige Rückreferenzen (rote Bögen) eingeschlichen, die zu Zyklen führen. Die Zyklen in Abbildung 6 werden nur durch sechzehn Klassen hervorgerufen und lassen sich in diesem Fall leicht ausbauen. Abbildung 6 stellt also eine gut gelungene Schichtenarchitektur dar.

Abb. 6: Zyklen auf Architekturebene

Abb. 6: Zyklen auf Architekturebene

Der Klassenzyklus in Abbildung 7 stammt von einem anderen System. Die 242 Klassen in diesem Zyklus sind über achtzehn Verzeichnisse verteilt. Jedes Verzeichnis ist in Abbildung 7 mit einer anderen Farbe vertreten.

Abb. 7: Zyklus aus 242 Klassen

Abb. 7: Zyklus aus 242 Klassen

Insgesamt hat das System, aus dem der Zyklus in Abbildung 7 stammt, 479 Klassen. Hier brauchen sich also über die Hälfte aller Klassen (242) direkt oder indirekt. Noch dazu hat dieser Zyklus eine starke Konzentration im Zentrum und wenige Satelliten. Eine natürliche Möglichkeit, ihn anhand von Kopplungszentren zu zerlegen, bietet sich also nicht an. Zum Glück finden sich in den meisten Systemen kleinere und weniger konzentrierte Zyklen, die man mit wenigen Refactorings zerlegen kann.

Zusammenfassung

In diesem Artikel haben Sie einen ersten Eindruck bekommen, wie technische Schulden in Architekturen entstehen und wie man sie reduzieren kann. Technische Schulden lassen sich abbauen, indem man Strukturen schafft, die unser Gehirn leicht verarbeiten kann. Hat man die Architektur in diese Richtung verbessert, so geht Wartung und Erweiterung effizienter und schneller von der Hand. Weitere Details zur Analyse von technischen Schulden und architekturverbessernden Refactorings finden Sie in meinem Buch „Langlebige Softwarearchitekturen“ vom dpunkt.verlag.

 

Aufmacherbild: Technical debt on the wall von Shutterstock / Urheberrecht: hatoriz

Geschrieben von
Dr. Carola Lilienthal
Dr. Carola Lilienthal
Dr. Carola Lilienthal ist Senior-Softwarearchitektin bei der Workplace Solutions GmbH und Mitglied der Geschäftsleitung. Sie hat an der Universität Hamburg studiert und dort zum Thema „Komplexität von Softwarearchitekturen“ promoviert. Seit 2003 analysiert sie im Auftrag ihrer Kunden in ganz Deutschland regelmäßig die Architektur von Softwaresystemen und fasst das Ergebnis in Qualitätsgutachten sowie mit priorisierten Refactoring-Maßnahmen zusammen. Außerdem leitet sie seit 2000 Softwareprojekte von unterschiedlicher Größe im Banken-/Versicherungs- und Logistikbereich und berät das Management kleiner und mittelständischer Unternehmen bei der Entwicklung einer modernen IT-Strategie.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: