Software-Engineering Produktiver dank UML?

Häufig werden neue Notationen, Werkzeuge oder Methoden eingeführt in der Hoffnung, die Effizienz im Software-Engineering zu verbessern. Doch nicht immer stellt sich das gewünschte Ergebnis ein. Weder führt der Wechsel der Notation von ANSI-C zu C++ stets zu objektorientierter Programmierung, noch verbessert der Einsatz von UML-Diagrammen die Qualität der Software-Dokumentation. Tatsächlich können Maßnahmen zur Effizienzsteigerung sogar kontraproduktiv sein. Wie lassen sich solche Lapsi vermeiden?

Viele Produkte haben sich in den letzten Jahren von einfachen Geräten mit gewissen Grundfunktionen zu komplexen Systemen mit vielen Zusatzfunktionen entwickelt. In der Praxis sind im Software-Engineering moderner Systeme mindestens folgende sieben Ebenen zu berücksichtigen:

  • Timing,
  • Datenfluss,
  • Logisches Verhalten,
  • Prioritäten,
  • Versionen,
  • Varianten,
  • Betriebsmodi.

Heute ist ANSI-C immer noch die am meisten eingesetzte Notation zur Programmierung technischer Systeme. Jede Änderung auch nur einer Zeile im C-Code erfordert es, die potenziellen Auswirkungen bezogen auf alle sieben Ebenen zu berücksichtigen. Das macht Wartung, Pflege und Weiterentwicklung im Software-Engineering so anfällig.

Verständlicher dank UML

ANSI-C und C++ sind Notationen mit extrem hoher Informationsdichte. Der Nachteil: Einzelne Aspekte lassen sich nur sehr schlecht darstellen, beispielsweise der zeitliche Ablauf oder der Datenfluss. Hier kann die UML Abhilfe schaffen. Sie ist eine Notation, die in ihren einzelnen Darstellungsmöglichkeiten eine sehr viel geringere Informationsdichte hat, dafür aber einen bestimmten Fokus sehr »verstehbar« darstellen kann. Bezogen auf die oben angesprochenen Ebenen ermöglicht die UML auf Basis ihrer unterschiedlichen Diagramm-Arten verschiedene Sichtweisen auf das System mit dem Fokus auf bestimmte Ebenen.

Zum Beispiel erlauben Timing-Diagramme eine viel verständlichere Darstellung der Zeitebene (Bild 1). Das gleiche gilt für alle anderen Diagrammformen in Bezug auf andere Ebenen. Klassendiagrammen sind Spezialisten für die statische Architektur, Zustandsautomaten für das logische Verhalten, Sequenzdiagramme visualisieren den Datenfluss, Komponentendiagramme die Varianten, und so weiter.

Es handelt sich also um ein probates Mittel, um den heutigen Problemen der mangelnden Verständlichkeit von C-Programmen entgegenzuwirken. In der Praxis sieht der Einstieg in UML häufig so aus, dass Diagramme Verwendung finden, um die C-Programme zu dokumentieren und damit »verstehbarer« zu gestalten. Dazu dient häufig ein preiswertes Werkzeug, etwa Microsofts »Visio«. Doch erfüllt dies den angestrebten Zweck?

Die UML hat gegenüber C- oder auch C++-Code den Vorteil, die angesprochenen Ebenen detaillierter darzustellen. Die verbesserte »Verstehbarkeit« wird durch eine geringere Informationsdichte erkauft. Das führt zu mehrfach redundanten Informationen, die gewartet und gepflegt werden müssen.

Die Darstellung etwa eines Events kann in verschiedenen Diagrammen sinnvoll sein. Das nachträgliche Ändern des Namens des Events sollte natürlich in allen Diagrammen geschehen, in denen der Event in der Darstellung auftaucht.

Das richtige Modell für den richtigen Zweck

Aus diesem Grund haben die meisten Modellierungswerkzeuge eine Datenbank (Repository), auf deren Basis die Diagramme aufgebaut werden. In den Diagrammen steht (als Information eines Elementes) der Link in das Repository. Wird nun ein Element in einer Darstellung geändert, dann findet die Änderung nicht in der Grafik, sondern im Repository statt und dadurch in allen Diagrammen gleichzeitig.

Das Repository ist also eine sehr wichtige Voraussetzung für die effiziente Anwendung der UML. Reine Zeichenwerkzeuge erhöhen die Redundanz, und damit entstehen schnell Inkonsistenzen. Die Software wird verstehbarer, jedoch ist die Konsistenz der Informationen nicht mehr gewährleistet.

Es ist nur noch eine Frage der Zeit, wann alle Arbeiten wieder auf C-Code-Ebene stattfinden. Dann waren alle Investitionen in die UML vergeblich.

Ein weiteres Beispiel für ineffizientes Vorgehen ist die Anwendung von Modellierungsmethoden und UML-Diagrammen, die nicht mit der darunterliegenden Software-Architektur harmonieren. Eine der meist eingesetzten Diagramme zur Modellierung von technischen Systemen sind Zustandsdiagramme. Nun muss man wissen, dass Zustandsautomaten im ursprünglichen Gedanken bei Zustandsübergängen auf Ereignisse reagieren.

Viele technische Systeme haben jedoch keine ereignis-, sondern zeit-orientierte Laufzeitarchitektur. Das heißt, die verschiedenen Teile einer Applikation werden mit bestimmten Zeitintervallen von der CPU abgearbeitet. Der Datenfluss geschieht dementsprechend nicht auf Basis von Ereignissen, sondern auf Basis von Signalen (aus zeitlicher Sicht kontinuierlich gültige Werte z.B. in Form von Variablen).

Die meisten Werkzeuge, darunter »Matlab Stateflow« oder »IBM Rational Rhapsody« lassen nun auch die Modellierung von Zustands-automaten auf Basis kontinuierlich anliegender Signale zu. Dazu werden für Zustandsübergänge so genannte »Guards« verwendet, welche die Inhalte von Variablen abfragen. Als Beispiel werde nun die Logik einer Bedienerschnittstelle betrachtet.

Es gebe drei Tasten zur Bedienung eines Monitors: zwei Tasten, um Lautstärke (volume) oder Helligkeit (brightness) zu erhöhen oder zu verringern (plus, minus), und eine Taste zum Umschalten zwischen der Bedienung von Lautstärke und Helligkeit (mode). Entsprechend den Tasten werden einmal Ereignisse in Form von Events erzeugt und einmal Variablen gesetzt. In Bild 2 ist das beschriebene Verhalten auf Basis von Ereignissen, in Bild 3 auf Basis von Guards implementiert.

So weit könnte man denken, beide verhielten sich gleich, und oberflächlich betrachtet wird das Verhalten auch gleich sein. Ein genauerer Blick auf das Zeitverhalten zeigt jedoch, dass die CPU diesen Teil der Applikation nicht exklusiv ausführt, sondern in einem Zeitraster.

Ein zwar besonderer, doch nicht abwegiger Fall: Die Logik befindet sich im Modus »Helligkeit« und ein Bediener möchte die Lautstärke erhöhen. Dann wird er erst die »mode«-Taste drücken und dann die »plus«-Taste. Aber genau in diesem Augenblick ist die CPU sehr beschäftigt und der Bediener drückt die Tasten schnell hinter-einander, sodass in der Austast-lücke des Zustandsautomaten beide Tasten innerhalb eines Zeitintervalls gedrückt werden.

Jetzt reagieren beide Implementierungen unterschiedlich. Im Zustandsautomat, basierend auf Ereignissen, liegt im Hintergrund eine Queue. In ihr ist die Reihenfolge der Ereignisse gespeichert, und in genau dieser Reihenfolge arbeitet der Zustandsautomat sie auch ab. Die Logik wäre also eindeutig. Im Fall der zeitgetriebenen Architektur auf Basis von Signalen gibt es die Information, in welcher Reihenfolge die Variablen geändert wurden, aber nicht.

In dem Augenblick, in dem der Zustandsautomat wieder Rechenzeit bekommt, sind zwei Variablen geändert. Daraus ergibt sich eine zweideutige Situation: Je nachdem, in welcher Reihenfolge »mode« und »plus« abgearbeitet werden, würde einmal die Helligkeit und ein anderes Mal die Lautstärke erhöht werden. Um das zu verhindern, müssten die Zustandsübergänge priorisiert bzw. mit einer generellen Reihenfolge versehen werden, was beispielsweise in »Matlab Stateflow« möglich, in der UML aber nicht vorgesehen ist.

Auch wenn dies einige wenige Werkzeuge unterstützen, kann der Zustandsautomat schnell komplex werden, wenn immer auf Reihenfolgen zu achten ist und dass es keine mehrdeutigen Zustandswechsel geben darf. Und was ist, wenn der Anwender erst die Helligkeit erhöhen und dann erst den Modus wechseln möchte? In diesem Fall wäre eine vorgegebene Reihenfolge falsch. Dächte man nun die Logik auf Basis eines zeitgetriebenen Systems mit Signalen konsequent zu Ende, so ergäbe sich ein Ablauf- oder Aktivitätsdiagramm.

Grundsätzlich ist es also ratsam, bei Laufzeitarchitekturen mit Zeitsteuerung auf Basis von Aktivitätsdiagrammen zu modellieren und bei ereignisorientierten Laufzeitarchitekturen auf Basis von Zustandsautomaten. Leider werden häufig zeitorientierte Architekturen mit Zustandsautomaten modelliert - doch jene eignen sich nicht gut für die (auch implizite) Modellierung von Abläufen.

Das Resümee ist dann häufig die pauschale Aussage, dass die UML nicht tauglich sei. In Wirklichkeit wurde nur eine falsche Modellierungsmethode auf eine vordefinierte Laufzeitarchitektur angewandt. Generell gilt: Nur wenn Methode, Notation, Architekturdesign, Werkzeug, Anwender-Know-how und Prozess harmonisch ineinander greifen, kann die Effizienz steigen. Häufig ist das nicht der Fall, und dann ist es schade, wenn pauschal der Nutzen der UML in Frage gestellt wird.