Softwarearchitektur

Aus Das Sopra Wiki

Softwarearchitektur […] beschreibt die grundlegenden Komponenten und deren Zusammenspiel innerhalb eines Softwaresystems.[1]

Den Softwareaspekt mal außen vorgelassen, so weckt der Begriff „Architektur“ doch gewisse Vorstellungen in uns. Der Architekt – hochgebildet und erfahren – plant und entwirft Bauwerke nach praktischen und ästhetischen Gesichtspunkten und überwacht die korrekte Ausführung. Doch so sehr sich die Konstruktion von Bauwerken und die von Software auf den ersten Blick ähneln mögen und so sehr sich entsprechende Metaphern aufdrängen[2], so gibt es doch insbesondere einen entscheidenden Unterschied: Bauwerke sind vorwiegend statisch. Es sind Immobilien (lateinisch im-mobilis ‚unbeweglich‘). Software ist soft, weich, veränderbar. Und so sehr man sich auch einbilden mag, den Use Case zu kennen, allein das Wissen um diese Veränderbarkeit sorgt dafür, dass stets Bedarf für Veränderung gefunden wird.

Dies gilt umso mehr für Entwicklung von Videospielen. Hier ist das Ziel der Spielspaß, der schwer zu messen und höchst subjektiv ist.

Der Nutzen guter Architektur

Gute Architektur ist kein Selbstzweck! Es geht nicht darum, möglichst viele Design Patterns zu verwenden. Um nochmals die hinkende Metapher zum Bau zu ziehen: Gebäude sollen ihren praktischen Nutzen erfüllen (Schutz bieten und das Leben erleichtern) sowie ästhetischen Anforderungen genügen (gut aussehen und sich in die Umgebungsbebauung einfügen). Was heißt das für Software? Hier ist der tatsächliche Code meist vor dem Endanwender verborgen. Funktionale Software kann also durchaus mit schlechter Architektur gelingen. Die wahren Nutznießer guter Architektur sind also die Entwickler selbst. Eine gute und „ästhetische“ Architektur erleichtert die Entwicklung, vor allem in den Aspekten:

  • Verständnis vorhandener Funktionalität
  • Veränderungen vornehmen
  • Alle Arten des Testens (nicht nur Unit Tests, auch manuelle Tests durch den Entwickler, Integrationstests etc.)

Und eben diese Aspekte nehmen die meiste Zeit in Anspruch, je größer ein Softwareprojekt wird. Schlussendlich gilt jedoch: Der Erfolg gibt Recht. Und erfolgreich kann man mit verschiedenen Ansätzen sein.[3]

Bewährte Prinzipien

Robert C. Martin stellt in seinem Buch „Clean Architecture“ eine Reihe von Behauptungen auf:[4]

  • Architektur ist wichtiger als Funktionalität. Gute Architektur erleichtert das Vornehmen von Veränderungen. So kann Funktionalität bei guter Architektur einfach hergestellt werden. Eine funktionierende Software ohne gute Architektur funktioniert allerdings genau so lange, bis sie geändert werden muss.
  • Ein Softwarearchitekt sollte gleichzeitig auch ein Programmierer sein. Es ist schwer, gute Designentscheidungen zu treffen, wenn man von diesen nicht auch selbst betroffen ist.
  • Das Ziel guter Architektur ist es, so viele Entscheidungen wie möglich so lange wie möglich hinauszuzögern.

Anmerkung: Robert C. Martin's Bücher sind zwar stets empfehlenswert, jedoch nicht ohne Kontroversen.

Zur Umsetzung eben dieser Grundsätze gibt er folgende Ratschläge mit auf den Weg:

Grenzen abstecken (Boundaries)

In Use Cases denken statt in Implementierungsdetails

Trennung nach Funktionalität

Besonderheiten bei der Entwicklung von Videospielen

Videospiele nehmen in der Softwarewelt eine Besonderheit ein. Nicht nur technologisch, da unterschiedlichste Subsysteme in Echtzeit zusammenarbeiten müssen. Auch die Entwicklung ist davon geprägt, dass es sich um an sich um ein verbrauchbares Konsumgut handelt (die meisten Videospiele hat man irgendwann ausgespielt). Entsprechend unwichtiger ist langfristige Wartbarkeit, da sich der Lebenszyklus des Spiels kaum vorhersehen lässt. Dies äußert sich natürlich auch in der Architektur:[5]

  • Auf Applikationsebene (also der Code, der unmittelbar für das Spielgeschehen verantwortlich ist) möchte man flexibel bleiben. Hier muss es nicht immer hübsch und korrekt zugehen. Man möchte schnell herausfinden ob Dinge funktionieren und ob es Spaß macht.
  • Auf Ebene der Engine sollte Stabilität im Vordergrund stehen. Damit ist nicht zwingend gemeint, dass der Code nicht verändert wird. Vielmehr geht es um das Interface und die Funktionsweise. Niemand will schließlich, dass neue Elemente plötzlich an den Anfang statt ans Ende einer Liste gehangen werden. Mehr als sonst lohnt sich hier die Beachtung der CleanCode Prinzipien.

Jedoch ist auch hier der Übergang fließend. Gerade Datenstrukturen (z.B. ein Quad-Tree) sollten definitiv so stabil wie möglich sein und entsprechender Aufwand rentiert sich durchaus. Sollten im Spiel viele verschiedene Einheitentypen vorkommen, ist die Nutzung von Design Patterns ebenfalls zu empfehlen, damit diese ohne viel Copy&Paste implementiert werden können. Einmalige Spezialfähigkeiten hingegen können auch gerne mal schnell reingepfuscht werden. Das wichtigste Clean Code Prinzip „Don‘t repeat yourself“ (DRY) darf also im Videospielbereich durchaus ab und zu ignoriert werden.

Vorsicht! Anforderungen ändern sich. Anfangs mögen z.B. wenige Einheitentypen geplant sein und entsprechend wird das irgendwie zurechtgehackt. Doch irgendwann wird doch beschlossen, dass man mehr möchte und dann werden Veränderungen schwierig. Ob und wo sich die Anforderungen verändern, lässt sich jedoch kaum vorhersehen.

Beispiel

Eine erste Architektur entsteht meist ganz natürlich, wenn es darum geht Code zu strukturieren und Dopplungen zu vermeiden. Am Beispiel eines beliebigen Videospiels könnte das so aussehen, dass wir erst mal allen Code in unserer Game-Klasse gerade herunterschreiben. So würde für jeden Schritt/Tick Folgendes ausgeführt werden:

  • Eingabe wird erfasst (eine großes If- oder Switch-Anweisung)
  • Update der Spiellogik
  • Aktualisierung der Anzeige (Bilder werden ausgewählt und an die entsprechende Grafik-Schnittstelle weitergereicht)

Nun bietet es sich an, diese verschiedenen Funktionalitäten in entsprechenden Klassen abzubilden, z.B. InputHandler, GameLogicUpdater, DisplayDrawer. Nun sind nur noch drei Zeilen Code in unserer Game-Klasse selbst nötig:

  • inputHandler.handle(...)
  • gameLogicUpdater.update(...)
  • displayDrawer.draw(...)

Als nächstes stellen wir fest, dass in unserer GameLogicUpdater-Klasse viele verschiedene Einheiten bewegt werden: King, Queen, Knight... Alle davon haben eine Methode move(), die immer etwas anders funktioniert. Damit man die aber alle in eine Liste stecken kann, bietet sich hier die Verwendung eines Interfaces "Movable" an. Statt für jeden einzeln move() aufrufen zu müssen lässt sich das nun in einem einfachen Loop machen:

foreach (Movable movableUnit in units) { movableUnit.move(); }

Ebenso lässt sich zum Beispiel wieder der InputHandler unterteilen, in einen KeyboardInputHandler und einen MouseInputHandler. Im DisplayDrawer könnte zwischen Hintergrund, Vordergrund und GUI unterschieden werden und so weiter. Irgendeine Architektur kommt also in jedem Fall zustande. Die Qualität dieser Architektur kann schlussendlich nur dadurch bestimmt werden, wie leicht sich damit arbeiten lässt.

 

Das sieht so zwar erst mal hübsch aus, aber natürlich braucht die Spiellogik Zugriff auf die Eingabe. Und die Bewegung der verschiedenen Einheiten muss natürlich auch angezeigt werden. Die Komponenten müssen irgendwie aufeinander zugreifen und so gehen die Probleme los. Außerdem: Wie kompliziert wäre eine Änderung, wenn nicht nur Vordergrund und Hintergrund sondern eine beliebige Anzahl von Ebenen angezeigt werden soll?

Referenzen

  1. https://de.wikipedia.org/wiki/Softwarearchitektur
  2. „Metaphern sind wie Lügen, nur ausschmückender.“ (Terry Pratchett: Wachen! Wachen!)
  3. Amy Brown, Greg Wilson, editors. The Architecture of Open Source Applications. Volume I and II. 2014. https://www.aosabook.org
  4. Robert C. Martin. Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall, 2017.
  5. How to make your dream game, publish it and not die in the process