Sonntag, 27. Juli 2008

Gelegenheit macht Generierung

Ich muß bekennen, Routineentwicklungsaufgaben finde ich langweilig und meist nicht wert, dafür aufzustehen. Aber für langweilige Aufgaben sind doch die Maschinen erfunden worden. Also warum nicht den Computer die Arbeit machen lassen. Das ist der Grund, warum ich zum überzeugten Metaprogrammierer geworden bin.

Aber Vorsicht: Metaprogrammierung macht süchtig. Schon die Jungs in der Zeit von Flower power haben nicht nur LSD eingeworfen. Einige waren auch - wie man hört - süchtig nach M4. Dabei handelt es sich um einen vielseitigen Makroprozessor, der auch zur Codegenerierung verwendet wird. Leute, die mal sendmail konfiguriert haben, wissen, von welchem Werkzeug ich schreibe.

Die Kanonen: MDA und DSLs

Das ist zunächst MDA. MDA steht für "Model Driven Architecture", wobei mir niemand bekannt ist, der erklären kann, wie es zu dem "A" dabei gekommen ist. Die Architektur steckt im Modell und was getrieben werden soll ist die Generierung eines fertigen Systems. Der MDA-Ansatz wird von der OMG vorangetrieben, die uns CORBA und UML gebracht hat. Die Idee dabei ist, ein Modell basierend auf dem UML-Metamodell zu erstellen und daraus über eine oder mehrere Transformationen am Ende ausführbaren Code zu bekommen. Klingt kompliziert, ist kompliziert, aber es gibt einige vielversprechende Realisierungsansätze z.B. im Eclipse Modeling Project.

Der andere Ansatz sind DSLs. Hier baut man sich eine Grammatik auf für eine Sprache, die es möglichst einfach erlaubt, die für das konkrete Problem relevante Information zu erfassen. Dann kann man z.B. mit einem Parser Generator wie Antlr die Information in eine geeignete interne Darstellung einlesen, die es erlaubt mit einer Templateengine daraus Code zu erzeugen.

Besonders vorteilhaft sind diese Ansätze, wenn man unterschiedlichen Code aus derselben Information erzeugt. So kann man aus derselben Information die DDL für die Datenbankobjekte, die Zugriffsschicht für die Datenbank und die XSD für die XML-Darstellung erzeugen.

Die Spatzen

Oft ist es überhaupt nicht notwendig, sich über eine Sprache Gedanken zu machen, in der man die Information darstellen will. Vielmehr finden sich immer wieder strukturierte Dokumente, wie z.B. Excel-Tabellen, in der Information abgelegt ist, die man brauchen kann, um die Programme zu schreiben. Diese Dokumente sind viel zu schade um nur abgeschrieben zu werden. Notfalls müssen sie ein wenig nacheditiert werden. Danach geht es ans parsen.

Parsen

Dazu reicht in einfachen Fällen, wie csv-Dateien oft schon die split-Funktion, die in vielen Programmiersprachen zu finden ist. Bei Textdateien braucht man meist reguläre Ausdrücke. Manchmal hat man es auch mit XML-Dateien zu tun. Da tut es ein Sax-Parser.

Die Daten, die beim Parsen anfallen, müssen jetzt in geeignete Datenstrukturen untergebracht werden. Man kann da, wenn man gelehrt klingen will, von einem Meta-Modell sprechen. Meist braucht man aber nur wenige Listen und Dictionaries (Hashtable oder wie das Ding auch immer genannt wird), manchmal auch verschachtelt.

Transformieren

Gelegentlich wird nach dem ersten Einlesen noch eine einfache Transformation gebraucht, z.B. wenn Beziehungen zwischen den Elementen hergestellt werden müssen, wozu man die vollständig gefüllten Dictionaries braucht.

Generieren

Jetzt kann Code generiert werden. Es geht hier nicht um ganze Systeme, wie z.B. bei dem MDA-Ansatz. Vielmehr freuen wir uns schon über Codefragmente. Schön ist es, wenn es ein abgeschlossenes Dokument ergibt. Es ist aber auch nicht schlimm, wenn das Ergebnis noch nicht 100%ig ist. Wenn hunderte Zeilen Code generiert werden, dann aber 5 Zeilen manuell hinzugefügt werden, ist immer noch viel gewonnen.

Der so generierte Code kann auch manuell weiterverarbeitet werden. Dann kann der Codegenerator nach einmaliger Benutzung erst einmal eingemottet werden. Der Code ist aber sicher hilfreich als Vorlage für den nächsten Fall. Wenn sich die zugrundeliegenden Dokumente häufig ändern, lohnt es sich, darauf zu achten, daß man für die Einarbeitung der Änderungen die Codegenerierung nutzen kann.

Die Werkzeuge

Codegeneratoren kann man mit allen möglichen Werkzeugen bauen. Wenn die Daten in einer Exceltabelle vorliegen, benutze ich auch schon mal einen Excelausdruck und kopiere die resultierende Spalte. Wenn die Daten in einer Datenbank liegen, läßt sich Code auch mit SQL erzeugen.

Meist benutze ich eine Skriptingsprache. In früheren Jahren war das Perl. Heute benutze ich Ruby oder Python. Pythonprogramme sind meist lesbarer und haben die bessere Unterstützung für die Verarbeitung von XML. Ruby hingegen hat seine Stärken u.a. bei der Codegenerierung. Man kann leicht sehr mächtige Templates mit dem #{}-Ausdruck schreiben, der es erlaubt Codefragmente in das Template einzubetten. Damit sind auch rekursive Templates kein Problem.

Das Ergebnis

Mit recht wenig Aufwand kann man Code aus allen möglichen Dokumenten generieren. Abschreibefehler entfallen dabei. Der Code ist völlig konsistent. Dadurch fallen Feher schnell auf und entdeckte Fehler müssen nur an einer Stelle korrigiert werden.

Natürlich braucht es etwas Übung, aber es lohnt sich. Man bekommt nicht nur schneller bessere Ergebnisse. Die Arbeit macht auch mehr Spaß.

Sonntag, 20. Juli 2008

Grenzen der Abgrenzung

Eine Anwendung besteht oft aus einer Vielzahl von Systemen, die zusammenarbeiten müssen. Da gibt es z.B. eine Webanwendung, die Bestellungen entgegennimmt. Diese Information wird an ein Ordermanagementsystem übergeben, das wiederum die Information an ein ERP-System, ein Logistkiksystem und ein Kundenmanagementsystem weiterleitet. In komplexen Fällen kommen leicht ein Dutzend beteiligter Systeme zusammen.

Wenn sich Anforderungen ändern, hat das gewöhnlich Auswirkungen auf mehrere Systeme. Gewöhnlich werden die einzelnen Systeme unabhängig voneinander von verschiedenen Teams entwickelt und anschließend integriert. Jedes Team ist natürlich daran interessiert, seine Umgebung möglichst stabil zu halten, da nur so die Termine zu halten sind. Wie kann man sich möglichst gut mit Änderungen aus den umgebenden Systemen umgehen?

Eine umfassende Darstellung verschiedener Szenarien findet sich in dem Buch von Eric Evans "Domain Driven Design". Ich werde dies hier nicht wiederholen, sondern mich auf wenige Aspekte beschränken.

Politik

Der Ausmaß von Änderungen für ein System ist oft Verhandlungssache. Manchmal kann die Notwendigkeit in Frage gestellt werden. Oft geht es aber um verschiedene Lösungsmöglichkeiten, die sich dadurch unterscheiden, wie sehr die einzelnen Systeme betroffen sind. Das endet dann in einer Pokerpartie zwischen den Projektleitern. Die Karten werden dadurch bestimmt, wer mehr zu verlieren hat, wenn es bei der Integration zu Problemen kommt. Der Rest ist Bluff.

Technische Abgrenzung

Wie die meisten technischen Themen - mit Ausnahme des Baues eines Perpetuum Mobiles, d.h. einer risikofreien Energieversorgung - ist hier die Lösung verhältnismäßig einfach. Hier gibt es z.B. die Möglichkeit nach innen eine einheitliche technische Schnittstelle zu haben und nach außen hin flexibel zu sein, indem man einen Enterprise Service Bus einsetzt. Es gibt eine großes Angebot von Systemen, die gewöhnlich mit einer Vielzahl von fertigen Adaptern kommen.

Auch wenn man nicht ein solches Produkt einsetzt, so empfiehlt es sich immer, die technische Schnittstelle von der fachlichen zu entkoppeln. Man kann dann ein System so modular aufbauen, daß verschiedene technische Schnittstellen (z.B. SOAP über HTTP gegen Queueing System gegen Tuxedo ...) und verschiedene fachliche Schnittstellen einfach durch Konfiguration zusammengebracht werden können. Dies führt zu einer hohen Wiederverwendbarkeit der technischen Schnittstelle.

Unterschiedliche Sichten

Verschiedene Systeme benötigen unterschiedliche Sichten auf die Daten. So sind die Bedürfnisse an die Daten für ein Logistiksystem offensichtlich andere als für das System, daß die Rechnung stellt.

In vielen Fällen gibt es eine Menge von Daten, die z.B. im Ordermanagementsystem zusammengebracht wird, von der die Daten, die die übrigen Systeme benötigen. in Form von verschiedenen Untermengen abgeleitet werden können. Manchmal sind kompliziertere Abbildungen notwendig. Solange es aber möglich eine Abbildung zu finden, ist bewiesen, daß die Sichten der einzelnen System kompatibel sind.

Man kann sich die Arbeit vereinfachen, falls man genügend Einfluß auf die Entwicklung der einzelnen Systeme hat, indem man ein übergeordnetes Modell bildet. Jedes System beschränkt sich dann darauf, ausschließend Untermengen dieses übergeordneten Modells zu verwenden und bestenfalls einfache technische Transformationen durchzuführen. Dies bedeutet, daß dieses Modell dann die zentrale Instanz ist. Wenn Änderungen in der Gesamtanwendung erforderlich sind, müssen sie hier eingepflegt werden und jedes System muß entsprechend angepaßt werden, wenn es die entsprechenden Elemente übernommen hat.

Antikorruptionsschicht

Wenn es nicht möglich ist, alle Systeme an ein gemeinsames Modell anzupassen, z.B. weil ein Altsystem dabei ist, so kann man zumindestens verhindern, daß sich Konzepte aus dem einen System in das andere einschleichen. Das entsprechende Muster bezeichnet Evans als "Antikorruptionsschicht". Was man damit nicht verhindern kann, ist, daß fachliche Anforderungen der so abgeschotteten Systems erfüllt werden müssen. Das bedeutet dann, daß man nicht nur sein System anpassen muß, sondern auch die Antikorruptionsschicht.

Außerdem muß man bedenken, daß eine zusätzliche Schicht auch ein zusätzlicher Ort von Fehlern ist. Wenn man eine schlecht konstruierte, instabile Antikorruptionsschicht hat, kann es sein, daß sich die Probleme dadurch vergrößern, da das System jetzt nicht nur geändert werden muß, wenn von außen neue Anforderungen kommen, sondern auch dann, wenn Probleme in der Zwischenschicht entdeckt werden.

Während die Antikorruptionsschicht häufig das Leben deutlich vereinfacht, ist für eine gute Umsetzung auch Aufwand notwendig. Es sollte also überlegt werden, ob die Probleme, die damit gelöst werden können, so groß sind, daß sie das Risiko und den Aufwand wert sind.

Sonntag, 13. Juli 2008

Vorsicht Meeting

Heute geht es nicht um interessante Technik, sondern um das, womit die meisten sich mindestens so häufig beschäftigen, wie die "eigentliche Arbeit": Meetings. Eigentlich ist Kommunikation doch der wichtigste Teil eines Softwareprojekts, der den Unterschied zwischen Erfolg und Mißerfolg eines Projekts ausmacht. Warum haben Meetings bei so vielen so einen schlechten Ruf?

Typen von Meetings

Ohne Anspruch auf Vollständigkeit hier einige Typen von Meetings:

  • Routinemäßiges Teammeeting: Kann nützlich sein, wenn es knapp und mit strengen Regeln geführt wird (Dialy Scrum Meeting). Negative Faktoren sind hier:

    • schwache Moderation, Diskussionen kommen auf, die anderswo geführt werden sollten

    • es werden immer wieder die gleichen Probleme angesprochen, aber niemand unternimmt etwas zur Lösung

    • die Teammitglieder haben das Gefühl, daß ihre Arbeit beurteilt wird. Probleme werden unter den Tisch gekehrt

    • es wird versucht, hier Beschlüsse zu fassen. Beschlüsse brauchen Vorbereitung, damit keiner sich überfahren fühlt

  • Strategiemeetings: eine Strategie soll verabschiedet werden. Negative Faktoren:

    • die Beschlußvorlage wird zu spät verteilt, so daß sich die Teilnehmer nicht vorbereiten können

    • es wird versäumt zu Beginn des Meetings Einigung über Ziel und Notwendigkeit des Themas zu erzielen

    • unterschiedliche Meinung werden zuerst unterdrückt und kommen dann in endlosen Diskussionen zum Vorschein. Besser: Raum für die Präsentation von abweichenden Vorschlägen geben

    • Teilnehmer mit abweichenden Ansichten erst im Meeting einfangen zu wollen, führt selten zum gewünschten Erfolg. Bestenfalls bekommt man sein Meeting durch, aber die Fragen werden immer wieder hochkommen. In der Politik wird so etwas schon im Vorfeld zu klären versucht.

  • Meeting mit Kunden. Negative Faktoren:

    • problematische Themen werden vorher nicht offen zwischen den Teilnehmern auf Anbieterseite besprochen

    • die Rollen sind nicht klar. Wer hat welche Kompetenzen für Zusagen? Wer darf nur Fragen stellen? Wer nur beobachten?

    • der Erfolg oder Mißerfolg wird gegenüber dem Kunden einzelnen Teammitgliedern zugewiesen. Schuldfragen interessieren nur intern.

Kommunikationsfallen

Hier typische Kommuniktationsfallen, die häufig in Meetings beobachtet werden können:

Ein Vorlage wird vorgestellt. Wenn Gegenvorschläge kommen, reagiert der Vorstellende gereizt. Warum kann das nicht funktionieren? Wenn die Vorlage nicht zur Diskussion stehen soll, braucht es kein Meeting. Wenn der betreffende die notwendige Kompetenz hat, verkündet er sie einfach und stiehlt den Teilnehmern nicht die Zeit. Wenn er zum Meeting einlädt, signalisiert er, daß die Vorlage ein erster Entwurf ist, die im Meeting weiterentwickelt werden soll.

Die Diskussion dreht sich im Kreis über immer neue, meist kleinteilige Probleme. Wird hier etwa über eine Lösung diskutiert, bevor das Problem genau definiert wurde?

Einzelne Teilnehmer halten lange Vorträge und beschweren sich bei Unterbrechungen. Während es als unhöflich gilt, Leute zu unterbrechen, so ist es zumindest wenig souverän, Nachfragen abzuwürgen. Warum sollte jemand einem langen Vortrag zuhören, wenn er schon nicht von der Prämisse überzeugt wurde. Wenn dies eine Taktik ist, die Oberhand durch Redezeit zu gewinnen, wird es meist eher das Meeting sprengen als zum Erfolg zu führen.

Machtkämpfe und pragmatischen Suche nach Lösungen für Probleme vertragen sich selten.

Kommunikationstechniken

Viel Firmen geben erheblich mehr Geld dafür aus, ihre Mitarbeiter in Kommunkationstechniken zu schulen wie in konkreten technischen Fertigkeiten. Ich bin kein großer Freund von diesen Kursen, aber die dort gelehrten Methoden fuhren erstaunlicherweise häufig zu Erfolgen.

Warum werden die Techniken so selten angewandt? Ein Kommunikationskoffer mit den entsprechenden Spielzeugen (Karten, Stifte, Heftmittel etc.) ist meist verfügbar. Aber so ein Meeting erfordert von dem Einladenden mehr Vor- und insbesondere Nacharbeit. Außerdem taugen sie nicht zu Machtspielchen und die Ergebnisse sind nachprüfbarer.

Teilnehmerliste

Wichtig ist es, die richtigen Leute zu einem Meeting einzuladen. Es ist wenig hilfreich, in einem Meeting etwas zu beschließen, was von Dritten erwartet wird, die möglichst davon nicht unterrichtet werden.

Es ist aber auch nicht sinnvoll, Leute einzuladen, an deren Meinung man nicht interessiert ist, es sei denn, man will diese Leute loswerden.

Sonntag, 6. Juli 2008

Flexible Programmiersprachen

Während sich einige Programmiersprachen wie z.B. SQL auf spezielle Gebiete beschränken, sind die meisten Programmiersprachen, die wir täglich benutzen prinzipiell für alle Probleme einsetzbar. Leute, die ihre theoretischen Kenntnisse deutlich machen wollen, sprechen hier von Turing-vollständig. Dieser Ausdruck basiert auf den Arbeiten des englischen Mathematikers Alan Turing, der schon vor dem Bau der ersten Computer theoretisch gezeigt hat, wie man mit einem Mindestsatz von Operationen alle vorstellbaren mathematischen Probleme lösen kann. Aber nicht jede Programmiersprache macht es dem Benutzer gleich leicht, ein Problem zu formulieren. Einige Programmiersprachen sind optimiert, um bestimmte Probleme zu lösen, während andere Dinge nur schwer umzusetzen sind. So kann man mit FORTRAN relativ gut numerische Algorithmen implementieren. Eine Textverarbeitung oder Datenbank möchte ich aber eher nicht damit schreiben. Andere Programmiersprachen sind leicht zu lernen, wie z.B. BASIC, womit es aber schwer ist, größere Programme lauffähig und wartbar hinzubekommen.

Maschine gegen Logik

Bei der Entwicklung der Programmiersprachen gibt es zwei unterschiedliche Ansätze. Diese entsprechen auch den unterschiedlichen Abstammungen der Informatiklehrstühle der Universitäten. Bevor sich die Informatik als eigenständige Disziplin etabliert hatte, waren die späteren Informatiker entweder Elektroingenieure oder Mathematiker. Demnach gab es zwei unterschiedliche Herangehensweisen. Die einen hatten ihre Schaltungen und versuchten damit etwas sinnvolles anzufangen. Die anderen hatten ihre Logik, Lambdacalculus, Kategorientheorie etc. und versuchten diese Theorien irgendwie auf die Maschinen abzubilden. Das sieht man auch den Programmiersprachen bis heute an. Zum einen gibt es da die Sprachen wie C. C stammt in direkter Linie von der Maschinensprache ab. Ich bezeichne es gerne als prozessorarchitekturunabhängigen Assembler. Damit lassen sich recht einfach Programme schreiben, die sehr effizient mit der Maschne umgehen. Auf der anderen Seite gibt es Sprachen wie LISP und Smalltalk. Diese beruhen auf wenigen, aber mathematisch fundierten Grundprinzipien. Die Effizienz kann zwar auch von Programmierer beeinflußt werden, liegt aber zuerst einmal bei dem Implementierer der Compiler und der Laufzeitumgebung.

Kontrollstrukturen

Wichtige Elemente von Programmiersprachen, sind zum einen Funktionen und zum anderen Kontrollstrukturen. In der Regel wird eine Programmiersprache mit einer Bibliothek von Funktionen ausgeliefert. Der Benutzer kann aber - und muß, wenn er etwas sinnvolles bewerkstelligen will - aber eigene Funktionen hinzufügen. Bei den Kontrollstrukturen ist das aber bei Sprachen wie C oder auch Java nicht möglich. Wenn es noch keine foreach-Schleife gibt, um über eine Sammlung von Objekten zu iterieren, so muß man halt auf eine neue Version der Sprache warten, die dieses Kontrollstruktur hinzufügt.

Bei Smalltalk z.B. ist das aber anders. Diese Sprache ist das extremste Beispiel für das "alles ist ein Objekt"-Prinzip. Kontrollstrukturen basieren hier auch auf Objekten. Eine If-Else-Struktur basiert auf Objekten vom Typ Boolean. Dieser Typ besitzt Methoden IfTrue: oder IfElse:. Diesen Methoden werden Codeblöcke als Argumente mitgegeben, die abhängig vondem Wert des Objekts ausgeführt werden. Bei den Codeblöcken handelt es sich um dasselbe, was wir letzte Woche als Closure kennengelernt haben.

Warum geht ähnliches nicht mit Java? Wenn wir versuchen, ein Commandobjekt zu bauen, das ein Attribut des Typs bool besitzt und zwei mit der Signatur eines Commandobjekts, so kann man eine Verknüpfung erreichen, die in eine If-Else-Verzweigung simuliert. Dies wird gelegentlich auch so implementiert, wenn man den Programmablauf zur Laufzeit zusammensetzen muß.

Das Problem hier ist, daß das alles extrem umständlich wird, da man den Objekten alles explizit mitgeben muß, was sie benötigen. In einem normalen If-Else-Konstrukt sind aber alle Elemente des Kontextes, in dem das Konstrukt exisitert verfügbar, ohne das sie extra hinkopiert werden müßten.

Wenn ich Closures in meiner Programmiersprache unterstütze, so kann die Sprache jederzeit um neue Kontrollstrukturen erweitert werden. Recht verbreitet sind hier individuelle Schleifen, die über eine Sammlung von Objekten iterieren und hierbei die innere Struktur der Sammlung kennen. Siehe z.B. folgenden Ruby-Code:

a = [ "a", "b", "c", "d" ]
a.collect {|x| x + "!" } #=> ["a!", "b!", "c!", "d!"]

Flexibilität für alle?

Wenn man die Sprache so leicht erweitern kann, so kann man sehr kompakten und übersichtlichen Code schreiben. Wie alles ist aber auch dies nicht umsonst zu haben. Leute, die von der C-Denkschule kommen, müssen zuerst umlernen. Außerdem gilt hier noch mehr als in anderen Fällen, daß es immer schwerer wird, neue Mitarbeiter für ein bestehendes Projekt einzuarbeiten, da zu Beginn die allgemeinen Kenntnisse der Programmiersprache nur eine Grundlage geben, aber bei weitem nicht ausreichen, den Programmcode lesen zu können.
Insbesondere LISP-Programmierer sind berüchtigt dafür, daß sie für jedes Projekt praktisch eine eigene Programmiersprache entwickeln, die auf Lisp aufbaut und die typische Klammerstruktur besitzt, aber über ganz eigene Kontrollstrukturen verfügt.
Der Vorteil aber ist, daß, nachdem die Grundlagen geschaffen sind, die Arbeit deutlich vereinfacht wird und die Anzahl von Fehlern dadurch zurückgeht.
Der Trend in den modernen Programmiersprachen geht eindeutig in diese Richtung. Schon C# 3.0, Ruby und Python bieten viele Möglichkeiten, ganz zu schweigen von aufkommenden Sprachen wie Scala und F#.