Sonntag, 24. Februar 2008

Was sind das für Zustände

In jeder Einführung zur objektorientierten Programmierung wird erklärt, daß ein Objekt einen Zustand besitzt, der durch die Werte der Attribute des Objekts bestimmt ist, und Methoden, die diesen Zustand manipulieren können. Was man häufig findet, sind Methoden, deren Wirkung von dem Zustand des Objekts abhängt. Beispielsweise könnte eine Klasse Wasser eine Methode "wird von Stein getroffen" besitzen. Diese Methode gibt bei einer Temperatur unter 0°C "knirsch" aus, zwischen 0°C und 100°C aber "platsch".

Bestimmte Anwendungen neigen besonders zu solchen Abhängigkeiten. Komplexe Eingabemasken aktivieren oder deaktivieren Eingabeelemente basierend auf Attributen. In Workflowsystemen ist es geradezu das Ziel, daß das Verhalten von dem inneren Zustand abhängt.

Wenn man sich den Code solcher Anwendungen ansieht, findet man häufig erstaunlich komplexe Abfragen, die dazu dienen, den aktuellen Zustand zu bestimmen. Besonders gruselig wird es, wenn Flags eingeführt werden und die Abfrage dann über diverse Flags gemischt mit natürlichen Werten gemacht wird.

Dieser ad hoc Umgang mit Zustand führt dazu, daß immer wieder subtile Fehler auftreten und die Stabilität der Anwendung nur sehr langsam konvergiert. Da eine solche Vorgehensweise durch Tools wie Visual Basic gefördert wird, ist es oft erstaunlich teuer, wenn man damit einen unerfahrenen Entwickler eine Maske entwickeln läßt.

Wo liegen die Probleme?

  1. Die Anzahl der möglichen Zustände ist nicht klar definiert. Aus willkürlicher Kombination von Merkmalen zur Bestimmung des Zustands ergibt sich häufig eine viel zu große Anzahl von möglichen Zuständen.

  2. Die Vollständigkeit der zu betrachtenden Übergänge ist nicht einfach festzustellen. Es kann leicht passieren, daß ein Zustand überhaupt nicht erreicht werden kann.

  3. Es kann sein, daß sich die Definitionen zweier Zustände unabsichtlich überlappen. Wenn der eine Zweig bei "grün" durchgeführt wird, der andere aber bei "rund" und beide führen zu jeweils unterschiedlichen Folgezuständen, so hängt der resultierende Zustand bei Objekten, die sowohl "grün" als auch "rund" sind von der Reihenfolge der Abarbeitung ab, was insbesondere zu interessanten Bugs führt, wenn die Abarbeitung in parallelen Threads erfolgt.

Also, was tun? Sobald Methoden von dem Zustand der Objekte abhängig werden, sollte man ein Zustandsdiagramm malen - entweder mit einem UML-Tool oder auf einem Schmierzettel. Darin werden alle möglichen Zustände festgehalten und die Bedingungen unter denen ein Zustand in einen anderen wechselt. Die Anzahl von Zuständen muß endlich sein, andernfalls müßten auch unendlich viele Verzweigungen in der Programmlogik notwendig sein. Dadurch, daß Bedingungen zum Zustandswechsel angegeben werden, wird ein entscheidender Wechsel in der Perspektive durchgeführt. Ursprünglich haben wir immer den aktuellen Zustand überprüft, wenn wir eine Entscheidung in der Programmlogik durchgeführt haben. Jetzt betrachten wir die Ereignisse, die einen Wechsel des Zustands bedingen. Dadurch wird unser drittes Problem automatisch gelöst. Das zweite Problem läßt sich so auch einfacher überprüfen und das erste wurde bereits durch die Auflistung der für den Ablauf notwendigen Zustände angegangen.

Jetzt führen wir eine Enumeration für den Zustand in die Klasse ein und können danach einfache Entscheidungen im Programmablauf durchführen. Oder wir verbinden den Code direkt mit dem Zustand und kommen zu dem GoF Zustandmuster.

Dieses Zustandsattribut erscheint redundant zu sein. Eigentlich läßt es sich aus den übrigen Attributen berechnen. Machen wir das, so führen wir durch die Hintertür in abgeschwächter Form wieder unsere alten Probleme ein. Was aber Sinn macht, ist, Zusicherungen einzubauen, die überprüfen, ob der aktelle Zustand mit dem errechneten Zustand übereinstimmt. Ist dies nicht der Fall, liegt ein Programmfehler vor.

Ein Zustandsdiagramm läßt sich leicht in Code überführen. Es gibt auch Tools, die den Code automatisch aus einem UML-Diagramm generieren. Dadurch, daß wir die Zustandsbehandlung von der übrigen Logik getrennt haben, haben wir eine erhebliche Vereinfachung erreicht. Jetzt wird die Eingabemaske auch in der geschätzten Zeit fertig und im Workflow bleiben keine Aufträge mehr überraschend hängen.


Keine Kommentare: