Code-Generierung

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

"Gute Programmierer schreiben guten Code. Großartige Programmierer schreiben Programme, um ihn zu generieren."
Autor unbekannt

Einleitung

Automatisierung ist der heilige Gral der Software-Entwicklung. Im Grunde genommen wird ein Großteil der Software-Entwicklung vom Konzept der Code-Generierung aus einer Spezifikation auf höherer Ebene beeinflusst. Ist das schließlich nicht genau das, was alle Assembler und Compiler machen? Bei einer anderen Form von Code-Generierung ist die Zielsprache kein ausführbarer Machinencode, sondern eine Hochsprache wie Java oder C++. Aber warum sollten Sie Code auf diese Weise generieren wollen, und was hat das mit XML zu tun?

Was Sie beim Schreiben eines Programms im Wesentlichen tun, ist Wissen vielfältiger Art in einer sehr speziellen Syntax zu kodieren, die für eine bestimmte Entwicklungsphase des Lebenszyklus optimiert ist. Die bei der Kodierung geleistete Arbeit lässt sich deshalb nur schwer bei anderen wichtigen Entwicklungsaufgaben wirksam einsetzen, weil der Code in einer Programmiersprache schwer zu parsen ist und vieles an interessanter Information in spontanen Kommentaren versteckt ist. Die Darstellung von Anwendungswissen in XML-Form bietet die Aussicht, eine viel größere Wirkung zu erzielen. Aus XML-Spezifikationen können Sie Anwendungscode, Testprogramme, Dokumentation und womöglich sogar Testdaten generieren. Das soll nicht heißen, dass XML Ihnen das frei Haus liefert. Wie bei allen Aufgaben in der Software-Entwicklung ist eine beträchtliche Menge an Planung und an Aufbau von Infrastruktur nötig, um in den Genuss der Vorteile zu kommen.

Dieses Kapitel unterscheidet sich von den meisten anderen Kapiteln in diesem Buch dadurch, dass hier die meisten Beispiele aus Komponenten einer Lösung innerhalb eines bestimmten Anwendungskontextes bestehen. Diese besondere Struktur hat zweierlei Gründe.

Zum einen ist es unwahrscheinlich, dass Sie Informationen in XML kodieren werden, um Code zu generieren, nur weil XML cool ist. Meistens muss ein größeres Problem gelöst werden, damit XML besonders wirksam eingesetzt werden kann. Die Beispiele in diesem Abschnitt machen mehr Sinn, sobald sie im Zusammenhang mit einem größeren Problem gesehen werden.

Zum anderen ist das vorgestellte Problem typisch bei der Entwicklung von Anwendungen in großem Maßstab, d.h., für viele Leser kann das für sich allein genommen schon interessant sein. Aber selbst, wenn das für Sie nicht so sein sollte, verringert das Rahmenproblem nicht die Anwendbarkeit der Konzepte bei anderen Entwicklungsaufgaben.

Worin besteht dieses große Problem also?

Stellen Sie sich eine komplexe Client-Server-Anwendung vor. Komplex heißt, dass sie aus vielen verschiedenen Arten von Server- und Client-Prozessen besteht. Diese Prozesse kommunizieren über Nachrichten (engl. messages) miteinander, wobei eine nachrichtenbasierte Middleware (entweder Point-to-Point, Publish-Subscribe oder beides) verwendet wird. Hierbei sind IBM MQSeries, Microsoft Message Queuing (MSMQ), BEA Systems Tuxedo und TIBCO Rendezvous nur einige der vielen Produkte, die sich in diesem Bereich tummeln. Für dieses Beispiel ist es unwichtig, um welches konkrete Middleware-Produkt es sich handelt. Worauf es ankommt ist, dass die gesamte wichtige Arbeit, die das System verrichtet, durch den Empfang einer Nachricht und der darauf folgenden Antwort in Form von einer oder mehreren Nachrichten ausgelöst wird. (Es ist offensichtlich, dass auch die Benutzerein- und -ausgabe relevant ist. Allerdings können Sie sich den I/O auch in Form von Messages vorstellen. Diese Benutzer-I/O-Messages werden normalerweise über verschiedene Kanäle gesendet und empfangen, was aber nicht für Interprozess-Messages gilt.) Dabei darf die Nachricht aus XML (SOAP), nicht-XML-Text oder binären Daten bestehen. Das Kapitel Rezepte für vertikale XSLT-Anwendungen behandelt SOAP im Zusammenhang mit WSDL. In diesem Kapitel geht es vorwiegend um die Kommunikation von Server zu Server, bei der XML weniger oft verwendet wird.

Was bei solch komplexen Systemen besonders einschüchternd wirkt, ist der Umstand, dass man sie nicht einfach dadurch verstehen kann, dass man den Quellcode einer bestimmten Art von Prozess studiert. Stattdessen muss man damit anfangen, zuerst die Konversation, also die Protokolle zwischen diesen Prozessen, d.h. die sogenannten Interprozess-Nachrichtenprotokolle (engl. interprocess messaging protocols), zu verstehen. Dieses Kapitel geht noch einen Schritt weiter und behauptet, dass in einer ersten Näherung die Details zu jedem einzelnen Prozess irrelevant sind. Sie können einfach alle Prozesse als Black-Box betrachten und dann – anstatt die Hunderttausende von Codezeilen zu verstehen, aus denen das gesamte System besteht – damit anfangen, die kleine Menge an Nachrichten zu verstehen, die diese Prozesse miteinander austauschen.

Nun stellt sich also die Frage, wo man anfängt, um die Interprozess-Sprache einer komplexen Anwendung zu verstehen? Können Sie diese Information an einem einzigen Ort erhalten? Leider ist das meistens nicht der Fall. Meiner Meinung nach werden Sie nur selten eine aktuelle und vollständige Spezifikation des Nachrichtenprotokolls einer Anwendung finden. Normalerweise können Sie Teile eines Puzzles in verschiedenen Header-Dateien und andere Teile in Design-Dokumenten finden, die über den Lebenszyklus eines Systems hinweg entwickelt wurden, aber Sie werden selten eine einzige Quelle für eine derart wichtige Information finden. Und in vielen Fällen ist die einzige wirklich zuverlässige Methode, an diese Information heranzukommen, die, sie mittels Reverse-Engineering aus dem Quellcode der Anwendung selbst zu holen, also genau so, wie es eigentlich nicht sein sollte!

Nun gut, aber was hat dieses Problem mit XML, XSLT und insbesondere mit Code-Generierung zu tun? Die Lösung dieses Problems können Sie in Form eines Bedarfs an Dokumentation beschreiben, die den Nachrichtenaustausch einer Anwendung in allen Details beschreibt. Was für eine Art von Dokument könnte das sein? Vielleicht sollten die Entwickler ein MS Word-Dokument führen, das alle Nachrichten beschreibt, oder, noch besser, eine Nachrichten-Website, auf der man stöbern und suchen kann? Oder vielleicht sollte (und diese Antwort haben Sie sicher schon erraten) die Information als XML vorliegen? Vielleicht sollten Sie die Website aus diesem XML erzeugen? Und wenn Sie schon dabei sind: Vielleicht sollten Sie ein bisschen Code für die Anwendungen generieren, die diese Nachrichten verarbeiten? Tatsächlich ist das genau das, was Sie in diesem Kapitel tun werden. Den Satz an XML-Dateien bezeichne ich als Interprozess-Nachrichten-Repository (engl. interprocess message repository), und viele Rezepte in diesem Kapitel demonstrieren, wie man Code mit Hilfe dieses Repositorys generieren kann.

Bevor Sie zu den eigentlichen Rezepten kommen, erhalten Sie in diesem Kapitel noch eine Vorstellung vom Design des Repositorys in Gestalt seines Schemas. Zu diesem Zweck wird ein W3C XSD-Schema benutzt, das hier aber nur für all jene als intuitive grafische Ansicht angegeben wird, die mit XML-Schemata nicht ausreichend vertraut sind.

Die folgende Abbildung wurde mit Hilfe von XMLSpy 4.0 von Altova erzeugt. Die Icons mit drei Punkten (...) stellen eine geordnete Sequenz dar. Das Icon, das wie eine Mehrfachweiche aussieht, stellt eine Wahlmöglichkeit dar.

Grafische Darstellung eines XSD-Schemas für ein Repository

Abbildung: Grafische Darstellung eines XSD-Schemas für ein Repository.

Auch wenn dieses Schema ausreicht, um interessante Rezepte zur Code-Generierung zu illustrieren, so ist es vermutlich unzureichend für ein industrietaugliches Nachrichten-Repository. Ein Nachrichten-Repository kann zusätzliche Angaben speichern, wie sie in der folgenden Liste aufgeführt sind:

  • Symbolische Konstanten, die in Array- und String-Längen sowie bei Aufzählungswerten vorkommen
  • Informationen über komplexere Darstellungen von Daten, z.B. Unions und Typ-Namen-Aliase (C-typedefs)
  • Informationen über Nachrichtenprotokolle (komplexe Nachrichtensequenzen, die von einer Reihe von Prozessen ausgetauscht werden, um eine spezielle Funktionalität zu erreichen)
  • Historische Informationen wie Autoren, letzte Änderung durch, Änderungsdatum usw.
  • Informationen über Zustellung und Transport bezüglich der Namen von Publisher, Subscriber oder Schlangen (engl. queues)

Als Beispiel für solche Repository-Daten können Sie sich eine einfache Client-Server-Anwendung vorstellen, die Aufträge auf dem Aktienmarkt platziert. Das Repository für eine solche Anwendung könnte wie folgt aussehen:

<MessageRepository xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="C:\MyProjects\XSLT Cookbook\code gen\MessageRepository.xsd">
  <DataTypes>
    <Primitive>
      <Name>Real</Name>
      <Size>8</Size>
      <Category>real</Category>
    </Primitive>
    <Primitive>
      <Name>Integer</Name>
      <Size>4</Size>
      <Category>signed integer</Category>
    </Primitive>
    <Primitive>
      <Name>StkSymbol</Name>
      <Size>10</Size>
      <Category>string</Category>
    </Primitive>
    <Primitive>
      <Name>Message</Name>
      <Size>100</Size>
      <Category>string</Category>
    </Primitive>
    <Primitive>
      <Name>Shares</Name>
      <Size>4</Size>
      <Category>signed integer</Category>
    </Primitive>
    <Enumeration>
      <Name>BuyOrSell</Name>
      <Enumerators>
        <Enumerator>
          <Name>BUY</Name>
          <Value>0</Value>
        </Enumerator>
        <Enumerator>
          <Name>SELL</Name>
          <Value>1</Value>
        </Enumerator>
      </Enumerators>
    </Enumeration>
    <Enumeration>
      <Name>OrderType</Name>
      <Enumerators>
        <Enumerator>
          <Name>MARKET</Name>
          <Value>0</Value>
        </Enumerator>
        <Enumerator>
          <Name>LIMIT</Name>
          <Value>1</Value>
        </Enumerator>
      </Enumerators>
    </Enumeration>
    <Structure>
      <Name>TestData</Name>
      <Members>
        <Member>
          <Name>order</Name>
          <DataTypeName>AddStockOrderData</DataTypeName>
        </Member>
        <Member>
          <Name>cancel</Name>
          <DataTypeName>CancelStockOrderData</DataTypeName>
        </Member>
      </Members>
    </Structure>
    <Structure>
      <Name>AddStockOrderData</Name>
      <Documentation>Anfrage zur Buchung einer neuen Order.</Documentation>
      <Members>
        <Member>
          <Name>symbol</Name>
          <DataTypeName>StkSymbol</DataTypeName>
        </Member>
        <Member>
          <Name>quantity</Name>
          <DataTypeName>Shares</DataTypeName>
        </Member>
        <Member>
          <Name>side</Name>
          <DataTypeName>BuyOrSell</DataTypeName>
        </Member>
        <Member>
          <Name>type</Name>
          <DataTypeName>OrderType</DataTypeName>
        </Member>
        <Member>
          <Name>price</Name>
          <DataTypeName>Real</DataTypeName>
        </Member>
      </Members>
    </Structure>
    <Structure>
      <Name>AddStockOrderAckData</Name>
      <Documentation>Eine positive Bestätigung einer erfolgreich aufgenommenen Order.</Documentation>
      <Members>
        <Member>
          <Name>orderId</Name>
          <DataTypeName>Integer</DataTypeName>
        </Member>
      </Members>
    </Structure>
    <Structure>
      <Name>AddStockOrderNackData</Name>
      <Documentation>Eine negative Bestätigung einer nicht aufgenommenen Order.</Documentation>
      <Members>
        <Member>
          <Name>reason</Name>
          <DataTypeName>Message</DataTypeName>
        </Member>
      </Members>
    </Structure>
    <Structure>
      <Name>CancelStockOrderData</Name>
      <Documentation>Eine Anfrage zum Abbruch einer ganzen oder eines Teils einer Order.</Documentation>
      <Members>
        <Member>
          <Name>orderId</Name>
          <DataTypeName>Integer</DataTypeName>
        </Member>
        <Member>
          <Name>quantity</Name>
          <DataTypeName>Shares</DataTypeName>
        </Member>
      </Members>
    </Structure>
    <Structure>
      <Name>CancelStockOrderAckData</Name>
      <Documentation>Eine positive Bestätigung eines erfolgreichen Order-Abbruchs.</Documentation>
      <Members>
        <Member>
          <Name>orderId</Name>
          <DataTypeName>Integer</DataTypeName>
        </Member>
        <Member>
          <Name>quantityRemaining</Name>
          <DataTypeName>Shares</DataTypeName>
        </Member>
      </Members>
    </Structure>
    <Structure>
      <Name>CancelStockOrderNackData</Name>
      <Documentation>Eine negative Bestätigung eines fehlgeschlagenen Order-Abbruchs.</Documentation>
      <Members>
        <Member>
          <Name>orderId</Name>
          <DataTypeName>Integer</DataTypeName>
        </Member>
        <Member>
          <Name>reason</Name>
          <DataTypeName>Message</DataTypeName>
        </Member>
      </Members>
    </Structure>
  </DataTypes>
  <Messages>
    <Message>
      <Name>ADD_STOCK_ORDER</Name>
      <MsgId>1</MsgId>
      <DataTypeName>AddStockOrderData</DataTypeName>
      <Senders>
        <ProcessRef>StockClient</ProcessRef>
      </Senders>
      <Receivers>
        <ProcessRef>StockServer</ProcessRef>
      </Receivers>
    </Message>
    <Message>
      <Name>ADD_STOCK_ORDER_ACK</Name>
      <MsgId>2</MsgId>
      <DataTypeName>AddStockOrderAckData</DataTypeName>
      <Senders>
        <ProcessRef>StockServer</ProcessRef>
      </Senders>
      <Receivers>
        <ProcessRef>StockClient</ProcessRef>
      </Receivers>
    </Message>
    <Message>
      <Name>ADD_STOCK_ORDER_NACK</Name>
      <MsgId>3</MsgId>
      <DataTypeName>AddStockOrderNackData</DataTypeName>
      <Senders>
        <ProcessRef>StockServer</ProcessRef>
      </Senders>
      <Receivers>
        <ProcessRef>StockClient</ProcessRef>
      </Receivers>
    </Message>
    <Message>
      <Name>CANCEL_STOCK_ORDER</Name>
      <MsgId>4</MsgId>
      <DataTypeName>CancelStockOrderData</DataTypeName>
      <Senders>
        <ProcessRef>StockClient</ProcessRef>
      </Senders>
      <Receivers>
        <ProcessRef>StockServer</ProcessRef>
      </Receivers>
    </Message>
    <Message>
      <Name>CANCEL_STOCK_ORDER_ACK</Name>
      <MsgId>5</MsgId>
      <DataTypeName>CancelStockOrderAckData</DataTypeName>
      <Senders>
        <ProcessRef>StockServer</ProcessRef>
      </Senders>
      <Receivers>
        <ProcessRef>StockClient</ProcessRef>
      </Receivers>
    </Message>
    <Message>
      <Name>CANCEL_STOCK_ORDER_NACK</Name>
      <MsgId>6</MsgId>
      <DataTypeName>CancelStockOrderNackData</DataTypeName>
      <Senders>
        <ProcessRef>StockServer</ProcessRef>
      </Senders>
      <Receivers>
        <ProcessRef>StockClient</ProcessRef>
      </Receivers>
    </Message>
    <Message>
      <Name>TEST</Name>
      <MsgId>7</MsgId>
      <DataTypeName>TestData</DataTypeName>
      <Senders>
        <ProcessRef>StockServer</ProcessRef>
      </Senders>
      <Receivers>
        <ProcessRef>StockClient</ProcessRef>
      </Receivers>
    </Message>
  </Messages>
  <Processes>
    <Process>
      <Name>StockClient</Name>
    </Process>
    <Process>
      <Name>StockServer</Name>
    </Process>
  </Processes>
</MessageRepository>

Dieses Repository beschreibt die Nachrichten, die zwischen einem Client (namens StockClient) und einem Server (namens StockServer) ausgetauscht werden, während die Anwendung verschiedene Aufgaben erledigt. Leser, die sich mit WSDL auskennen, werden eine Ähnlichkeit dazu beobachten können. Allerdings ist WSDL speziell für die Spezifikation von Webdiensten gedacht und wird meistens im Zusammenhang mit SOAP-Diensten benutzt, obwohl die WSDL-Spezifikation rein technisch protokollunabhängig ist.

Die letzten beiden Beispiele dieses Kapitels haben mit dem Nachrichtenproblem nichts zu tun. Das erste konzentriert sich auf die Generierung von C++-Code aus UML-Modellen (Unified Modeling Language), die aus einem UML-Modellierungswerkzeug über XMI (XML Metadata Interchange) exportiert wurden. Das zweite behandelt den Einsatz von XSLT bei der Generierung von XSLT.

Bevor ich mit den eigentlichen Beispielen fortfahre, möchte ich mich für die Wahl von C++ in den meisten Beispielen entschuldigen. Ich habe sie nur deswegen getroffen, weil ich diese Sprache am besten kenne. Außerdem habe ich tatsächlich Generatoren dafür geschrieben. Aber das konzeptuelle Framework ist auf andere Sprachen übertragbar, auch wenn das für den XSLT-Code im gleichen Wortlaut nicht gilt. (Fast möchte ich hinzufügen – und nur halb im Scherz –, dass C++ eine derart horrend komplexe Sprache ist, dass ihre Benutzer die größte Motivation haben dürften, solchen Code zu generieren statt zu schreiben!)

XSLT 2.0

Wegen der speziellen Natur dieser Rezepte stelle ich hier keine Lösungen für XSLT 1.0 und 2.0 vor, wie es in vielen vorherigen Kapiteln der Fall war! Jene Leser, die Interesse am Einsatz von XSLT 2.0 bei der Code-Generierung haben, sollten Das Potenzial von XSLT 2.0 ausnutzen lesen. Viele der dort vorgestellten 2.0-spezifischen Eigenschaften und Techniken können bei der Code-Generierung gut angewendet werden. Hier ist eine Liste allgemeiner Ideen dazu:

  • Nutzen Sie das neue separator-Attribut in xsl:value-of.

    Beim Generieren von Code werden häufig Sequenzen von getrennten Elementen (z.B. durch Kommata getrennte Listen) erzeugt. In diesen Fällen kann die Möglichkeit von xsl:value-of, bei der Ausgabe einer Sequenz automatisch ein Trennzeichen einzufügen, zu einfacheren Generatoren führen.

  • Machen Sie Gebrauch von den mächtigen Sequenzkonstrukten in XPath 2.0.

    Eines der größten Ärgernisse beim Schreiben von Generatoren in XSLT 1.0 ist deren Wortfülle. Generatoren neigen dazu, eine Vielzahl von Schleifen- und Bedingungskonstrukten zu verwenden, und xsl:for-each und xsl:choose sind keine Vorbilder in Sachen Kürze. Viele Generierungsaufgaben lassen sich aber von XSLT nach XPath 2.0 verlagern. Dabei muss man oft sowohl XSLT als auch XPath benutzen, um zuerst die XML-Eingabe in ein oder mehrere Sequenzen zu konvertieren und diese dann mit XPath auf Code abzubilden:

<!-- Eine Funktion zur Generierung einer Funktionsdeklaration im C-Stil, die vorwiegend XPath 2.0 verwendet. -->
<xsl:function name="ckbk:function-decl" as="xs:string*">
  <xsl:param name="name" as="xs:string"/>
  <xsl:param name="returnType" as="xs:string"/>
  <xsl:param name="argNames" as="xs:string*"/>
  <xsl:param name="argTypesPre" as="xs:string*"/>
  <xsl:param name="argTypesPost" as="xs:string*"/>
  <xsl:variable name="c" select="count($argNames)"/>
  <xsl:sequence select="$returnType, $name, '(', for $i in 1 to $c return ($argTypesPre[$i], $argNames[$i], $argTypesPost[$i], if ($i ne $c) then ',' else ''),');'"/>
</xsl:function>
  • Modulares, wiederverwendbares XSLT kann man mit 2.0 einfacher gestalten.

    Falls Sie scharf darauf sind, Generatoren zu erzeugen, die über mehrere Zielsprachen hinweg funktionieren, dann lohnt es sich, die Techniken zu lernen, die in den Rezepten Modularisierung und Modi und Objektorientierte Wiederverwendung und Entwurfsmuster emulieren vorgestellt werden.

  • Die Ausgabe in mehrere Dokumente ist Standard in 2.0.

    Autoren von Generatoren für Sprachen wie C und C++, die separate Header- und Quelldateien benutzen, werden mit Sicherheit das nunmehr standardisierte xsl:result-document zu schätzen wissen, mit dem man Stylesheets schreiben kann, die mehrere Ausgabedokumente erzeugen.

  

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2006 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XSLT Kochbuch" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O'Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de