B Java
Die bei einer Softwareentwicklung verwendete Programmiersprache hat wesentlichen Einfluss auf die Effektivität der Entwickler sowie die Qualität, Kosten, Weiterentwickelbarkeit und Wartbarkeit des Produkts. Die in dieser Arbeit vorgestellte Methodik UML/P verwendet als Zielsprache Java. Für eine gute Integration mit der Modellierungssprache UML/P werden daher bei der Definition von UML/P soweit wie möglich Elemente aus der Java-Grammatik verwendet. Deshalb wird in diesem Abschnitt die in [GJSB05] definierte Referenzgrammatik für die Sprache Java 6 in EBNF-Form übertragen.
Bei der Verwendung eines der nachfolgend eingeführten Nichtterminale in anderen Kapiteln wird dem Nichtterminal das Subscript der Abbildung, in der es definiert ist, angehängt. So verweist ⟨TypeB.2⟩ auf die Definition dieses Nichtterminals in Abbildung B.2.
Auf eine Darstellung der lexikalischen Elemente der Sprache Java wird hier verzichtet und auf [GJSB05] verwiesen. Dort wird die Form der Kommentare, der „white spaces“, des für Java benutzen Unicode-Alphabets und der Schlüsselwörter erklärt. Insbesondere seien die in Abbildung B.1 aufgelisteten Nichtterminale ⟨Literal⟩, ⟨Identifier⟩ und ⟨Digits⟩ als bekannt voraus gesetzt (siehe auch Anhang A).
Wie aus den eingeführten Nichtterminalen ersichtlich ist, werden die englischen Originalnamen verwendet, damit ein direkter Vergleich mit der in [GJSB05] gegebenen Grammatik möglich ist. Aus den dort gegebenen 124 Nichtterminalen mit relativ einfach strukturierten Produktionen werden 62 Nichtterminale mit komplexeren EBNF-Produktionen komprimiert. Dabei behalten fast alle übernommenen Nichtterminale ihre ursprüngliche Bedeutung. Einige wenige Nichtterminale werden neu eingeführt.
In Abbildung B.2 sind die Produktionen für Typen normale und generische Klassen, qualifizierte Namen und Kompilierungseinheiten (Dateien) enthalten.
Die Definition von Klassen und Interfaces ist in Abbildung B.3 beschrieben.
Die in Klassen- und Interface-Deklarationen enthaltenen Attribute, Methoden und Konstruktoren werden in Abbildung B.4 erklärt.
Die Form der Java-Anweisungen ist in der Abbildung B.5 erklärt. Die hier beschriebene Sprache ist trotz der starken Kompaktifizierung der Grammatik, die durch die Verwendung der EBNF möglich wurde, mit der Originalsprache Java nahezu identisch. Geringfügige Vereinfachungen wurden vorgenommen, die ausschließlich der Erhöhung der Lesbarkeit der Grammatik dienen. So ist bei einer try-Anweisung nicht durch die Grammatik festgelegt, dass wenigstens ein catch oder finally anzugeben ist. Außerdem ist diese kompakte Grammatik bezüglich des bekannten if-then-else-Phänomens mehrdeutig und daher nicht direkt als Vorlage für einen Parser geeignet.
Die Teilsprache zur Beschreibung der Ausdrücke ist in der Abbildung B.6 definiert. Die in Java zur Verfügung stehende Ausdruckssprache ist nicht zuletzt aufgrund der Möglichkeit, innere Klassen zu definieren und zu nutzen sowie Arrays zu initialisieren, relativ komplex. Die Grammatik aus [GJSB05] wurde deshalb zum einen durch eine vollständige Auslagerung der Operator-Prioritäten in der Tabelle B.7 kompaktifiziert. Zum anderen wurden die Produktionen für ⟨Primary⟩ und ⟨Expression⟩ umgebaut.
Priorität
|
Operator
|
Assoziativität
|
Operand, Bedeutung
|
|
|
|
|
13
|
++, --
|
rechts
|
Zahlen
|
|
+, -, ~, !
|
rechts
|
Zahlen, Boolean (!)
|
|
(type)
|
rechts
|
Typkonversion (Cast)
|
12
|
⋆, /, %
|
links
|
Zahlen
|
11
|
+, -
|
links
|
Zahlen, String (+)
|
10
|
<<, >>, >>>
|
links
|
Shifts
|
9
|
<, <=, >, >=
|
links
|
Vergleiche
|
|
instanceof
|
links
|
Typvergleich
|
8
|
==, !=
|
links
|
Vergleiche
|
7
|
&
|
links
|
Zahlen, Boolean
|
6
|
^
|
links
|
Zahlen, Boolean
|
5
|
|
|
links
|
Zahlen, Boolean
|
4
|
&&
|
links
|
Boolesche Logik
|
3
|
||
|
links
|
Boolesche Logik
|
2
|
? :
|
rechts
|
Auswahlausdruck
|
1
|
=, ⋆=, /=, %=
|
rechts
|
Zuweisung
|
|
+=, -=
|
|
|
|
<<=, >>=, >>>=
|
|
|
|
&=, ^=, |=
|
|
|
|
Tabelle B.7: Prioritäten der Infixoperatoren
Annotationen und ihre Definition sind in Abbildung B.8 definiert.
ocl-Anweisung für Zusicherungen
Java bietet eine assert-Anweisung, die es erlaubt in den Code Zusicherungen einzubauen, die vom Laufzeitsystem geprüft werden. Damit ist bereits ein wesentlicher Schritt zur praktischen Verwendung von Invarianten erreicht. In diesem Abschnitt wird eine ergänzende Form der assert-Anweisung vorgeschlagen, die die Verwendung von OCL-Ausdrücken erlaubt, und definierte und benannte OCL-Bedingungen auch durch Referenzierung einbauen kann. Diese Anweisung wird mit dem Schlüsselwort ocl gekennzeichnet. Zusätzlich wird das let-Konstrukt aus der OCL übernommen, um damit lokale Variablen zu definieren, die genau wie in der OCL zur ausschließlichen Verwendung in Invarianten gedacht sind. In Abbildung B.9 werden die Erweiterungen eingeführt, indem das Nichtterminal ⟨Statement⟩ aus Abbildung B.5 um entsprechende Anweisungen ergänzt wird.
Die hier definierte ocl-Anweisung erlaubt nur die Verwendung von OCL-Ausdrücken, so dass die Abwesenheit von Seiteneffekten garantiert ist. Das erste Argument der ocl-Anweisung ist das zu prüfende boolesche Prädikat. Das optionale zweite Argument wird ausgewertet, wenn das Prädikat falsch ist, und dessen Wert zum Beispiel beim Einsatz in Tests angezeigt.
Während die erste Variante der ocl-Anweisung im ersten Argument die direkte Verwendung eines OCL-Ausdrucks erlaubt, beziehen sich die anderen beiden Formen durch Verwendung eines Namens auf eine OCL-Bedingung, die an anderer Stelle definiert wurde. Damit können OCL-Bedingungen mehrfach verwendet werden.
Eine OCL-Bedingung beginnt mit einer expliziten Definition eines Kontexts in Form einer oder mehrerer Variablen. Wie in Abschnitt 3.1.1 erklärt, wird damit eine Allquantifizierung über die angegebenen Variablen vorgenommen. Um diese Quantifizierung aufzulösen, kann eine explizite Zuordnung der zu prüfenden Objekte auf die Kontext-Variablen vorgenommen werden, indem die OCL-Bedingung als boolesches Prädikat verstanden wird, dem diese Objekte als Argumente übergeben werden. Ist beispielsweise folgende Bedingung definiert:
context Auction a, Person p inv NM:
p in a.bidder implies
forall m in a.message : m in p.message
so kann mit ocl NM(a,theo) geprüft werden, ob die Nachrichten einer Auktion an Person theo versandt wurden.
Ist der Kontext der OCL-Bedingung nicht mit context, sondern dem Schlüsselwort import festgelegt, so findet nach Definition keine Allquantifizierung statt, sondern die dabei festgelegten Namen werden direkt aus dem Kontext importiert. Das bedeutet, unter Nutzung der Variation der obigen OCL-Bedingung
import Auction a, Person p inv NM2:
p in a.bidder implies
forall m in a.message : m in p.message
kann mit ocl NM2 eine Aussage über die beiden im Java-Kontext definierten Variablen a und p gemacht werden, ohne dass diese Variablen explizit angegeben werden.
Erfahrungsgemäß sind zur Prüfung von Zusicherungen zu einem Zeitpunkt die früheren Belegungen von Attributen oder Zwischenergebnisse früherer Berechnungen notwendig. Diese stehen unter Umständen zum Zeitpunkt der Evaluation einer Zusicherung nicht mehr zur Verfügung und müssen daher vorher explizit zwischengespeichert werden. Für die Definition von Zwischenergebnissen, die ausschließlich zur Prüfung von Zusicherungen verwendet werden, ist das let-Konstrukt geeignet. Es führt eine Zwischenvariable ein, die im normalen Java-Code nicht verwendet werden kann und deshalb keinen Effekt auf die Programmausführung haben kann. let-Anweisungen können also wie ocl-Anweisungen im Produktionssystem weggelassen werden.
Gemäß der Semantik von OCL-Bedingungen und des OCL-let-Konstrukts, werden die während der Auswertung der Argumente dieser Konstrukte auftretenden Exceptions abgefangen. Evaluiert das Argument des ocl-Konstrukts zu einer Exception, so wird dies als Nichterfüllung der Bedingung gewertet. In der let-Anweisung wird jedoch die Variable mit einem Default-Wert wie zum Beispiel null besetzt.
Bernhard Rumpe. Agile Modellierung mit UML. Springer 2012