Generative Programmierung

Die Generative Programmierung ist ein Programmierparadigma bei der methodischen Softwareentwicklung. Charakteristisch für die generative Programmierung ist die automatische Erzeugung von Programmcode durch einen Generator.

Funktionsweise eines Programmgenerators

Ein Programmgenerator kann am besten wie ein gewöhnliches Programm nach dem EVA-Prinzip verstanden werden. Aufgrund bestimmter Inputparameter erzeugt der Programmgenerator einen bestimmten Output, das sogenannte Generat. Allerdings ist der Output eines generativen Programms wiederum ein Programmcode, nämlich der Code, welcher für die konkretisierte Situation ausgeführt werden soll.

Grundlage für automatisch erzeugten Code ist die Abstraktion häufig vorkommender Programmkonstrukte in formalen Modellen. Die Programmierung unterteilt sich in drei Phasen:

  1. Der Programmierung eines bestimmten Programmgenerators
  2. Der Parametrierung oder Ergänzung und Konfiguration des formalen Modells auf eine spezifische Modellausprägung
  3. Dem Aufruf des Programmgenerators mit den spezifischen Inputparametern, welcher dann das spezifische Zielprogramm erstellt

Ein Programmgenerator bzw. Codegenerator ist demnach auf eine generische Anwendungs- und Programmklasse spezialisiert. Er bezieht sich auf ein bestimmtes, zugrundeliegendes generisches Programmmodell, aus welchem er nach konkretisierender Parametrisierung den Zielcode erzeugt. Dies kann ein Quellcode, Zwischencode oder Binärcode sein.

Während ein normales, funktional programmiertes Programm die Varianz der Aufgabenstellungen ausschließlich mit Datenvariablen abdeckt, arbeitet die generative Programmierung auch mit variabilisiertem Programmcode, der erst im Hinblick auf den Zielcode eindeutig ausgeprägt wird.

Dieses Vorgehen eignet sich besonders für Problemlösungen, die in entsprechend großer Zahl von Variationen in der Praxis vorkommen, da für die Erstellung des Modells und des Generators ein nicht geringer Aufwand eingeplant werden muss. Dieser Aufwand kann sich aufgrund höherer Qualität des Programmcodes und kürzerer Entwicklungszeit amortisieren. Häufig werden die Zielprogramme nur temporär zum einmaligen Gebrauch generiert und danach wieder gelöscht. Dadurch kann der zu einem bestimmten Zeitpunkt persistent vorhandene Programmcode, z. B. gemessen anhand Anzahl Codezeilen, ggf. um einige Zehnerpotenzen reduziert werden.

Generative Programmierung ist überall dort sinnvoll, wo bestimmte Codeteile analog variablen Textbausteinen zu einer Vielzahl von Zielprogrammen zusammengefügt werden sollen. Die generative Programmierung erlaubt im Weiteren auch die Erstellung von Zielprogrammen, deren Zielparameter zum Zeitpunkt der Codierung des Programmgenerators noch gar nicht bekannt sind.

Persistenter Zielcode

Der von einem Programmcode erzeugte Zielcode kann

  • einmal erzeugt, persistent gespeichert und dann permanent genutzt werden oder
  • nach Bedarf dynamisch erzeugt und ausgeführt und danach wieder gelöscht werden.

Wenn ein Zielcode einmal erzeugt und dann persistent gehalten wird, kann die Programmgenerierung und die Ausführung des Zielprogramms zeitlich entkoppelt stattfinden. Die Programmgenerierung und die Ausführung des Zielprogramms sind hier nur insofern voneinander abhängig, als die Generierung vor der Ausführung des Zielcodes stattfindet. Die Codegenerierung wird dann typischerweise vom Programmierer oder von einem Systemadministrator bei der Softwareinstallation angestoßen, also typischerweise nicht vom Endbenutzer.

Beispiel: Ein Programmgenerator (Codewizard) zur Erstellung des Basiscodes einer Programmklasse fragt verschiedene Parameter ab, wie Klassennamen, Anzahl, Namen und Typ der Klasseneigenschaften, Anzahl und Namen der Klassenmethoden und erstellt dann den Programmcode der Klasse.

Eine Neugenerierung ist nur dann notwendig, wenn sich Änderungen an den Generierungsparametern ergeben.

Dynamisch erzeugter Zielcode

Im zweiten Fall wird die Programmgenerierung und die Ausführung des Zielcodes in der Regel direkt vom Endbenutzer angestoßen. Dabei erfolgt die Programmgenerierung idealerweise so schnell, dass der Endbenutzer gar nicht merkt, dass der von ihm genutzte Programmteil erst vor wenigen Sekundenbruchteilen automatisch ausprogrammiert worden ist. Der Ablauf dieses dynamischen Vorgangs soll in einzelnen Schritten nachvollzogen werden:

  1. Der Anwender macht eine Auswahl der Eingangsparameter, z. B. den Namen einer Datenbanktabelle.
  2. Der Programmgenerator nimmt den Tabellennamen vom Endbenutzer, liest aus dem Datadictionary der Datenbank die Felder, Feldtypen und Fremdschlüsselbeziehungen und erzeugt aus diesen Steuerparametern den Programmzielcode eines Suchformulars zur Datenanzeige für die vom Benutzer vorgegebene Datenbanktabelle.
  3. Der Programmzielcode wird nun kurz kompiliert und dann vom Programmgenerator mit einem dynamischen Aufruf ausgeführt.

Der letzte Schritt stellt bestimmte Anforderungen an die verwendete Programmiersprache:

  • Es muss im Programmgenerator möglich sein, eine Routine aufzurufen, deren Namen variabel vorgegeben und im Kontext des Programmgenerators nicht zwingend bekannt ist (z. B. Vorgabe der aufzurufenden Routine durch eine Stringvariable, Late Binding).
  • Die notwendige Flexibilität in der Programmgenerierung verlangt nach einer interpretierten Sprache, d. h. in der Regel wird als Zielcode ein Interpretercode erzeugt und nicht ein Maschinencode. Grundsätzlich kann der Zielcode aber alles sein, d. h. ein Quellcode, Zwischencode oder Binärcode.

Der dynamisch erzeugte Zielcode ist sinnvollerweise oft in der gleichen Sprache codiert wie das codegenerierende Programmmodul. Ein Programmgenerator ist demnach ein auf eine generische Anwendungs- und Programmklasse spezialisierter Codegenerator. Er bezieht sich auf ein bestimmtes, zugrundeliegende generisches Programmmodell, aus welchem er nach konkretisierender Parametrisierung den Zielcode erzeugt. Eine komplexe Parametrisierung kann z. B. über ein Tabellenmodell in einer Datenbank erfolgen, welches die flexible Codegenerierung steuert.

Anwendungsbeispiele

UML

UML erlaubt die Erstellung einer Softwarearchitektur in Form eines Diagramms. Daraus kann dann automatisch Code erzeugt werden, der dann gewöhnlich „von Hand“ vervollständigt werden muss. Anspruchsvollere Entwicklungsumgebungen ermöglichen auch das gleichzeitige Arbeiten auf der UML und Sourcecode-Ebene. Man kann so wahlweise die UML oder den Sourcecode verändern und die Entwicklungsumgebung erstellt dann automatisch die jeweils andere Darstellung des Programms. Dabei wird also entweder UML-Code aus dem Sourcecode generiert oder umgekehrt.

Mit XML und XSLT

Auch mit XSLT ist automatische Codegenerierung möglich. Das gewünschte Modell wird in einem XML-Dokument dargestellt, dessen Syntax man selbst deklarieren kann. Dann erstellt man ein zu dem XML-Dokument passende XSLT-Skript, das den Programmcode generiert. Dies kann auch in einem mehrstufigen Prozess geschehen, z. B. generiert man mit einem ersten XSLT-Skript eine Batchdatei, ein Shellskript oder eine Makefile mit einer Liste weiterer XSLT-Verarbeitungsschritte oder anderer Befehle.

Formulargeneratoren

Anhand einer listenförmigen Beschreibung der Tabellenstruktur wird jeweils aus konkret vorgegebenen Tabellen eine Bildschirmmaske erstellt. Aufbau und Funktionsweise des Formulars ist fest vorgegeben. Die verschiedenen Tabellen unterscheiden sich jedoch bezüglich Art und Anzahl der Felder, der Feldbezeichnungen, -typen und Fremdschlüsselbeziehungen.

Compiler-Compiler

Die Syntax einer Programmiersprache wird z. B. in EBNF-Notation vorgegeben. Aufgrund dieser formalen Sprachdefinition erzeugt ein Compiler-Compiler den Compiler, bzw. ein Parsergenerator den Parser für die spezifizierte Sprache. Siehe hierzu auch: Coco/R und yacc.

Produktkonfiguration

Die generative Programmierung kann auch für die Abarbeitung von Stücklisten mit variablen Stücklistenpositionen verwendet werden. Im Rahmen des Customizings oder der Installation können generative Programme die Variantenkonfiguration von komplexen Softwareanwendungen auf die gewünschte Zielform bringen.

Rapid Control Prototyping

Beim Rapid Control Prototyping wird in der Regelungstechnik aus Blockdiagrammen ein für das Steuergerät angepasster Code erzeugt, sodass Fehler bei der Umsetzung von Blockdiagrammen in steuergerätspezifischen Code möglichst nicht mehr auftauchen.

Siehe auch

Literatur

  • Czarnecki, Krzysztof, Ulrich W. Eisenecker: Generative Programming: Methods, Tools, and Applications. Addison-Wesley, 2000, ISBN 0-201-30977-7.
  • Olaf Zwintzscher: Komponentenbasierte & generative Software-Entwicklung. W3L, 2003, ISBN 3-937137-50-5.
  • Peter Rechenberg, Hanspeter Mössenböck: Ein Compiler-Generator für Mikrocomputer. Grundlagen, Anwendung, Programmierung in Modula-2. Hanser, 1988, ISBN 3-446-15350-0.
  • Michael Klar: Einfach generieren. Generative Programmierung verständlich und praxisnah. Hanser, 2006, ISBN 3-446-40448-1.
  • Claude Gomez, Tony Scott: Maple programs for generating efficient FORTRAN code for serial and vectorised machines. In: Computer Physics Communications. Band 115, Nummer 2–3, 1998, S. 548–562, doi:10.1016/S0010-4655(98)00114-3.
  • T.C Scott, M.B Monagan, I.P Grant, V.R Saunders: Numerical computation of molecular integrals via optimized (vectorized) FORTRAN code. In: Nuclear Instruments and Methods in Physics Research Section A: Accelerators, Spectrometers, Detectors and Associated Equipment. Band 389, Nummer 1–2, 1997, S. 117–120, doi:10.1016/S0168-9002(97)00059-4.
  • T.C. Scott, Wenxing Zhang: Efficient hybrid-symbolic methods for quantum mechanical calculations. In: Computer Physics Communications. Band 191, 2015, S. 221–234, doi:10.1016/j.cpc.2015.02.009.