MGSE

Modellgetriebene Softwareentwicklung - HTWG

Modellgetriebene Softwareentwicklung - HTWG


D. S.
Diese Lernkarten decken fortgeschrittene Themen der Informatik auf Universitätsniveau ab. Sie konzentrieren sich auf modellgetriebene Softwareentwicklung (MDSD), domänenspezifische Sprachen (DSL) und deren Implementierung mit Tools wie Xtext. Die Karteikarten behandeln Themen wie Syntax, Codegenerierung, Metamodelle und die Vorteile von MDSD, einschließlich Produktivitätssteigerung und höherer Qualität. Sie richten sich an Entwickler und Studierende, die sich mit der Erstellung und Nutzung von DSLs sowie der Automatisierung von Softwareentwicklung beschäftigen.
Cartes-fiches
138
Utilisateurs
1
Langue
Allemand
Catégorie
Informatique
Niveau
Université
Créé / Mis à jour
30.01.2018 / 01.02.2018

Cartes-fiches

Externe DSL: Parser-Generator bison

bison generiert einen Parser, der Tokenfolgen auf syntaktische Korrektheit prüft

Externe DSL: Parser-Generator ANTLR

ANTLR 4 (Another Tool for Language Recognition Version 4) generiert Klassen für

die Erkennung von Tokens (Lexer statt Scanner genannt) und die Syntaxanalyse.

Aus einer Grammatikbeschreibung Name.g4 entstehen im Falle von Java:

NameLexer.java

NameParser.java

NameListener.java

NameBaseListener.java

Anders als bei flex und bison (und älteren Versionen von ANTLR) soll die

Grammatikbeschreibung keine Aktionen mit Programmcode enthalten.

Die Aktionen sollen statt dessen getrennt von der Grammatik in einer

Listener-Implementierung bereitgestellt werden (alternativ auch Visitor-Implementierung).

Empfehlungen DSLs, XML-DSL und UML-DSL

• Modelle bevorzugt mit DSLs formulieren!

Modellerstellung wird kompakter, weniger fehleranfällig und für Domänenexperten verständlicher
aber: die Entwicklung einer DSL kann je nach Sprachklasse sehr aufwendig sein

• Graphische oder textuelle DSL?

wenn vor allem komplexe Beziehungen zu modellieren sind, ist eine Graphik tendenziell anschaulicher Werkzeugunterstützung für Text ist wesentlich einfacher

vor der Entscheidung für die eine oder andere konkrete Syntax sollten erst einmal abstrakte Syntax und statische Semantik entworfen werden

• UML-Spezialisierung als DSL?

- Profile durch UML-Werkzeuge nicht wirklich gut unterstützt
- Einschränkung der Mächtigkeit von UML mittels OCL sehr mühsam

Profile sind eher geeignet, UML-Modelle mit Annotationen anzureichern
(wir kommen darauf bei den Modelltransformationen zurück)

• XML-Spezialisierung als DSL?

- wenig Spielraum beim Sprachdesign
- großer syntaktischer Ballast

+ sehr gute und reichhaltige Werkzeugunterstützung vorhanden
XML-Dialekte sind eher als Datenaustausch- und Speicherungsformat geeignet,
wegen des syntaktischen Ballasts nur in einfachen Fällen menschenlesbar/schreibar

Empfehlungen für interne und externe DSL

• interne DSL?

- wenig Spielraum beim Sprachdesign
- beschränkt auf Zielgruppe Programmierer, die die Wirtssprache beherrschen
+ geringer Implementierungsaufwand durch Wiederverwendung der Werkzeuge
der Wirtssprache
+ geringer Lernaufwand für die Zielgruppe

• externe DSL?

+ maximaler Spielraum beim Sprachdesign
- Implementierungs-, Pflege- und Lernaufwand kann sehr groß werden

deshalb eher für kleine Domänen geeignet
erstrebenswert: Komposition von DSLs aus wiederverwendeten Teil-DSLs

Was fpr Modelltransformation gibt es?

- Modell-zu-Modell-Transformation

- Modell-zu-Text-Transformation

Was ist eine Modell-zu-Modell-Transformation?

• Abbildung eines Quellmodells auf ein Zielmodell

• Formulierung der Transformationsvorschriften auf der Ebene der Metamodelle

• beide Metamodelle müssen das gleiche Meta-Metamodell haben

erlaubt mehrstufige Transformationen

erlaubt Anpassung von Modellen an neue DSL- und Metamodell-Versionen (Model Refactoring, Model Migration)

Was ist Modell-zu-Text-Transformation?

• Abbildung eines Quellmodells direkt auf eine textuelle Repräsentation des Zielmodells

• für die Abbildung wird kein Ziel-Metamodell und damit auch kein gemeinsames Meta-Metamodell benötigt

M2T ist der übliche Weg bei der Software-Generierung

Modelltransforomation - prinzipieller Ablauf

| Quellmodell [konkrete Synatax] | ----> (abstrahieren)

                                                                     |------> | Quellmodell (abstrakte Syntax) | 

| Transformationsvorschrift | ------------------------> (transformieren) <-----------|

                                                                      API   |-------------> | Zielmodell (abstrakte Syntax) |

                                                                          (konkretisieren) <-----------|

                                                                                              |----------> | Zielmodell (konkrete Syntax)

Modelltransformation: vereinfachter Ablauf

  |Modell (konkrete Syntax) | --> (abstrahieren) --> |Modell (abstrakte Syntax) | ----> (generieren)

                                                                                                                  Template >------| |----> |Generierte Code (konkr. Syntax)|

Model-to-Model-Transformation - Eigenschaften

Aspekte für die Klassifikation von M2M-Transformationen:

• Kardinalität

1:1 aus einem Quellmodell entsteht ein Zielmodell

M:1 Zusammenfassung mehrere Quellmodelle zu einem Zielmodell

• Richtung

unidirektional Quellmodell wird in Zielmodell überführt

bidirektional wechselseitige Synchronisation von Quell- und Zielmodell

• Metamodell

einheitlich Refactoring von Modellen

verschieden echte Transformation (Meta-Metamodell ist einheitlich)

• Modellzugriff

erhaltend Quellmodell wird nur gelesen, Zielmodell ist neue Instanz

modifizierend Quellmodell wird geändert, z.B. beim Refactoring

Standardfall: unidirektionale, erhaltende 1:1-Transformation mit verschiedenem Metamodell

Modell-to-Text Transformation - Eigenschaften

Klassifikation von M2T-Transformationen:

• Spezialgenerator mit interner Abbildungsvorschrift

Der Generator gibt die Abbildung von Modellen auf Text fest vor.

Änderung der Abbildung nur durch Umprogrammieren des Generators

kein offen gelegtes API für den abstrakten Syntaxbaum

Beispiel: Stub-Generatoren für den Fernaufruf von Methoden in verteilten Systemen

• universeller Generator mit externer Abbildungsvorschrift

Die Abbildung von Modellen auf Text ist in Templates ausgelagert.

Änderung der Abbildung durch Verwendung anderer Templates

offen gelegtes API für den abstrakten Syntaxbaum

Templates werden mit einer Templatesprache formuliert

Modell-to-Transformation: Modell - Programm - Plattform [WICHTIG]

WICHTIG WICHTIG WICHTIG

Begriffsdefinition zu Plattform

Plattform

von einer Software als gegeben vorausgesetzte Umgebung, typischerweise
bestehend aus Hardware-Architektur, Betriebsystem und Laufzeitbibliotheken

Eine Software wird dementsprechend oft als plattform-unabhängig bezeichnet, wenn sie
auf unterschiedlichen Betriebssystemen und / oder Hardwarearchitekturen lauffähig ist.

Software-Plattform

System von APIs (Application Programming Interfaces),
das eine Anwendung benötigt, um ablauffähig zu sein.

Die Bezeichnungen Plattform und Anwendung sind dabei relativ. Eine Anwendung kann
ihrerseits wieder Plattform für Code einer nächst höheren Abstraktionsebene sein.

Zu den APIs können Systemaufrufe des Betriebssystems, die Schnittstellen von
Standardsoftware (z.B. Datenbanksystem), die Laufzeitbibliothek der Programmiersprache,
domänenspezifische Frameworks usw. gehören.

Abstraktionsebenen von Plattformen

Unterscheidung von Plattformen nach Abstraktionsebene:

• elementare Software-Plattform

nur Betriebssystem mit etwas Standardsoftware und der Laufzeitbibliothek der Programmiersprache

Art des generierten Codes: normaler Programmcode
Umfang des generierten Codes: vergleichsweise groß

Generierung erlaubt es, statt komplizierter Abstraktionsmechanismen eine einfache Softwarearchitektur mit viel horizontaler Redundanz zu wählen.

• komplexe Software-Plattform

Verwendung domänenspezifischer Frameworks

Art des generierten Codes: vorrangig Konfiguration
Umfang des generierten Codes: vergleichsweise gering

Generierung vereinfacht die Konfiguration der Framework und erleichtert die Migration auf andere Frameworks

Verwendungszwecke von Plattformen

Unterscheidung von Plattformen nach Verwendungszweck:

• Zielplattform: darauf soll das fertige Programm letztendlich produktiv laufen

• Entwicklungs- und Testplattform: vorläufiger Ersatz für die Zielplattform

Gründe für eine Entwicklungs- und Testplattform:

• Zielplattform wird auch erst entwickelt

Abgrenzung zwischen Anwendungs- und Plattformcode dann mitunter unscharf

• Zielplattform nur eingeschränkt verfügbar

z.B. weil Hardware oder Softwarelizenzen nur in geringer Stückzahl vorhanden sind

• Zielplattform mühsam zu benutzen

z.B. lange Compile-Load-Run-Zyklen bei Spezialhardware

oder eingeschränkte Debug-Möglichkeiten

• Zielplattform ungeeignet für Komponententests (Unit-Tests)

z.B. weil ein Testframework verwendet werden soll

Abhängigkeiten von generierter und manueller Code

• Generierter und manueller Code in getrennten Übersetzungseinheiten:

Abhängigkeiten entstehen durch die wechselseitige Verwendung von Namen

(z.B. Variablenzugriffe, Methodenaufrufe, Typinstanzierungen, ...)

kein prinzipieller Unterschied zu den Abhängigkeiten vom Plattform-Code

zur Entschärfung der Abhängigkeiten allgemeine Regeln zur losen Kopplung beachten

• Generierter und manueller Code in einer Übersetzungseinheit gemischt:

Abhängigkeiten entstehen durch die Einbettung

(z.B. in generierter Methode Implementierung ganz oder teilweise manuell, ...)

manuelle Teile gehen bei erneutem Generierungslauf verloren,

wenn sie nicht besonders geschützt werden

deshalb vermischten Code möglichst vermeiden

Präprozessortechnik bei generierter und manueller Code

Präprozessortechnik trennt Codeanteile auf Dateiebene

• generierten und manuellen Code in getrennten Dateien erstellen

und per Präprozessor zu übersetzbarem Code kombinieren

z.B. Einbettung manuellen Codes in generierten Code mittels C++-Präprozessor

(umgekehrte Einbettung von generiertem in manuellen Code funktioniert analog):

 

// GenerierteKlasse.cpp

#include "GenerierteKlasse.h"

...

void GenerierteKlasse::GenerierteMethode() {

#include "manuelle_methodenimplementierung.h"

}

...

 

• Dateiaufteilung führt tendenziell zu schwer lesbarem unübersichtlichem Code,

erleichtert aber die Versions- und Konfigurationsverwaltung

 

Entwurfsmuster in generierter und manueller Code

Objektorientierte Entwurfsmuster trennen Codeanteile auf Klassenebene

• abstrakte Oberklasse (oder ein Interface) generieren und

abstrakte Methoden in konkreter Unterklasse manuell implementieren

(oder auch umgekehrt)

// GenerierteKlasse.java

public abstract class GenerierteKlasse {

...

public abstract void generierteMethode();

...

}

// ManuelleKlasse.java

public final class ManuelleKlasse extends GenerierteKlasse {

public void generierteMethode() {

... // Implementierung

}

}

Geschützte Bereiche in generierter und manueller Code

Geschützte Bereiche erlauben Einbettung von manuellem in generierten Code

• im generierten Code den manuellen Code mit speziellen generatorspezifischen

Kommentaren markieren, damit er bei Neugenerierungen übernommen wird

// GenerierteKlasse.java

public class GenerierteKlasse {

...

void generierteMethode() {

// PROTECTED REGION ID(123456) START

... // manueller Code

// PROTECTED REGION END

}

...

}

• auch geschützter manueller Code kann bei Neugenerierungen verloren gehen

z.B. kann nach dem Umbenennen oder Löschen von Modellelementen

der geschützte manuelle Code eventuell nicht mehr zugeordnet werden

• geschützte Bereiche erschweren die Versions- und Konfigurationsverwaltung

Templates in generierter und manueller Code

Codetemplates erlauben Einbettung von generiertem in manuellen Code

• in manuellen Code spezielle generatorspezifische Anweisungen einbetten,

die den generierten Code einfügen

// ${entity.name}.java

public class ${entity.name} extends ManuelleKlasse {

...

#foreach($attribute in $entity.attributes)

public ${attribute.type} get${attribute.name}() {

return this.${attribute.name};

}

private ${attribute.type} ${attribute.name};

#end

...

}

• aktuell die bevorzugte Technik für das Mischen der beiden Codeanteile

Modellanreicherung in generierter und manueller Code

Modellanreicherung erlaubt Einbettung von manuellem Code auf Modellebene

• manuellen Code in das Modell einbetten statt in den daraus generierten Code

z.B. beim Scanner-Generator flex und dem Parser-Generator bison

wird die Grammatikspezifikation um C-Code angereichert

• vermeidet im Vergleich zu geschützten Bereichen die Probleme

mit der Versions- und Konfigurationsverwaltung

• aber führt tendenziell zu schwer lesbaren unübersichtlichen Modellen

• beeinträchtigt die Plattformunabhängigkeit des Modells

durch den eingebetteten Code legt ein angereichertes Modell bereits

die Programmiersprache und einige benötigte APIs fest

Was sind Templatesprachen?

Templatesprachen beschreiben die Einbettung von Modellelementen in Zielcode

• bieten Sprachmittel für den Zugriff auf den abstrakten Syntaxbaum des Modells

• erlauben auch Transformationen in nicht-formale Sprachen

• Templates ähneln weitgehend dem zu generierenden Code

und lassen sich deshalb leicht aus Beispielcode ableiten

Beispiele: VTL (Velocity Template Language), StringTemplate

Was sind M2M-Sprachen?

M2M-Sprachen (Relationssprachen) beschreiben Relationen zwischen

Elementen der Quellsprache und Elementen der Zielsprache

• erlauben das Formulieren von Regeln für die Abbildung von

Knoten des Quell-AST auf Knoten des Ziel-AST

• verlangen in der Regel eine gemeinsames Meta-Metamodell für Quelle und Ziel

Beispiele: ATL (Atlas Transformation Language), XSLT

VTL

VTL ist die Templatesprache des Apache Velocity Projekts.

• Referenzen erlauben den Zugriff auf Java-Objekte eines Modells:

$name.property

$name.method(Parameter)

Die Objekte werden Java-seitig in Form einer Hashtabelle bereitgestellt.

Schlüssel ist ein String name, der dann im Template als $name angegeben wird.

Properties werden auf getter-Methoden abgebildet.

Modellzugriffe liefern als Ergebnis immer einen String.

• Iteration über Modellelemente:

#foreach( $i in Referenz ) ... #end

• Bedingte Template-Abschnitte:

#if( Bedingung ) ... #elseif( Bedingung ) ... #else ... #end

• Aufteilung und Gliederung von Templates:

unter anderem mit #include(...), #parse(...), #macro(...) ... #end

StringTemplate

StringTemplate ist die Templatesprache des ANTLR Parser Generators,

ist aber unabhängig von ANTLR verwendbar.

• Referenzen erlauben den Zugriff auf Java-Objekte eines Modells:

<attribute.property>

Attribute sind sozusagen Parameter von Templates und werden Java-seitig

mit Objekten des Modells initialisiert.

Properties werden auf getter-Methoden abgebildet.

• funktionaler Stil für die Iteration über Modellelemente:

<attribute.property:Template()>

<attribute.property:Template(); separator=”…”>

Auf alle Elemente der Property das angegebene Template anwenden.

• Bedingte Template-Abschnitte:

<if( Bedingung )> ... <elseif( Bedingung )> ... <else> ... <end>

XSLT

XSLT ist eine M2M-Sprache für die Umwandlung von XML-Dokumenten in andere

XML-Dokumente (oder auch sonstige Textformate).

• Templates werden auf Knoten im DOM-Tree angewendet:

<xsl:template match="XPath-Ausdruck"> ... </xsl:template>

• funktionaler Stil für die Iteration über Kindknoten im DOM-Tree:

<apply-templates/>

XPath

XPath ist eine Sprache für die Navigation in DOM-Trees.

• XPath-Ausdrücke bestehen aus einer durch Schrägstriche '/' getrennten

Folge von Lokalisierungsschritten:

axis::nodetest[predicate]

Achsen sind u.a. self der aktuelle Knoten

descendant alle Knoten unterhalb im Baum

ancestor alle Knoten oberhalb im Baum

following alle Knoten rechts im Baum = unterhalb im Dokument

preceeding alle Knoten links im Baum = oberhalb im Dokument

Generieren oder manuell erstellen?

Grundsätzlich alles generieren, was sich aus dem Modell herleiten lässt

und in der gewählten Plattform nicht bereits abgedeckt ist

• aber Modell nicht um der Generierung willen aufblähen

bei Modellen auf klar umrissene Domänen und auf Plattformunabhängigkeit achten

• entweder generieren, oder manuell erstellen

generierten Code möglichst nicht manuell nachbearbeiten

• gut lesbaren Code generieren

in der Regel die gleichen Qualitätskriterien erfüllen wie bei manuell erstelltem Code,

z.B. Formatierungsregeln einhalten, Entwurfsmuster einsetzen

• nicht nur für Zielplattform, sondern auch für Testplattform(en) generieren

z.B. Mocks und Dummies für Komponententests generieren

• nicht nur Programmcode generieren

z.B. Dokumentation generieren,

z.B. Skripte für Buildprozess, Deployment, Datenmigration usw. generieren

Generieren oder wiederverwenden?

• wiederverwendeten Code in Plattform integrieren

bei Wiederverwendung von Komponenten braucht oft nur noch

einfacher Glue-Code generiert zu werden

• aber Plattform auch nicht zu sehr aufblähen

oft wird nur ein Bruchteil der Funktionalität einer wiederverwendeten Komponente

tatsächlich genutzt und der Rest als toter Code mitgeschleppt (Rattenschwanzeffekt)

• auch Generierungen wiederverwenden, d.h. die Generierung kaskadieren

z.B. aus dem Modell einer verteilten Anwendung

im 1. Schritt IDL-Beschreibungen generieren und daraus

im 2. Schritt per wiederverwendetem Generator die Client- / Server-Stubs generieren

Was ist der Kern von MDSD?

WICHTIG

Modelgetriebene Softwareentwicklung - Motivation und Idee

 

• Motivation

Software wird größtenteils manuell geschrieben

Verwendung von Frameworks führt zu einer großen Menge

strukturähnlichen Codes

• Idee

Beschreibung des zu erzeugenden Systems durch ein Modell

Die Ausdrucksmöglichkeiten des Modells hängen von der

speziellen Domäne ab

Die domänenspezifische Sprache (DSL) beschreibt variable Teile

des Systems/Codes

In der DSL formulierte Modelle beschreiben die Varianten

DSL-Editoren ermöglichen einfache Erzeugung von Modellen

Generator generiert Artefakte aus Modellen

Vorteile von Modellgetriebene Softwareentwicklung

- Produktivitätssteigerung:
Entwicklungsaufwand verringert sich erheblich

- Komplexität wird verborgen:
Abstraktion vom verwendeten Framework | Rollenteilung zwischen fachlicher und nichtfunktionaler Entwicklung möglich

- Höhere Qualität, weniger Fehler:
Die Fehlerzahl im schematischen Code wird durch die Generierung erheblich reduziert. Fehler im generierten Code werden zentral im Generator behoben.

- Größere Flexibilität für Technologiewechsel und Produktvarianten
-- Technologiewandel
-- Produktfamilie

- Gute Dokumentation
Das Modell ist Kernbestandteil der Entwicklung. Er ist immer aktuell und veraltet nicht bei Änderungen des Codes.
Die Businesskonzepte sind vollständig dokumentiert. Modell abstrahieren von technologiscchen Abhängigkeiten und sind oft auch besser für die Domänenexperten verständlich.

 

 

Vorteile von MDSD

Vorteile:

1. Produktivitätssteigerung

2. Komplexität wird verborgen

3. Höhere Qualität

4. Flexibilität für Technologiewechsel und Produktvarianten

5. Gute Dokumentation

 

Nachteile von MDSD

Nachteile

• Kenntisse in Metamodellierung und

Modellierung nötig

• Entwicklung und/oder Nutzung eines

Toolsets erforderlich (DSL-Editoren)

• Entwicklung einer entsprechenden

Architektur incl. Generator nötig

• Innovationswille der Beteiligten

erforderlich

• Versionierung von Modellen nicht ganz

unproblematisch

DSL

Sprache, die die fachliche Domäne beschreibt (oft auch Metamodell)

Generator

Tool zur automatischen Erzeugung von Artefakten aus Modellen

Generat

Vom Generator erzeugtes Artefakt, z.B. Programm-Code oder Konfigurationsdateien

Template

Schablonenhafte Beschreibung des Generierungsvorgangs

Cartridge

DSL, Generator und Templates für eine spezielle Zielplattform

DSL-Editor

Erstellung eines (graphischen) DSL-Editors

Étudier