Mittwoch, Februar 13, 2008

Vom Entwurf zum Programm




Was lernt man nicht alles für feine Dinge als Softwaretechniker: Die Software-Entwürfe entstehen am Reißbrett, oftmals hart erarbeitet, und sie präsentieren sich als schicke Diagramme. Dabei bedienen wir uns einer "Geheimnotation" namens Unified Modeling Language (UML), die für Laien kaum verständlich ist. Da gibt es Kästchen, Linien, Rauten, Zahlen, Benamungen, Textanmerkungen -- das wirkt alles sehr imposant. Es braucht einiges an Zeit, ehe man die hohe Kunst des Software-Entwurfs erlernt hat.

Irgendwann findet man sich jedoch auf der Programmier-Baustelle wieder. Hier werden die Entwürfe in für einen Computer verständliche Anweisungen umgesetzt. Und, wenn man genau hinschaut, geht es hier schlimmer zu als auf jeder echten Baustelle. Der Programmcode hat nur lose Ähnlichkeiten mit den Entwürfen. Programmierer ziehen mal hier und mal dort eine Wand ein, halten an anderer Stelle einen Durchbruch für angemessen, und das Maßband scheint ihnen völlig abhanden gekommen zu sein. "Pi mal Daumen" -- ja, so ungefähr entspricht das dem Entwurf. Wer seine Aufgabe als Bauherr auf der Programmier-Baustelle ernst nimmt, der kann verzweifeln.

Ein kleines Beispiel gefällig?

Schauen Sie sich mal oben das Bild an. Ein UML-Diagramm, das ich in einer Vorlesung nutze. Es ist ein Entwurf für ein OO-Framework. Hübsch anzusehen, gell? Ich weiß, das Diagramm ist nicht perfekt -- aber darauf kommt es mir hier nicht an.

Zu diesem OO-Framework gibt es eine einfache Implementierung in Python von mir. Auch die spreche ich mit den Studierenden durch. Vor wenigen Wochen, als ich versuchte, den Code zu erklären, griff ich zur Kreide. Ich begann, die Referenzbezüge, so wie sie im Python-Code aufgebaut sind, in einer informellen Notation an die Tafel zu malen, in der Hoffnung, so werde sich der Wald lichten. Tat er aber nicht. Das Ganze entwickelte sich zu einer wunderschönen Fallstudie, wie schwer sich Entwurf und Implementierung tun, aufeinander zu passen.

Was haben die beiden Bilder gemeinsam? Man erkennt Ähnlichkeiten. Die großen Kästchen tragen die gleichen Namen. Aber sonst? An der Tafel sind verwirrend viele Linien hinzugekommen. Wie kommt's?

Ein Kernproblem ist, dass die Ausdrucksmittel aus dem UML-Diagramm keine klare Entsprechung in meiner Zielsprache, der Programmiersprache Python haben. Ich muss also mit "Workarounds" arbeiten, die die Intentionen aus dem Entwurf umsetzen. Zum Beispiel die Kompositionen. In der UML baut eine Komposition besondere Zugriffsbezüge und Abhängigkeiten in den Lebensbezügen auf. In den gängigen OO-Sprachen, wie auch in Python, Java, C# oder Ruby gibt es jedoch keine Komposition. Die meisten OO-Sprachen basieren auf Objekt-Referenzen, die keine Zugriffsbezüge durch Namensräume regeln, jedoch auf den Lebenszyklus von Objekten einen erheblichen Einfluss haben. Also bildet man die Intention der Komposition auf anderen Wegen ab und sorgt sich um einen gesitteten Umgang mit Referenzen. Ähnliches gilt für das "instanceOf".

Und hier gehen die Probleme los. Wer das intendierte Mapping von Code und Entwurf nicht kennt -- dieses Mapping wird nämlich selten, eher gar nicht dokumentiert --, der steht in der Gefahr, irgendwann einmal am Code Änderungen vorzunehmen, die den Entwurf verletzen und der austarierten Balance von Code und Entwurf radikal Schaden zufügt. So entstehen Fehler, die in aller Regel schwer zu entdecken, wenn, dann meist folgenreich sind und deren Beseitigung erhebliche Anstregungen kostet.

Ein anderes Beispiel. In Python nutze ich gerne Dictionaries (sie kennen ähnliches vielleicht als HashMaps oder assoziatives Arrays) -- und zwar als Caches für einen schnellen Zugriff auf Objekte unter einem Schlüssel. Es sind die "Qualifier" (die kleinen Boxen an den großen Boxen) auf dem Tafelbild. Streng genommen, mit Blick auf den Entwurf, bräuchte ich sowas nicht. Doch der Einsatz von Dictionaries macht den Python-Code oft einfacher und übersichtlicher. Der Preis: Ich entferne mich vom Entwurf. Die Dictionaries beginnen ein Eigenleben zu führen.

Wenn Sie Studentin oder Student der Informatik sind, dann haben sie wahrscheinlich zahllose, weitere solche Beispiele auf Lager, die einen erheblichen "Clash" aufzeigen zwischen Entwurf und Umsetzung. Bisweilen entsteht das UML-Diagramm zum Entwurf erst nachträglich, nach der Programmierung, und schönt und verhübscht die "Häßlichkeiten" des Codes erheblich.

Sind Codegeneratoren ein Ausweg, die aus UML-Diagrammen direkt den Programm-Code (z.B. für Python) erzeugen? Jaein. Zum einen ist es gar nicht so einfach, gute schematische Code-Templates für die Code-Generation zu entwickeln. Der Versuch, Komposition sauber in einer rein referenz-basierten Sprache nachzubilden, ist nicht wenig aufwendig und versucht einer Sprache etwas überzustülpen, wofür sie nicht gemacht wurde. Das ist zumindest bei einem Blick auf das Laufzeitverhalten nicht ganz außer acht zu lassen. Allein die Nachbildung von Singletons, ein lapidarer Stereotyp in der UML, ist nicht trivial, besonders wenn Singletons thread-safe sein sollen.

Doch das eigentliche Problem liegt in meinen Augen tiefer. Code-Generierung aus Entwürfen ist ok, sofern man einen ausführbaren Entwurf haben möchte. Nicht immer ist das sinnvoll. Wichtig ist zu erkennen, dass es einen Unterschied gibt zwischen Entwurf (Design) und Umsetzung (Implementierung). Eine Umsetzung muss einen Entwurf erfüllen. Nicht mehr und nicht weniger. Das wird wenig verstanden. Ich habe versucht in meinen Posts zu User, Domain und Realization Model darauf in einem anderen Kontext einzugehen. Denn häufig sehe ich nicht nur Studierende, sondern auch Profis einen Fehler machen: Es wird weniger über einen Entwurf nachgedacht! Es wird Programmcode mit Hilfe von UML-Diagrammen beschrieben, nicht ein Entwurf! Dann wird der Code-Generator nur zum Erfüllungsgehilfen von mit Diagrammen "gemaltem" Code.

Glauben Sie nicht? Wann haben Sie das letzte Mal Mehrfachvererbung angewendet? Das wird Ihnen als Ausdrucksmittel in der UML für Ihre Entwürfe angeboten und kann ganz hilfreich sein. Was? Mehrfachvererbung nutzen Sie nicht? Warum? Weil Sie Java- oder C#-Programmierer sind? Erwischt! :-)

Kommentare:

Till hat gesagt…

"Bisweilen entsteht das UML-Diagramm zum Entwurf erst nachträglich, nach der Programmierung, und schönt und verhübscht die "Häßlichkeiten" des Codes erheblich."

Das halte ich fast noch für eine Untertreibung. Ich habe über die wenigen Semester die Erfahrung gemacht, dass UML sehr häufig erst im Nachhinein entsteht.

Warum?
Naja zum einen würde einen die Modellierung ja dazu zwingen sich vorher intensiv mit dem entwerfenden System außeinander zu setzen ;-)
Dazu fehlt (auch mir) nicht selten die Selbstüberwindung. Man wüsste es eigentlich besser. Macht es dann aber doch nicht.

Zum anderen liegt es sicher auch daran, dass es noch zu wenig UML Know-How gibt. Das betrifft natürlich nicht nur dei Professoren. Ich erlebe es auch während meiner Arbeit als Werkstundent ebenfalls. In den Firmen existiert zu wenig Know-How zur UML was dazu führt das sich Diagramme nur begrenzt an den Standard halten und somit ad absurdum geführt werden.

Ich denke es wäre wichtig dafür zu sorgen, dass das UML Know-How den Studenten nicht nur vermittelt wird, sondern auch regelmäßig eingefordert.

Noch etwas für alle die Ihr Wissen gerne verbrieft hätten gibt es
hier
die Möglichkeit sich zertifizieren zu lassen. Wäre cool wenn man das als Zusatzqualifikation in unserem Studiengang erwerben könnte...

Anonym hat gesagt…

"In den Firmen existiert zu wenig Know-How zur UML was dazu führt das sich Diagramme nur begrenzt an den Standard halten und somit ad absurdum geführt werden."

UML nach Standard zu verwenden ist wahrscheinlich einfach ein viel zu großer Aufwand. Jedenfalls für mich. UML im "sketching mode" reicht im Normalfall aus. Vor allem, wenn man den Quelltext nicht automatisiert aus dem Diagramm generiert. Und was Zertifizierungen angeht: Die schützt einen wahrscheinlich nicht davor, ein System zu entwerfen, dass in der Praxis nichts taugt, aber in 'perfektem' UML ganz toll aussieht. Wer sich einmal die paar hundert Seiten UML-Standard durchgeblättert hat, verliert wahrscheinlich schnell die Lust an UML. ;)

In meinen Augen ist UML hilfreich (im sketching mode, um ein gemeinsames Vokabular zu haben und Entwürfe zu visualisieren und zu besprechen), aber überbewertet (zumindest, wenn man eher am Aussehen von Diagrammen und deren formaler Richtigkeit rumdeutelt, statt über den vorgelegten Entwurf sinnvoll zu diskutieren)

Gereon hat gesagt…

Ich merke immer wieder wie wichtig es wäre, bei der Programmentwicklung vernünftig zu planen. Das Problem ist aber: UML ist total unpraktisch.

Ich plane gerne in meinem Notizbuch mit dem Bleistift. Das geht schon kaum. Wenn es sich zB heraustellt, dass eine Box doch etwas größer gezeichnet werden müsste, da noch mehr heineinpassen muss, dann ist mit dem Radierer meist schon nichts mehr zu machen und ich muss das Kunstwerk von vorne beginnen.

Gut, man kann auch einen UML-Editor verwenden. Dann gibt es ein weiteres Problem: Ich will mir die Entwürfe ausdrucken, darauf rumschmieren und sie abhaften können. Mit UML hat man das Problem, die Zeichnung auf einem DIN-A4-Blatt unterzubringen. Wenn die Zeichnung zu groß wird muss sie arg verkleinern oder aufteilen - alles nicht das wahre.

Ein weiteres Problem ist der Schritt vom Entwurf zum Programm. Man kann das Programmskelett natürlich von der UML generieren. Nett, aber nicht wirklich nützlich. Von Hand geht das auch sehr schnell.

Das eigentliche Problem ist meiner Erfahrung nach, Entwurf und Programm synchron zu halten, d.h. die Wände die man bei der Ausführung einzieht auch in den Plan einzuzeichnen. UML bringt hier nichts: es ist ein Zusatzaufwand und unter Termindruck ist die Pflege der Dokumentation das erste was auf der Strecke bleibt. Anstelle von UML hätte ich gerne eine prägnante textbasierte Notation, die den kontinuierlichen Prozess des Umbauens und Verbesserns unterstützt, angefangen von der ersten groben Skizze bis schließlich zum fertigen Programm.
Hilfreich wäre beispielsweise ein Tool, das die Notation der Entwurfsskizzen lesen und mit der Implementierung vergleicht. Ich wüsste dann immer ob die Beschreibung auch der Implementierung entspricht.

dh hat gesagt…

@Geron: Ich kann Ihren Kommentar vollkommen verstehen und nachvollziehen. Irgendwie zeigen sich die graphisch orientierte UML und textuelle Programmiersprachen nicht so kompatibel, wie man sich das wünschen würde.

Was ist eigentlich Design? Ein Design formuliert Constraints (Beschränkungen), die die Optionen in der Ausgestaltung einer Implementierung beschränken. Man kann Constraints implementierungsneutreal formulieren und in eine Zielumgebung übersetzen lassen. Dann kann man die Implementierung gegen die Vorgaben (= Constraints) des Designs checken lassen. Wenn man so vorgeht tritt deutlich zu Tage, wie schwer es ist, ein brauchbares und überprüfbares Design zu formulieren. Das ist eine der wenigen Möglichkeiten, quantifizierbares Software Engineering zu betreiben! Es hilft, Design und Implementierung beständig synchron zu halten.