Johann Wolfgang Goethe – Universität Frankfurt am Main Diplomarbeit Entwicklung eines Klassengraphen von Carsten Rudolf Stocklöw Fachbereich Biologie und Informatik Prüfer: Prof. Dr.- Ing. D. Krömker Betreuer: Dipl.-Inform. Tobias Breiner vorgelegt am 30. September 2004 Inhaltsverzeichnis Kapitel 1 Einleitung........................................................................................... 1 1.1 Software Engineering....................................................................................3 1.2 Refaktorisierung............................................................................................3 1.3 Sichten auf ein Software-System.................................................................. 4 1.4 Aufbau der Arbeit......................................................................................... 5 Kapitel 2 Graphische Darstellungsmöglichkeiten...........................................6 2.1 Objektorientierte Analyse- und Designmethoden.........................................6 2.2 UML............................................................................................................10 2.2.1 Geschichte.....................................................................................11 2.2.2 Überblick...................................................................................... 12 2.2.2.1 Die Four Layer Modeling Architecture........................ 12 2.2.2.2 Infrastructure und Superstructure................................. 14 2.2.2.3 Erweiterungsmöglichkeiten.......................................... 15 2.2.3 Diagramme und Views................................................................. 16 2.2.3.1 Klassen- und Objektdiagramm.................................... 19 2.2.3.2 Model Management – Paketdiagramm......................... 28 2.2.3.3 Weitere Diagramme......................................................30 Kapitel 3 Stand der Technik........................................................................... 38 3.1 Programm: ClassBuilder 2.4 Alpha 1.7...................................................... 38 3.2 Programm: Jumli 1.4...................................................................................40 3.3 Programm: Metamill v3.1 (build 556) ....................................................... 42 3.4 Programm: ObjectDomain R3 (build 292)................................................. 43 3.5 Programm: objectiF 4.7.............................................................................. 45 3.6 Programm: Rational Rose Enterprise Edition (2003.06.00.436.0)............. 47 3.7 Programm: WithClass 2000 Enterprise 6.0................................................ 49 3.8 Weitere Programme.................................................................................... 52 3.9 Fazit............................................................................................................ 54 Kapitel 4 Diskussion und Konzept..................................................................55 4.1 Die Programmiersprache C++.................................................................... 55 4.2 Die graphische Darstellung.........................................................................56 4.2.1 Datentypen, Zeiger und Referenzen..............................................57 4.2.2 Module und Komponenten........................................................... 58 4.2.3 Klassen..........................................................................................59 4.2.4 Methoden...................................................................................... 63 4.3 Synchronisation mit Sourcecode.................................................................64 4.4 Design......................................................................................................... 65 Kapitel 5 Implementierung............................................................................. 66 5.1 Parser, Präprozessor....................................................................................66 5.1.1 Alternative Darstellungen............................................................. 68 5.1.2 Präprozessor..................................................................................69 5.1.2.1 Symbolische Konstanten und Makros.......................... 69 5.1.2.2 Dateien einfügen........................................................... 71 5.1.2.3 Bedingte Kompilierung.................................................72 5.1.2.4 Weitere Direktiven........................................................72 5.1.2.5 Unterstützung durch das Programm............................. 73 5.1.3 Analyseverfahren.......................................................................... 75 5.2 Automatisches Layout.................................................................................79 5.2.1 Ästhetik.........................................................................................80 5.2.2 Layoutalgorithmen........................................................................81 5.3 Das Programm DiaClassma........................................................................ 84 5.3.1 Übersicht.......................................................................................84 5.3.2 Externe Komponenten.................................................................. 85 5.3.3 Graphische Benutzungsoberfläche............................................... 86 5.3.3.1 Explorer........................................................................ 87 5.3.3.2 Klassendiagramm......................................................... 88 5.3.4 Parser............................................................................................ 89 5.3.5 Datenverwaltung...........................................................................90 Kapitel 6 Bewertung........................................................................................ 92 Anhang A Glossar............................................................................................ 95 Anhang B Stand der Technik........................................................................100 Anhang C Inhalt der beigefügten CD-ROM............................................... 103 Anhang D Abbildungsverzeichnis................................................................ 104 Anhang E Literaturverzeichnis.................................................................... 106 Kapitel 1 Einleitung Heutige Softwaresysteme gewinnen zunehmend an Komplexität. Manche objektorientierte Programme bestehen aus mehreren hundert, wenn nicht sogar tausend Klassen, die üblicherweise in Form einfacher Textdateien vorliegen. Einen kompletten Programmcode aus hunderttausenden von Codezeilen zu verstehen und damit zu arbeiten, während er kontinuierlich weiterentwickelt wird, ist für einen Menschen nicht zu bewältigen. Deshalb entwickeln meist eine große Anzahl von Leuten an einem Software-Projekt und sind nur für bestimmte Bereiche zuständig. Aber auch hier muss man den Überblick behalten, sowohl für das gesamte Software-System als auch für einzelne Teile, die im Zusammenspiel aufeinander abgestimmt werden müssen. Nun kann diese Problematik mit der Natur der Programmiersprachen zusammenhängen, die als Sprachen per se linear orientiert sind. Es stellt sich die Frage, ob graphische Darstellungen besser geeignet wären. Durch das Hinzufügen einer zweiten oder gar einer dritten Dimension könnten Vererbungshierarchien und vernetzte Zusammenhänge wie beispielsweise Funktionsaufrufe besser visualisiert und durch das Ausblenden von Implementierungsdetails auf einen Blick erfasst werden. Im Laufe der Zeit haben sich einige Hilfen für Programmierer entwickelt: • Dokumentationserstellung Anhand eines vorhandenen Sourcecodes wird eine Dokumentation erstellt, beispielsweise im HTML-Format • Wizards Wizards (engl. für Zauberer) sind kleine Helfer, die überall eingesetzt werden können und üblicherweise durch die kurze Abfrage einiger Informationen eine Aufgabe erfüllen. So kann es beispielsweise einen Wizard geben, der durch Abfrage eines Klassennamens eine neue Klasse erstellt. • Editoren Erweiterungen für einfache Texteditoren sind Syntax-Highlighting, Klammerungsanzeige (zu einer Klammer wird die entsprechende andere Klammer im Quelltext markiert), Vervollständigung von Wörtern, Sprung zur Definition eines Elements, Ausblenden von Methoden-Code (selten benutzte Methoden werden nur als Rumpf angezeigt), transparente Kommentare 1 Kapitel 1 Einleitung • (Graphische) Darstellung von Klassen, Methoden, Attributen und ihren Beziehungen zueinander • Quellcodeverwaltung und Versionskontrollsysteme (CVS, RVS, SVN, ..) • Debugger • Automatische Erzeugung von Quellcode für bestimmte Aufgabenbereiche (z.B. Parser- und Lexergeneratoren) • Dialogeditoren • Metriken Metriken bilden Software auf einen Zahlenwert ab. Beispiel: Lines of Code (LOC) ist die Anzahl der Programmzeilen • Profiler • ... Einige dieser Hilfen sind graphischer Natur und viele andere könnten graphisch visualisiert werden. So könnte beispielsweise die Metrik LOC visualisiert werden durch ein Balkendiagramm, in dem für jede Datei oder jede Programmkomponente angegeben wird, aus wie vielen Codezeilen es besteht. Das Programm Manhatten ([W3-Manhatt]) erzeugt aus Sourcecode eine virtuelle dreidimensionale Stadt, in der jede Methode durch ein Hochhaus dargestellt wird. Die Anzahl der Programmzeilen der Methode bestimmt die Höhe des Hauses und die Menge an Kommentaren wird durch die grünliche Färbung der Rasenfläche um die Häuser dargestellt. Diesen Weg wollen wir hier jedoch nicht beschreiten. In der vorliegenden Arbeit sollen nur Möglichkeiten untersucht werden, bei denen der Programmcode graphisch dargestellt wird und bei denen eine Änderung in der graphischen Darstellung auch zu einem veränderten Sourcecode führt. Dies kann man als graphisches Programmieren bezeichnen. Die Kernfrage, die in dieser Arbeit untersucht werden soll, ist, ob graphisch orientierte Tools die Programmierung wesentlich beschleunigen können. Dabei wird hauptsächlich auf eine – wenn möglich automatische – Visualisierung der vernetzten Strukturen von Klassen und Methoden Wert gelegt. 2 1.1 Software Engineering 1.1 Software Engineering Um Sourcecode graphisch darstellen zu können, muss er zuerst verarbeitet werden. Aus mehreren simplen Textdateien werden so die Konstruktionselemente gewonnen. Dies sind Klassen mit Attributen und Methoden sowie Methodenkörper, die aus einfachen Anweisungen und Kontrollstrukturen bestehen. Weiterhin werden die Beziehungen zwischen diesen Elementen gesucht. Dieses Verfahren wird Reverse Engineering genannt. Das Gegenteil ist das Forward Engineering, bei dem aus den Konstruktionselementen wieder ein Sourcecode entsteht. Nimmt man beide Verfahren zusammen, so erhält man die Möglichkeit, aus Sourcecode Konstruktionselemente zu erhalten, zu verändern und wieder in Sourcecode zu überführen. Dies wird Roundtrip Engineering oder Re-Engineering genannt. Werden keine Veränderungen vorgenommen, so sollte man im Idealfall den ursprünglichen Sourcecode wieder erhalten. 1.2 Refaktorisierung Ist das Reverse Engineering abgeschlossen, kann mit den Konstruktionselementen gearbeitet werden. So ist auch Refaktorisierung möglich. Diese Bezeichnung (engl.: Refactoring) wurde erstmals 1990 von William Opdyke und Ralph Johnson ([OpJo90]) benutzt und beschreibt das Umstrukturieren von vorhandenem Sourcecode ohne dessen Funktionalität zu beeinträchtigen. Die Semantik bleibt also erhalten, aber Lesbarkeit und Struktur des Sourcecodes wird verbessert. Refaktorisierung beinhaltet u.a.: • Umbenennen Vorhandenen Klassen, Attributen oder Methoden einen neuen Namen geben • Attribute kapseln Es werden neue Methoden eingeführt, so dass ein Zugriff auf Attribute nur über diese Methoden möglich ist • Methode extrahieren Ein markierter Teil einer Methode wird extrahiert und als neue separate Methode angelegt • Reihenfolge ändern Das Ändern der Reihenfolge der Parameter einer Methode oder der Reihenfolge der Methoden, in der sie im Sourcecode auftauchen 3 1.2 Refaktorisierung • Schnittstelle extrahieren Anhand einer gegebenen Klasse CGeg wird eine neue Klasse CNeu erstellt, so dass einige der Methoden von CGeg in CNeu als abstrakte Methoden definiert werden. Zusätzlich wird eine Generalisierung eingeführt, so dass CNeu eine Generalisierung von CGeg darstellt. • Subklasse extrahieren Entsprechend der Extraktion einer Schnittstelle kann auch Funktionalität in eine neue Subklasse ausgelagert werden. • Variable für einen Ausdruck anlegen Wird in einem Codefragment mehrmals ein langer oder jedes mal neu auszuwertender Ausdruck benutzt, so kann eine neue Variable eingeführt werden, die am Anfang den Wert dieses Ausdrucks annimmt. Jedes Vorkommen des Ausdrucks im Codefragment wird durch die Variable ersetzt. 1.3 Sichten auf ein Software-System Ein Software-System kann aus verschiedenen Perspektiven und für unterschiedliche Aufgabenbereiche betrachtet werden. Dabei werden individuelle Aspekte des Systems hervorgehoben und bestimmte Details oder Programmteile unterdrückt. Diese Ansichten können sein: • Überblick über das Gesamtsystem Hier ist kein Wissen über einzelne Bereiche oder Implementierungen nötig, sondern es werden einzelne Module und Komponenten betrachtet. • Zusammenspiel einzelner Komponenten Mehrere Gruppen von Programmierern arbeiten an einzelnen Modulen bzw. Bereichen; das Zusammenspiel dieser Komponenten muss funktionieren. Hier sind Schnittstellen von besonderem Interesse. • Neuer Programmierer oder alter Code Oft ist nur minimale oder keine Dokumentation vorhanden, um sich mit einem neuen Programm oder einem Codefragment, das lange nicht mehr weiterentwickelt wurde, vertraut zu machen. Automatisch generierte Informationen mit hohen Detailgrad über vorhandenen Sourcecode ist von Nutzen. • Programmierung Die Unterstützung der Programmierung einzelner Methoden durch 4 1.3 Sichten auf ein Software-System Visualisierung von Programmcode erleichtert den verschachtelte Schleifen und bedingte Ausführungen. 1.4 Überblick über Aufbau der Arbeit Im zweiten Kapitel wird der momentane Standard zur Darstellung von SoftwareSystemen vorgestellt: die Unified Modelling Language UML. Es werden die geschichtlichen Hintergründe dieser Notation sowie ein Überblick über die verschiedenen Diagramme gegeben. Das dritte Kapitel beschäftigt sich mit dem Stand der Technik. Dazu werden mehrere konkrete Programme auf ihre graphische Darstellung und die Möglichkeiten zur Modifikation von Sourcecode getestet. Im vierten Kapitel wird ein eigenes Konzept erarbeitet. Dabei werden die Schwachstellen der getesteten Programme berücksichtigt und auf die spezifische Aufgabenstellung angepasst. Im fünften Kapitel werden Möglichkeiten zur Implementierung und der konkreten Umsetzung beschrieben Den Abschluss bildet das sechste Kapitel mit einer Bewertung des Konzepts sowie der Implementierung. 5 Kapitel 2 Graphische Darstellungsmöglichkeiten Möglichkeiten zur graphischen Darstellung von Programmcode gibt es schon sehr lange. Dazu zählen einzelne Diagramme genau wie strukturierte Methoden, die den kompletten Entwicklungsprozess beschreiben und einzelne Phasen dieses Prozesses in Form von Diagrammen graphisch darstellen. Nach dem Entstehen der ersten objektorientierten Programmiersprache (Simula-67 aus dem Jahr 1967 gilt als erste objektorientierte Programmiersprache) wurden auch Methoden und Notationen für objektorientierte Systeme entwickelt. 2.1 Objektorientierte Analyse- und Designmethoden Seit den späten 80er Jahren macht man sich Gedanken darüber, wie man bei der Entwicklung komplexer objektorientierter Softwaresysteme vorgehen soll. Über die Jahre hinweg wurden so mehr als 50 Methoden beschrieben (weshalb diese Zeit oft als Methodenkrieg bezeichnet wird). Dabei wird üblicherweise der komplette Entwicklungsprozess beschrieben und in einzelne Phasen unterteilt. Es werden Tätigkeiten definiert und Zeitangaben für einzelne Phasen gegeben. Es werden hauptsächlich zwei Schritte unterschieden: Analyse und Design. Während der Analyse wird untersucht, was erstellt werden soll, und beim Design wird entschieden, wie dies umzusetzen ist. Zur Dokumentation und Visualisierung der einzelnen Phasen und des Programmcodes werden verschiedene textuelle und graphische Darstellungsformen (Notationen) vorgegeben. Die Methoden sind nicht alle unabhängig voneinander entstanden. Es gab immer wieder Beeinflussungen von anderen Methoden, die nicht notwendigerweise objektorientiert sein mussten, und es gab Bemühungen, die Vorteile der verschiedenen Methoden zu vereinigen. Ein solcher Versuch ist mit UML gelungen und manche Methoden benutzen UML als Notation (z.B. UP (RUP), Team Fusion, Catalysis, ...), weshalb im Folgenden nur eine Auflistung einiger Methoden erfolgen soll. 6 2.1 Objektorientierte Analyse- und Designmethoden Abk. Name und Homepage Autor Jahr Information BON Business Object Notation (http://www.bon-method.com) Nerson, Walden 1992 [WaNe95] Booch Booch (Object-Oriented Analysis Booch (OOAD) and Design) (zwei Versionen: Booch'91 und Booch'93) 1991+ [Booch91] 1993 [Booch94] Catalysis Catalysis (http://www.catalysis.org) D'Souza, Wills 1999 [HeEd94a] CRC Class-ResponsibilityCollaboration Wilkinson 1989 [BeCu89] [WiNa95] EROOS Entity-Relationship Based Object-Oriented Specifications Lewi, 1990 Steegmans, Van Baelen [Lewi90] Firesmith [Fire93] Firesmith Firesmith 1993 Fusion Fusion (später Team Fusion unter Coleman Benutzung von UML) 1994+ [Cole94] 1997 HOOD Hierarchical Object-Oriented Design Robinson 1992 HOORA Hierarchical Object Oriented Requirements Analysis (http://www.hoora.org) European Space Agency (ESA) 1995 [Robi92] MOSES Methodology for Object-Oriented Henderson- 1994 Software Engineering of Systems Sellers, Edwards [HeEd94b] OBA Object Behaviour Analysis Rubin, Goldberg 1992 [RuGo92] OMT Object Modelling Technique (zwei Versionen: OMT und OMT94) Rumbaugh u.a. 1991 [Rumb91] OOA / OOD Object-Oriented Analysis / Object-Oriented Design Coad, Yourdon 1991 [CoYo91a] [CoYo91b] OOCM Object-Oriented Conceptual Modeling Dillon, Tan 1993 [DiTa93] OODLE Object-Oriented Design Language Shlaer, Mellor [ShMe92] 1992 7 2.1 Objektorientierte Analyse- und Designmethoden Abk. Name und Homepage Autor Jahr Information OOSA Object-Oriented System Analysis Shlaer, Mellor 1988 [ShMe88] OOSC Object-Oriented Software Construction Meyer 1994 [Meyer97] OOSD Object-Oriented System Development Champeaux, 1993 Lea, Faure [ChLeFa93] OOSE Object Oriented Software Engineering(zwei Versionen: OOSE und OOSE'94) Jacobson [Jaco92] OPEN (OML) Object-Oriented Process, Environment and Notation (OPEN Modelling Language) (http://www.open.org.au) Henderson- 1996 Sellers, Graham u.a. [Hend96] RDD Responsibility Driven Design (DOOS) (Designing Object-Oriented Software) Wirfs-Brock 1990 [Wirf90] ROOM Real-Time Object-Oriented Modeling Selic, Gullekson, Ward 1994 [SeGuWa94] RUP Rational Unified Process (Unified Jacobson, Process angepasst für Rational Booch, Software) Rumbaugh 1998 [Kruch98] SOMA Semantic Object Modelling Approach Graham 1995 [Graham95] Sully Sully Sully 1993 [Sully93] Syntropy Syntropy Cook, Daniels 1994 [Cook94] UP Unified Process Jacobson, Booch, Rumbaugh 1998 [Kruch98] XP Extreme Programming Beck (www.extremeprogramming.org) 1999 [Beck99] 1992 8 2.1 Objektorientierte Analyse- und Designmethoden OOSA CRC RDD EROOS 1990 OOA / OOD Booch '91 OMT OBA HOOD OOSE OOCM Sully Booch '93 Fusion BON OOSD OODLE Firesmith OOSE 94 OMT 94 Syntropy ROOM MOSES OOSC Unified Method 0.8 SOMA HOORA 1995 UML 0.9 & 0.91 OPEN / OML UML 1.0 Team Fusion UML 1.1 UP (RUP) UML 1.2 UML 1.3 Catalysis XP 2000 UML 1.4 UML 1.5 UML 2.0 2004 Abbildung 1: Objektorientierte Methoden und Notationen im historischen Kontext 9 2.2 UML 2.2 UML Die Unified Modeling Language UML ist keine Methode, sondern eine graphische Notation zur Visualisierung, Dokumentation, Konstruktion und Spezifikation von Software-Systemen. Sie wurde standardisiert von der Object Management Group OMG ([W3-OMG]). Dieses 1989 von elf Gesellschaften gegründete Nonfor-Profit Konsortium besteht mittlerweile aus etwa 800 Mitgliedern mit dem Ziel, die Model Driven Architecture MDA zu etablieren und weiter zu entwickeln. Mit OMG's MDA wird Software plattformneutral und auf einer hohen abstrakten Ebene modelliert, um dann mit Hilfe von Generatoren Quellcode erzeugen zu können. Dadurch soll die Softwarequalität und Handhabbarkeit verbessert, die Wartbarkeit vereinfacht und die Entwicklungsgeschwindigkeit gesteigert werden. Die dazu benutzten Standards sind u.a. UML, Meta-Object Facility (MOF) und XML Meta-Data Interchange (XMI). UML wird unterstützt von namhaften Firmen wie HP, IBM, Microsoft und Oracle. Sie wird seit dem Beginn im Oktober 1994 kontinuierlich weiterentwickelt und ist mittlerweile ein weltweit anerkannter Standard. Detaillierte Informationen und Spezifikationen können unter [W3-UML] nachgesehen werden. Eine Übersicht über die UML-Notation und somit eine gute Referenz wird von [W3-OOSE] zum Download angeboten (dort ist ebenfalls ein Becher mit aufgedruckter UML-Referenz bestellbar; eine Übersetzung ins klingonische ist in Vorbereitung). Eine ausführliche Einführung in das Metamodell, Super- und Infrastructure kann in [SofDevUML2] nachgelesen werden. Einige Beispiele sind [UML2Toolkit] und [Jeckle04] entnommen. Eine Liste mit deutschen Übersetzungen kann unter [W3-JECKLE] eingesehen werden. 10 2.2 UML 2.2.1 Geschichte UML wurde maßgeblich von den drei Autoren James Rumbaugh, Grady Booch und Ivar Jacobson entwickelt, deren Methoden sich großer Beliebtheit erfreuten. Als im Oktober 1994 Rumbaugh zu Rational Software Corporation wechselte, begann er mit dem dort beschäftigten Booch an einer Vereinheitlichung ihrer Methoden OMT und Booch zu arbeiten. Ein erster Entwurf wurde unter dem Titel Unified Abbildung 2: Die drei Amigos: Grady Booch, James Rumbaugh, Ivar Jacobsen (v.li.n.re.) Method 0.8 im Oktober 1995 auf der OOPSLA veröffentlicht. Etwa zum selben Zeitpunkt wechselte Ivar Jacobsen zu Rational und seine Methode OOSE wurde integriert. Dieses Trio – bekannt unter dem Namen „Die drei Amigos“ – veröffentlichten das Ergebnis ihrer Arbeit unter dem inzwischen geänderten Titel Unified Modeling Language in den Versionen 0.9 im Juni 1996 und 0.9.1 im Oktober 1996. Im Januar 1997 wurde die UML Version 1.0 der OMG zur Standardisierung eingereicht als Antwort auf ihren RFP (Request for Proposal) für eine standardisierte Modellierungssprache. Nach der Kombination mit anderen Vorlagen entstand die Version 1.1, die im Juli der OMG offeriert, im September von OMG Analysis and Design Task Force (ADTF) und OMG Architecture Board akzeptiert und schließlich im November 1997 als Standard anerkannt wurde. In den folgenden Jahren erschienen weitere Versionen mit kleinen Änderungen. Aktuelle Version ist UML 1.5. UML 2.0 mit größeren Änderungen ist für Ende 2004 vorgesehen. Da sich diese Version im letzten Schritt des Standardisierungsprozesses befindet, sind keine technischen Änderungen mehr zu erwarten, weshalb die Änderungen bereits in einigen Programmen umgesetzt wurden und auch hier betrachtet werden. 11 2.2 UML 2.2.2 2.2.2.1 Überblick Die Four Layer Modeling Architecture Eine Klasse beschreibt eine Menge gleicher Objekte. Die Klasse definiert dabei das allgemeine Aussehen, d.h. die Eigenschaften in Form der Attribute und das Verhalten in Form der Methoden. Eine Instanz einer Klasse ist das Objekt, das spezifische Werte hat. So kann beispielsweise die Klasse Mensch definiert werden mit einer Zahl als Attribut, die das Alter angibt, und einer Methode, die aus diesem Alter und dem aktuellen Datum die Volljährigkeit ermittelt. Ein konkretes Objekt – also eine Instanz dieser Klasse – ist dann z.B. Paul mit einem Alter von 25 Jahren. Somit ist die Klasse ein Modell von mehreren unterschiedlichen Objekten. Die Klasse gibt eindeutig vor, wie die Objekte aussehen und was sie können. Benutzt man dieses Konzept und abstrahiert weiter, dann erhält man ein Metamodell. Dieses Metamodell beschreibt eindeutig, wie eine Klasse auszusehen hat. Es definiert also, dass eine Klasse Attribute und Methoden hat und dass sie Superklassen/Subklassen durch Vererbung haben kann. Ein Element dieses Metamodells ist Classifier und eine Instanz dieses Elements ist beispielsweise eine Klasse. UML ist ein solches Metamodell. „Meta“ bedeutet in diesem Zusammenhang, das es sich um ein Modell handelt, mit dem man Modelle beschreiben kann. Tatsächlich kann UML mit UML erklärt werden. Nun ist UML nicht nur für eine bestimmte oder eine kleine Gruppe von Programmiersprachen entworfen worden. Es muss also möglich sein, das Metamodell anzupassen. Die Fähigkeit der Mehrfachvererbung beispielsweise ist nicht in allen Sprachen gegeben. In C++ können Klassen von beliebig vielen anderen Klassen erben, in Java ist nur eine Einfachvererbung möglich. Um festzulegen, wie das Metamodell geändert werden darf, wurde eine weitere Schicht eingeführt: das Meta-Metamodell. Auch hier kommt dasselbe Konzept zum Einsatz. Das Meta-Metamodell beschreibt das Metamodell. Instanzen der Elemente aus dem Meta-Metamodell beschreiben ein Metamodell. Natürlich könnte man an dieser Stelle noch weiter abstrahieren und würde ein Meta-Meta-Metamodell usw. erhalten. Nach langen Diskussionen hat man jedoch eingesehen, dass weitere Metaschichten keinen zusätzlichen Abstraktionsgewinn bringen. Somit haben wir insgesamt vier Schichten oder Ebenen. Dies wird VierSchichten-Modellierungsarchitektur oder Four Layer Modeling Architecture 12 2.2 UML genannt. Die einzelnen Schichten werden durchnummeriert mit M0 bis M3, wobei das Meta-Metamodell die Schicht M3 darstellt. Zur Beschreibung des Meta-Metamodells kann ein weiterer Standard der OMG benutzt werden. Die Meta-Objects Facility MOF, die auch zur Definition anderer Technologien wie die Komponenten- und Schnittstellenbeschreibungssprache CORBA IDL (Common Object Request Broker Interface Definition Language) eingesetzt wird. MOF definiert das MOF-Modell, das ein Meta-Metamodell darstellt. Instanzen der Elemente des MOF-Modells sind Elemente eines Metamodells. Um einen standardisierten Austausch von Daten zwischen unterschiedlichen Programmen zu ermöglichen wurde XMI (XML Metadata Interchange) ins Leben gerufen. XMI beschreibt, wie Instanzen der Elemente des MOF-Modells auf XML Definitionen abgebildet werden können. Somit ist ein Austausch von UML-Modellen möglich. Auch XMI ist ein Standard der OMG. Meta-Metamodell (M3 = MOF) Classifier «instanceOf» Metamodell (M2 = UML) Attribut Classifier «instanceOf» Modell (M1) «instanceOf» «instanceOf» Mensch + Alter : Integer «instanceOf» Objekte (M0) Paul : Mensch Alter = 25 Abbildung 3: Four Layer Modeling Architecture von UML 13 2.2 UML 2.2.2.2 Infrastructure und Superstructure Zwei der Vier Schichten der Four Layer Modeling Architecture bedürfen der Standardisierung und finden sich tatsächlich auch in zwei unterschiedlichen Dokumenten wieder. Die UML Infrastructure erklärt das Meta-Metamodell und die UML Superstructure das Metamodell. Die UML Infrastructure definiert Modellelementen und Unterpaketen: vier Pakete mit unterschiedlichen 1. PrimitiveTypes: vordefinierte Datentypen Integer, Boolean, String und UnlimitedNatural (natürliche Zahlen und der Asterisk ('*') für unendlich) 2. Basic: Klasse, Attribut, Operation, Paket 3. Abstractions: abstrakte Metaklassen, die zur weiteren Spezialisierung gedacht sind und voraussichtlich in vielen Metamodellen benutzt werden: Generalisierung, Instanz, Multiplizität, Sichtbarkeit, Einschränkungen (Constraints) u.a. 4. Constructs: Assoziation Diese vier Pakete werden zusammengefasst in einem Paket namens Core und bilden somit die Grundlage für die UML Infrastructure. Core PrimitiveTypes Abstractions Basic Constructs Abbildung 4: Core Pakete der UML Infrastructure 14 2.2 UML Die UML Superstructure beschreibt das Metamodell, also UML selbst. Dabei werden die Elemente aus der Infrastructure übernommen, d.h. alle Elemente der Superstructure sind Instanzen der Infrastructure. Weiterhin werden alle Elemente der Superstructure importiert. Sie stehen also auch im Metamodell zur Verfügung. Das zentrale Paket hier wird als Kernel bezeichnet. 2.2.2.3 Erweiterungsmöglichkeiten UML bietet mehrere Möglichkeiten zur Erweiterung und auch zur Einschränkung unterschiedlicher Elemente von UML. Dies sind Constraints (Einschränkungen oder Zusicherungen), Tagged Values (Eigenschaftswerte) und Stereotypen. Benutzt man eine oder mehrere dieser Erweiterungen, so erhält man ein sogenanntes Profile, das auch mittels XMI weitergegeben werden kann. Stereotypen sind Namen, die in Guillemets eingefasst sind ( «Stereotyp-Name» ) und können auf unterschiedliche Modellelemente angewendet werden. Viele vordefinierte Stereotypen sind bereits in UML enthalten, z.B. «class», das eine Klasse beschreibt. Zusätzlich kann ein Icon, also eine graphische Repräsentation angegeben werden. Es können Stereotyp oder Icon oder beides angezeigt werden. Ein Beispiel für vordefinierte Stereotypen und Icons sind Control, Boundary und Entity. Sie basieren auf dem Model-View-Controller Modell (MVC-Modell), Abbildung 5: Icons für die mit dem die in Anwendungen oft benutzte Trennung Stereotypen Control, Entity, Boundary in Eingabe, Verarbeitung und Ausgabe auf GUIbasierte Systeme übertragen wird. Die Eingabe entspricht dabei der MVCKomponente Controller und dem UML-Stereotyp «control». Die Verarbeitung wird von der MVC-Komponente Model und dem UML-Stereotyp «entity» übernommen, während für die Ausgabe die MVC-Komponente View und dem UML-Stereotyp «boundary» verantwortlich ist und üblicherweise durch Fenster, Dialoge oder Kommunikationsklassen wie TCP/IP realisiert werden. Tagged Values bestehen aus einem Namen und einem zugehörigen Wert. Dadurch können die unterschiedlichsten Informationen untergebracht werden, wie den Status der Entwicklung, Informationen über Autor und Zweck. Sie werden in geschweifte Klammern gefasst, beispielsweise {Autor = „Paul“} oder {Status = „muss noch gemacht werden“, Fertigstellung = „2004“}. Diese Informationen tauchen üblicherweise nicht im fertigen Produkt auf, sondern werden nur im Modell angezeigt. Ist der zugehörige Wert des Tagged values vom Typ Boolean und true, so muss der Wert nicht angezeigt werden (Beispiel: {abstract}). 15 2.2 UML Constraints sind Einschränkung der Benutzung oder Seniorengruppe Bedeutung von Elementen. Dies soll an einem Beispiel verdeutlicht werden: Seien zwei Klassen namens {Person.alter > 60} „Person“ (mit Attribut „Alter“) und „Seniorengruppe“ Person gegeben sowie eine Beziehung zwischen diesen Klassen. Will man sicherstellen, dass nur Personen ab alter : Integer 60 Jahren einer Seniorengruppe zugewiesen werden Abbildung 6: Beispiel für können, dann kann das realisiert werden mit einem Constraints Constraint, das der Beziehung zugewiesen wird. Dieses hat die Form {person.alter > 60}. Üblicherweise werden Constraints in einer speziell für diesen Zweck ausgelegten Sprache – der Object Constaint Language OCL – verfasst. Aber auch andere Sprachen sind möglich. 2.2.3 Diagramme und Views UML 2.0 definiert mehrere Diagramme, die in die zwei Bereiche Struktur und Verhalten eingeteilt werden können: Strukturdiagramme (Structural Diagrams) • Klassendiagramm (Class Diagram) Zeigt Klassen mit ihren Attributen und Operationen sowie die Beziehungen untereinander. • Objektdiagramm (Object Diagram) oder Instanzdiagramm (Instance Diagram) Ähnlich dem Klassendiagramm werden hier Objekte mit spezifischen Werten dargestellt. Dies ist nur eine Momentaufnahme des Systems. • Komponentendiagramm (Component Diagram) Mit dem Komponentendiagramm werden Komponenten und ihre Beziehungen untereinander angezeigt. Komponenten sind hierbei modulare und austauschbare Teile des Systems mit eindeutig definierter Schnittstelle. • Verteilungsdiagramm (Deployment Diagram) Zeigt auf, wie das System auf unterschiedliche Computer verteilt ist und wie diese miteinander kommunizieren. • Kompositionsstrukturdiagramm (Composite Structure Diagram) Beschreibt die interne Struktur einer Klasse oder Komponente. 16 2.2 UML • Paketdiagramm (Package Diagram) In einem Paketdiagramm können verschiedene Modellelemente gruppiert werden. Dabei wird ein eindeutiger Namensraum festgelegt. Verhaltensdiagramme (Behavior Diagrams) • Anwendungsfalldiagramm (Use Case Diagram) Das Systemverhalten wird aus der Sicht des Anwenders dargestellt. Es wird nicht gezeigt, wie, sondern was möglich ist. • Aktivitätsdiagramm (Activity Diagram) Dient der Darstellung von Geschäftsprozessen wie sie Anwendungsfällen vorkommen, oder um komplexe Logik modellieren. in zu • Zustandsdiagramm (State Machine Diagram) Das Zustandsdiagramm beschreibt Zustände und Zustandsübergänge eines Objekts – ähnlich einem endlichen Automaten. • Interaktionsdiagramme (Interaction Diagrams) • Sequenzdiagramm (Sequence Diagram) Zeigen die sequentielle Logik, wobei in der Regel nur ein Weg durch einen Entscheidungsbaum angegeben wird. Es werden die Nachrichten zwischen verschiedenen Objekten in Abhängigkeit von der Zeit angegeben. • Kommunikationsdiagramm (Communication Diagram) Dieses Diagramm beschreibt den Nachrichtenaustausch zwischen Objekten, wobei – im Gegensatz zum Sequenzdiagramm – die interne Struktur im Vordergrund steht. • Interaktionsübersichtsdiagramm (Interaction Overview Diagram) Kombination aus Aktivitäts- und Interaktionsdiagrammen, wobei die einzelnen Aktivitäten Interaktionen sind. • Zeitdiagramm (Timingdiagramm) Das aus der technischen Informatik bekannte Timingdiagramm beschreibt Zustandsänderungen in Abhängigkeit von der Zeit und als Reaktion auf externe Ereignisse. 17 2.2 UML Ein Diagramm besteht aus mehreren Modellelementen, cd Klassen die zur besseren Übersicht innerhalb eines Rechtecks (frame) dargestellt werden können. In diesem Frame Ein Kommentar kann in der linken oberen Ecke Art, Name und Parameter des Diagramms angezeigt werden. Die Art Abbildung 7: UML wird durch Ausschreiben des Diagrammtyps oder durch Diagramm mit Kommentar eine Abkürzung kenntlich gemacht (z.B. pkg für package, sm für state machine, sd für sequence diagramm). Viele Modellelemente können in mehreren Diagrammarten vorkommen, beispielsweise der Kommentar, der durch ein Rechteck mit umgeknickter rechter oberer Ecke dargestellt wird. Kommentare können in allen Diagrammarten vorkommen und können sowohl alleine stehen als auch über eine gestrichelte Linie mit einem anderen Modellelement verbunden werden. Große Softwaresysteme können aus mehreren hundert oder sogar tausend Klassen bestehen. Sie alle in einem einzigen Klassendiagramm darzustellen, wäre nicht sehr übersichtlich. Diese Vorgehensweise war aber auch nicht von den Entwicklern der UML beabsichtigt. Vielmehr sollten mehrere kleine Diagramme erzeugt werden, um einzelne Komponenten des Systems zu beschreiben. Dabei ist auch das Zusammenspiel unterschiedlicher Diagrammarten von großer Bedeutung. Um ein großes Softwaresystem aus unterschiedlichen Blickwinkeln zu betrachten, werden sogenannte Views (Sichten) benutzt. Dies sind Ansichten spezifischer Aspekte, die meist aus mehreren kleinen und unterschiedlichen Diagrammen bestehen. Oft benutzte Sichten sind: • Anwendungsfallsicht (use case view): Zeigt die Funktionalität aus (Anwendungsfalldiagramm) der Sicht des Anwenders • Entwurfssicht (design view oder logical view): Dient der Darstellung der statischen und dynamischen Struktur des zu entwickelnden Softwaresystems (Klassen-, Objekt-, Zustands-, Aktivitäts- und Kollaborationsdiagramm) • Prozesssicht (process view): Benutzt die selben Diagramme wie die Entwurfssicht, jedoch stehen Aspekte wie Nebenläufigkeit, parallele Ausführung und die Synchronisation von Prozessen im Vordergrund. Diese Sicht wird manchmal auch concurrency view genannt. 18 2.2 UML • Implementierungssicht (implementation view) Zeigt die Organisation des Quellcodes (Komponenten- und Model Management Diagramme) • Verteilungssicht (deployment view): Beschreibt die Verteilung auf (Verteilungsdiagramm) unterschiedliche Computer Im Folgenden werden einige Modellelemente und Diagramme aufgezeigt, wobei der Schwerpunkt auf der Darstellung von Klassen und den Beziehungen zwischen Klassen liegt. Dabei ist zu beachten, dass in UML Farbe nicht semantisch benutzt wird; es werden lediglich Empfehlungen gegeben, um Unterschiede zwischen Elementen farblich hervorzuheben. Die Umsetzung bleibt aber dem Benutzer bzw. dem Hersteller von UML-konformer Software überlassen. Die UML-Spezifikation definiert im Wesentlichen einzelne Modellelemente. Diese können größtenteils in unterschiedlichen Diagrammen vorkommen. 2.2.3.1 Klassen- und Objektdiagramm Das Class Diagram oder Klassendiagramm beschreibt Klassen mit Attributen und Methoden sowie ihre Beziehungen untereinander. Eine Klasse wird dargestellt als Rechteck, das mittels horizontaler Linien in mehrere Bereiche unterteilt werden kann. Im ersten Bereich steht optional ein Klassen-Stereotyp, gefolgt vom Namen der Klasse (fett gedruckt) sowie optional Eigenschaften, die in geschweifte Klammern gefasst werden. Ist die Klasse abstrakt, so wird der Name in kursiver Schrift geschrieben und die Eigenschaft {abstract} hinzugefügt. Vor dem Klassennamen kann der Name eines Pakets gefolgt von zwei Doppelpunkten stehen. Der zweite Bereich listet die Attribute der Klasse auf, die in folgender Syntax verfasst sind: [visibility] [/] name [: type] [multiplicity] [= default] [{ property-string }] Zu Beginn steht ein Symbol für die Sichtbarkeit: “-“ für private, “+“ für public, “#“ für protected und “~“ für package. Der Schrägstrich wird bei abgeleiteten 19 2.2 UML Attributen gesetzt, d.h. bei Attributen, die zur Laufzeit aus anderen Attributen ermittelt werden können. Der anschließende Name des Attributs wird gefolgt von Typ, Multiplizität sowie einem Initialwert. Zusätzliche Eigenschaften wie {readOnly} oder auch die Angabe eines Wertebereichs werden in geschweiften Klammern festgehalten. Attribute, die als Typ eine Klasse haben, können auch durch Beziehungen visualisiert werden (siehe Aggregation). Im nächsten Bereich werden die Operationen bzw. Methoden dargestellt. Die Syntax ähnelt der der Attribute: [visibility] name ( [parameter-list] ) [: return-type] [{ property-string }] Ist die Methode abstrakt, so wird der Name entweder kursiv geschrieben oder unterstrichen. Zusätzlich kann die Eigenschaft {abstract} hinzugefügt werden. Die Parameterliste ist eine Komma separierte Liste von formalen Parametern und genügt folgender Syntax: [direction] name : type [multiplicity] [= default] [{ property-string }] direction gibt die Richtung des Parameters an. Fehlt diese Angabe, wird “in“ als Standardwert angenommen. Zulässige Werte sind “in“, “out“ und “inout“. Sowohl Attribute als auch Operationen können anhand der Sichtbarkeit gruppiert werden. Zusätzliche Bereiche mit benutzerdefinierten Bemerkungen oder Einschränkungen können folgen, jedoch sind alle Bereiche außer dem ersten optional. Objekte werden auf ähnliche Weise dargestellt. Im Namensbereich wird unterstrichen der Name des Objekts gefolgt von einem Doppelpunkt und dem Klassennamen angegeben. Anstelle der Attributsdefinitionen werden Attributnamen mit konkreten Werten verwendet. Beispiele für unterschiedliche Darstellungsformen von Klassen sind in Abbildung 8 gezeigt. (a) stellt eine Klasse mit Attributnamen und -Typen sowie einer Operation dar. In (b) werden zusätzlich Sichtbarkeit und Initialwerte angezeigt. (c) bietet eine Sortierung der Attribute nach Sichtbarkeit ähnlich der Syntax der Programmiersprache C++. Abbildung (d) und (e) sind Minimaldarstellungen, bei denen nur der Klassennamen angezeigt wird und (e) ist ein Objekt mit konkreten Werten. 20 2.2 UML (a) (b) (c) Window Window Window size: Area visibility: Boolean defaultSize: Area + size: Area = (100,100) # visibility: Boolean = true + defaultSize: Area draw() + draw() public size: Area = (100,100) defaultSize: Area protected visibility: Boolean = true + draw() (d) Window (e) Window (f) Ansicht1 : Window size = (100,100) visibility = true defaultSize = (50,50) Abbildung 8: UML Klassen und ein Objekt Bei den Beziehungen zwischen Klassen und Objekten werden vier Arten unterschieden: Assoziation, Generalisierung, Abhängigkeit und Realisation (Schnittstellen). Eine Assoziation stellt die allgemeinste Art einer Beziehung dar. Sie bedeutet lediglich, dass eine Klasse irgendwie Kenntnis von einer oder mehreren anderen Klassen haben muss. Die graphische Darstellung orientiert sich sehr an dem Entity-Relationship-Modell nach [Chen76], das u.a. bei der Modellierung von Datenbanken eingesetzt wird. Die nachfolgende Abbildung stellt einige Beispiele graphisch dar. Assoziationen werden durch eine durchgezogene Linie dargestellt. Soll die Beziehung nur in einer Richtung möglich sein, so wird die Linie an einem Ende mit einer Pfeilspitze abgeschlossen (a). Für jede der beiden Richtungen kann optional eine Bezeichnung angegeben werden. Die Richtung, für die diese Bezeichnung gilt, wird mit einem ausgefüllten Rechteck, das in die gewünschte Richtung zeigt, visualisiert (b). Diese Bezeichnung ist eine Beschreibung für die Assoziation selbst. Zusätzlich kann man angeben, welche Rolle eine Klasse bei einer Assoziation spielt (c). Weiterhin können Multiplizitäten angegeben werden (d). Diese geben an, wie viele Objekte der einen Sorte mit wie vielen Objekten der anderen Sorte in Verbindung stehen können. Formuliert wird dies als eine Zahl, eine Menge von Zahlen, Zahlenbereiche (z.B. „1..7“) oder eine Kombination davon (Beispiele: „1“ oder „3, 5, 7“ oder „1..7, 11“ ). Ist keine Multiplizität angegeben, so wird „1“ als Default-Wert angenommen. Ist eine Klasse in Assoziation mit sich selbst, so nennt man dies Rekursive Assoziation (e). Sind 21 2.2 UML mehr als zwei Klassen in einer Assoziation involviert (n-äre Assoziation), so werden die Linien der Assoziation mit einer leeren Raute verbunden (f). In einer n-ären Assoziation sind weder Aggregation noch Qualifizierte Assoziation erlaubt. Eine Qualifizierte Assoziation wird bei one-to-many und many-to-many Beziehungen benutzt und stellt eine Art Schlüssel dar, mit dem eindeutig zwischen den verschiedenen Elementen an dem „many-Ende“ der Assoziation unterschieden werden kann. Der Schlüssel bzw. qualifier wird dabei in einem (b) benutzt 1..* 0..* Auto Kind Person wird benutzt von (c) 1 (a) hat Mutter (e) 1..* Zug Vorlesung fährt mit Auto {xor} Person (i) Firma (h) Fahrschein (f) Tag Raum (g) Uni Matrikelnr (d) * Student Abbildung 9: UML Assoziationen kleinen Rechteck dargestellt, das Klasse und Assoziation miteinander verbindet. Dieses Rechteck sollte immer kleiner sein, als die mit ihm verbundene Klasse, um Verwirrung zu vermeiden (Beispiel (g): Eine Universität hat für jeden Studenten eine eindeutige Matrikelnummer). Einer Assoziation kann eine Klasse zugeordnet werden, die dann Assoziationsklasse genannt wird. Sie ist mittels einer gestrichelten Linie mit der Assoziation verbunden und stellt zusätzliche Informationen für diese Beziehung zur Verfügung (h). Eine weitere Möglichkeit für die Modellierung mit Assoziationen stellt das Xor-Constraint zur Verfügung. An dem Namen erkennt man bereits, dass es sich dabei um eine Einschränkung (Constraint) handelt und das Xor weist darauf hin, dass von zwei Möglichkeiten nur genau eine verwendbar ist. Dazu ein Beispiel (i): Wir haben die drei Klassen Auto, Person und Firma. Ein Auto wird zugelassen auf den Namen einer Privatperson oder auf eine Firma, d.h. es gibt Assoziationen zwischen Auto und Person sowie zwischen Auto und Firma. Ein Auto kann aber nicht auf beide zugelassen werden. Die Lösung bietet das Xor-Constraint, das nur genau eine der beiden Assoziationen zulässt. Dargestellt wird es mit einer gestrichelten Linie zwischen den beiden Assoziationen und einer für Constraints in UML üblichen Schreibweise {xor}. 22 2.2 UML Eine Aggregation ist eine spezielle Form der Assoziation, deren beteiligte Klassen eine Ganzes-Teil-Struktur darstellen, d.h. eine Klasse ist Teil einer Klasse. Dabei kann das Teil auch ohne das Ganze existieren (Beispiel (a): Auto aggregiert Reifen). Eine weitere Spezialisierung dieses Konzepts ist die Komposition, bei der das Teil nicht ohne das Ganze existieren kann (Beispiel: Fenster aggregiert Icon und Button (b), Haus aggregiert Zimmer). Dargestellt werden diese beiden Assoziationen durch eine Raute, die im Fall der Aggregation leer (a) und im Fall der Komposition ausgefüllt (b) ist. Als besondere Form von Assoziation können Aggregation und Komposition die von Assoziationen bekannten Elemente Rolle, Multiplizität und qualifier haben. Ist die aggregierte Klasse Teil von mehreren aggregierenden Klassen, so nennt man die Aggregation shared (Beispiel (c): Ein Team besteht aus Personen, aber eine Person kann auch Mitglied in mehreren Teams sein). Visualisiert wird dies durch die Angabe einer Multiplizität ungleich eins, die auf der aggregierenden Seite steht – also auf der Seite, an der die Raute dargestellt wird. Sind mehrere Klassen auf der „Teil“-Seite der Beziehung, dann können diese zusammengefasst und in einer Baumstruktur dargestellt werden (shared target style (d), im Gegensatz zu separate target style (b)). Soll auf die Visualisierung mit Hilfe von Assoziationen verzichtet werden, kann die Darstellung auch mit Attributen erfolgen ((e) mit Komposition, (f) mit Attribut). (a) hat Auto (c) * Team (b) (e) 4 Reifen Mitglied Window * Button Person (d) * Icon size Window 1 (f) Window * Button Area Window size: Area * Icon Abbildung 10: UML Aggregation und Komposition Die Generalisierung ist eine direkte Abbildung des Konzepts aus der objektorientierten Programmierung. Eine spezielle Klasse (Subklasse oder Unterklasse) erbt dabei alle Attribute, Operationen und Assoziationen von einer allgemeineren Klasse (Superklasse oder Oberklasse). Dieses Konzept kann nicht 23 2.2 UML Person (a) Beruf {incomplete, overlapping} (c) Geschlecht {complete, disjoint} (b) Mann Frau Mechaniker Lehrer Vehikel Landvehikel {incomplete, overlapping} Auto (e) (d) Wasservehikel {incomplete, overlapping} Segelboot Motorboot Amphibienfahrzeug Abbildung 11: UML Generalisierung auf Objekte übertragen werden und tritt deshalb im Objektdiagramm nicht auf. Generalisierung wird dargestellt durch eine durchgezogene Linie zwischen Superklasse und Subklasse mit einem leeren Dreieck auf der Seite der Superklasse (a). Wie bei Aggregation kann bei mehreren Subklassen die Darstellung in einer Baumstruktur erfolgen (b) oder sie kann durch eine gestrichelte Linie kenntlich gemacht werden (c). Auch eine Einteilung in mehrere logische Gruppen (Partitionen) ist möglich (Generalization Set, Beispiel (d): Landvehikel und Wasservehikel). Die Beschreibung der Gruppe wurde in früheren UML-Versionen Discriminator (Unterscheidungsmerkmal) genannt und wird an die zugehörige Generalisierung geschrieben. Es gibt vier Constraints, von denen je zwei gegensätzlich sind und nicht zusammen benutzt werden können: complete / incomplete und disjoint / overlapping. Complete (vollständig) bedeutet, dass keine weiteren Subklassen mehr hinzugefügt werden können (Beispiel (b): es gibt nur die Unterscheidung zwischen Mann und Frau, weitere Unterscheidungen wären nicht sinnvoll). Bei incomplete (unvollständig) können noch zusätzliche Subklassen modelliert werden. Die anderen beiden Constraints betreffen die Subklassen der Subklassen in der Vererbungshierarchie, wenn Mehrfachvererbung möglich ist. Eine Subklasse der Subklassen kann von allen Subklassen erben, die mit overlapping (überlappend) gekennzeichnet sind, während dies bei disjoint (trennen) nicht möglich ist. Dazu ein Beispiel: Sei Vehikel eine generalisierte Klasse der Klassen Auto und Motorboot, die mit dem Constraint overlapping 24 2.2 UML versehen wurden. Dann kann es eine Subklasse Amphibienfahrzeug (e) geben, die Auto und Motorboot als Superklasse hat. Da die Generalisierung zwischen einem allgemeinen und einem spezielleren Modellelement definiert ist, existiert auch eine Generalisierung von Assoziationen. Bei Abhängigkeiten (Dependencies) ist ein Modellelement abhängig von einem anderen. Wird ein Modellelement – das als Supplier bezeichnet wird – geändert, dann kann dies Auswirkungen auf das andere A (a) Element – das Client genannt wird – haben. ABCD Dargestellt wird dies mit einem Pfeil mit einer B gestrichelten Linie, die vom Client zum Supplier C zeigt. Optional kann der Pfeil mit einem Namen und DBCA einem Stereotyp versehen werden, von denen einige D vordefiniert sind. Es ist möglich, mehrere Clients und mehrere Supplier zu haben. In diesem Fall Sommerreifen zeigen die Pfeile aller Clients auf einen Punkt (der (b) auch als Punkt, also als kleiner ausgefüllter Kreis, «substitute» visualisiert werden kann), von dem aus Pfeile zu den Ganzjahresreifen Suppliern gehen (a). Es gibt mehrere verschiedene Arten von Abhängigkeiten, die durch Abbildung 12: UML Abhängigkeiten unterschiedliche Stereotypen gekennzeichnet werden. «abstraction» wird benutzt zwischen zwei Elementen, die dasselbe Konzept auf verschiedenen Abstraktionsstufen oder aus unterschiedlichen Blickwinkeln repräsentieren. Einige Spezialformen von «abstraction» sind vorgegeben (z.B. «derive», «refine», «trace»). So auch die Realisierung, bei der der Supplier eine Spezifikation und der Client dessen Implementierung repräsentiert. Die graphische Darstellung der Realisierung orientiert sich an Abhängigkeiten durch eine gestrichelte Linie und an Generalisierung durch ein leeres Dreieck auf der Seite des Suppliers (Spezifikation). Eine weitere Abhängigkeit ist mit «permit» gegeben, durch das der Client Zugriffsrechte auf alle Elemente des Supplieres erhält. Dies kann in C++ durch das Schlüsselwort „friend“ erreicht werden. Durch «substitute» können Instanzen von Klassen während der Laufzeit ausgetauscht werden, ohne dass Generalisierung verwendet wird (b). Dies ist beispielsweise sinnvoll bei Zielplattformen, die Vererbung und Polymorphie nicht unterstützen. Bei der Verwendung von «use» benötigt ein Modellelement für seine Implementierung und Ausführung andere Modellelemente. 25 2.2 UML Eine Schnittstelle (Interface) ist eine Klasse, bei der alle Methoden abstrakt sind. Eine solche Klasse kann nicht instanziert werden, sondern definiert nur die Namen der Funktionalität. Eine andere Klasse (Anbieter) muss dann die Implementierung dieser Schnittstelle liefern, die dann von einer weiteren Klasse (Nutzer) benutzt wird. Für Schnittstellen gibt es drei mögliche Darstellungsformen. Bei der ersten Möglichkeit (a) wird die Schnittstelle wie eine Klasse als Rechteck mit dem Stereotyp «interface» dargestellt und ist mit dem Anbieter über eine Realisierung und mit dem Nutzer über eine Abhängigkeit verbunden. In der zweiten Möglichkeit (b) wird die Schnittstelle als Kreis (ball) visualisiert, die mit dem Anbieter über eine durchgezogene Linie verbunden ist. Der Kreis wird mit dem Namen der Schnittstelle versehen und ist mit dem Nutzer über eine Abhängigkeit (a) Anbieter «interface» Schnittstelle (b) Anbieter Nutzer Schnittstelle (c) Anbieter Nutzer (d) Protokollierung zur Abrechnung Nutzer Schnittstelle Pay-TV Decoder [1] Signal Abbildung 13: UML Schnittstellen und Ports («use») verbunden. In der dritten Möglichkeit (c) wird der Anbieter und die Schnittstelle wie zuvor dargestellt. Der Nutzer ist mit einer durchgezogenen Linie mit einem Halbkreis (socket) verbunden, der eine benötigte Schnittstelle repräsentiert. Diese Form nennt sich ball-and-socket Notation. Schnittstellen beschreiben die Verbindung zwischen verschiedenen Softwarekomponenten, aber sie sagen nichts über die Systemumgebung aus. Zu diesem Zweck benutzt man Ports, die durch ein kleines Rechteck am Rand der Klasse (oder auch anderer Modellelemente) dargestellt werden. Die benötigten und unterstützten Schnittstellen werden dann an den Port angebracht und Name sowie in eckigen Klammen Multiplizität daneben geschrieben. Im Beispiel (d) benötigt man zum Nutzen von Pay-TV einen Decoder. Dieser benötigt ein Fernsehsignal und bietet eine Schnittstelle an, mit der ein Unternehmen die Nutzung protokollieren und so exakte Rechnungen stellen kann. 26 2.2 UML Parametrisierte Klassen (Templates) sind Klassen, die noch nicht vollständig spezifiziert sind. Erst wenn Parameter an sie gebunden, können sie instanziert werden. Dieses Konzept wird in C++ mit Templates und in Java mit Generics realisiert. Graphisch dargestellt werden sie durch ein Rechteck auf der rechten oberen Ecke der Klasse (a), in dem die Parameter definiert werden. Die T, k : int Liste (a) eintrag : T [0..k] (b) (b) «bind» <T→Gast, k→20> «bind» <T→Adresse, k→50> Gaestebuch Telefonbuch Abbildung 14: UML Parametrisierte Klassen (Templates) Parameterdefinition ist eine durch Kommas separierte Liste von Parametern der Form „name [: type] [= default]“, wobei der Typ Klasse nicht angezeigt wird ((a): „T“ hat als Typ Klasse). Gebunden werden die Parameter mit dem Stereotyp «bind» dem eine in die Zeichen „< >“ gefasste und durch Kommas separierte Liste mit Parametern der Form „Parametername → Wert“ folgt (b). Der Quellcode für dieses Beispiel in C++ ist: template <class T, int k> class Liste { T eintrag[k]; }; Liste<Adresse,100> Telefonbuch; Liste<Gast,20> Gaestebuch; und in Java: class Liste<T> { Vector<T> eintrag; public Liste(int k) { eintrag = new Vector<T>(k); } } Liste<Adresse> Telefonbuch=new Liste<Adresse>(100); Liste<Gast> Gästebuch =new Liste<Gast> (20); 27 2.2 UML Dabei sind Gast und Adresse noch zu definierende Klassen. Dieses Beispiel ist [Jeckle04] entnommen. Zusätzlich können Pakete im Klassendiagramm benutzt werden, die im Folgenden betrachtet werden. 2.2.3.2 Model Management – Paketdiagramm UML definiert drei Möglichkeiten, um Modellelemente zu gruppieren. Dabei werden unterschiedliche Aspekte des Systems hervorgehoben. Pakete können verschiedene Modellelemente enthalten. Dabei wird ein eindeutiger Namensraum festgelegt, d.h. sie können beispielsweise in C++ durch namespace realisiert werden. Graphisch dargestellt werden sie durch ein großes Rechteck über dem ein kleines links ausgerichtetes Rechteck – das tab genannt wird – steht. Die Elemente der Gruppe können entweder in dem großen Rechteck untergebracht werden (a) oder außerhalb der Gruppe. In letzterem Fall werden sie mit durchgängigen Linien mit der Gruppe verbunden, wobei am Gruppenende der Linie ein Kreis mit einem Plus-Zeichen angebracht ist (b). Sind die Elemente in der Gruppe, dann wird der Gruppenname im tab angezeigt (a). Ansonsten kann der Name im großen Rechteck dargestellt werden (b). Jedes Element kann eine Sichtbarkeit haben, die entsprechend der Sichtbarkeit von Attributen und Operationen definiert ist und visualisiert wird ((c) und (d)). Grundsätzlich gibt es drei Beziehungen zwischen Paketen, die in Form einer Abhängigkeit mit unterschiedlichen Stereotypen dargestellt werden. Mit «access» erhält ein Paket P1 Zugriff auf ein anderes Paket P2, wobei alle Elemente aus P2 in P1 die Sichtbarkeit private haben. Durch «import» werden die Elemente aus einem Paket in ein anderes importiert und sind auch dort von außen sichtbar. Sie erhalten also per default die Sichtbarkeit public (Beispiel: in (c) importiert das Paket X das Paket Y. Dabei werden nur die öffentlichen Elemente – d.h. die Elemente mit Sichtbarkeit '+' (public) – importiert. Dies sind „C“ und „E“. In (d) ist das Ergebnis des Imports zu sehen, wobei die importierten Elemente mit dem eindeutigen Namen des Pakets Y markiert sind, d.h. „C“ wird zu „Y::C“). In diesem Fall kann den importierten Elementen allerdings auch eine lokale Sichtbarkeit und ein lokaler Alias-Name zugewiesen werden (Beispiel: in (d) könnte „Y::C“ auch „-F“ genannt werden). Die dritte Abhängigkeit ist durch «merge» gegeben. Dadurch können Elemente mit gleichen Namen aus unterschiedlichen Paketen zu einem neuen Element verschmolzen werden (Beispiel: In (e) werden die Pakete „P1“ und „P2“ mit „P3“ verschmolzen. Das Ergebnis ist in (f) zu sehen. Dabei wurde das Element „A“, das in allen drei 28 2.2 UML (a) (c) Paket X Klasse A Klasse B Y A «import» B (b) Paket +E -D (d) + Klasse A +C X Klasse B Y A Y::C B Y::E (f) P3' «import» +C +E -D (e) P1 P2 A A a : String op1 () b : String op2 () B «merge» C «merge» P3 A A a : String b : String op1 () op2 () B D C D Abbildung 15: UML Pakete Paketen vorhanden war, zu einem neuen Element verschmolzen.). Auch Templates können auf Pakete angewendet werden. Models (a) werden benutzt, um verschiedene Aspekte des physikalischen Systems in unterschiedlichen Abstraktionsgraden zu visualisieren. Graphisch dargestellt werden Models wie Pakete mit dem Stereotyp «model» oder einem kleinen Dreieck als Icon. Zur Darstellung gleicher Konzepte in verschiedenen Models werden gestrichelte Verbindungslinien mit dem Stereotyp «trace» verwendet. 29 2.2 UML Mit Subsystemen (b) wird ein Verhalten gruppiert. Die Darstellung entspricht der des Pakets mit einem Icon. Das große Rechteck wird dabei aufgeteilt in zwei Bereiche. Die externe Sicht zeigt die Dienste, die das Subsystem zur Verfügung stellt (Spezifikation) und die interne Sicht zeigt die Realisierung mit Elementen, die in anderen Diagrammen benutzt werden können. So kann im Realisierungsbereich beispielsweise ein Anwendungsfalldiagramm oder Klassendiagramm enthalten sein. In einer hierarchischen Darstellung können Models und Subsysteme gemischt auftreten. Analyse (a) Design (b) Analyse Spezifikation Realisierung «trace» Abbildung 16: UML Model (a) und Subsystem (b) 2.2.3.3 Weitere Diagramme Die folgenden Diagramme sind für die weiteren Betrachtungen nicht notwendigerweise relevant und werden deshalb der Vollständigkeit halber kurz und mit einem Beispiel beschrieben. Mit dem Komponentendiagramm werden Komponenten und Artefakte und ihre Beziehungen untereinander angezeigt. Komponenten sind hierbei modulare und austauschbare Teile des Systems mit eindeutig definierter Schnittstelle und Artefakte sind die Umsetzungen (Manifestationen) der Komponenten, also beispielsweise Dateien mit Quellcode oder auch ausführbare Dateien. Beide Elemente werden durch Rechtecke dargestellt mit den «component» Stereotypen «component» bzw. java.sql. Titel_Impl connection Titel «artifact» und speziellen Icons. «manifest» Eine Abhängigkeitsbeziehung mit dem Stereotyp «manifest» «artifakt» kann Komponenten und Titel.jar Artefakte verbinden. Eine Abhängigkeit von Artefakt A Abbildung 17: UML Komponenten 30 2.2 UML nach Artefakt B kann darauf hindeuten, dass eine sprachenspezifische Beziehung zwischen A und B besteht, und dass eine Änderung in B eine Neukompilierung von A benötigt. Das Verteilungsdiagramm (Deployment Diagram) zeigt auf, wie das System auf unterschiedliche Computer verteilt ist und wie diese miteinander kommunizieren. Das zentrale Element dieses Diagramms ist der Node (Knoten). Nodes können Computer, Ausführungsumgebungen wie Serversoftware oder auch «device» Client1 : WindowsPC «TCP/IP» «device» Client2 : WindowsPC «device» Server «execution environment» Fileserver «TCP/IP» «execution environment» Webserver Abbildung 18: UML Verteilungsdiagramm Hardware zur Verbindung wie Switches und Hubs sein. Dargestellt werden Nodes durch dreidimensionale Boxen mit einem Namen und den Stereotypen «device» und «execution environment». Nodes sind in UML Classifier und können sowohl als Typ als auch als Instanz dargestellt werden (Beispiel: ein Node „FileServer“ als eine Klasse von Servern mit den Instanzen „FileServer1“, „FileServer2“ usw.). Der Name des Nodes wird dargestellt wie bei Klassen, d.h. fett gedruckt im Fall eines Classifiers bzw. unterstrichen und gefolgt von einem Doppelpunkt und dem Classifiernamen im Fall einer Instanz. Verbunden werden Nodes mit einfachen Linien, die ein Stereotyp entsprechend der Art der Verbindung haben (z.B. «TCP/IP»). Mit dem Kompositionsstrukturdiagramm (Composite Structure Diagram, auch Architekturdiagramm genannt) können die internen Strukturen von Classifiern – wie beispielsweise Klassen – und das Zusammenspiel mit anderen Systemkomponenten detaillierter beschrieben werden. Die verwendeten Modellelemente sind u.a. Part, Port und Kollaboration. Ports sind bereits aus dem Klassendiagramm bekannt. Mit Parts kann man darstellen, dass Instanzen des betrachteten Classifiers eine Menge von Instanzen anderer Classifier durch Komposition (man beachte den Namen des Diagramms: Kompositionsstrukturdiagramm) beinhalten kann. Die folgende Abbildung zeigt ein Beispiel dazu. Die Klasse Fahrrad (a) besitzt durch Komposition genau ein 31 2.2 UML Hinterrad, das durch eine Kette mit zwei Pedalen verbunden ist. Eine Assoziation zwischen Fahrrad und Pedal existiert ebenso. In Teil (b) der Abbildung wird derselbe Sachverhalt auf eine andere Art und Weise dargestellt. Dadurch ist die Angabe von Details über Hinterrad und Pedal möglich, die nur im Kontext der Klasse Fahrrad gelten, beispielsweise die Angabe der Assoziation „Kette“. Da nur eine Assoziation zwischen Fahrrad und Pedal existiert, wird Pedal in (b) in einem gestrichelten Rechteck dargestellt. Die Multiplizität kann entweder wie gewohnt in eckige Klammern gefasst werden oder in der rechten oberen Ecke des Rechtecks Platz finden, wie es bei den Pedalen in (b) dargestellt ist. Mit Kollaborationen wird die Zusammenarbeit von Classifiern oder Operationen visualisiert. Dargestellt werden sie durch eine horizontale gestrichelte Ellipse, die durch eine gestrichelte Linie in zwei Bereiche gegliedert ist. Im oberen Bereich steht zentriert und fett der Typ (Beispiel (c): „Käufer-Handel“) oder der Name gefolgt von einem Doppelpunkt und dem Typ (Beispiel (c): „Ankauf : Handel“). Im unteren Bereich wird die interne Struktur der Kollaboration dargestellt. Dabei können Kollaborationen geschachtelt auftreten (c) oder durch Abhängigkeitsbeziehungen miteinander verbunden sein (d). (a) Fahrrad Fahrrad (b) Hinterrad Kette Pedale 2 : Rad [1] 1 2 : Pedal Hinterrad 1 2 Pedale Kette Rad Pedal 2 1 Käufer-Handel (c) Käufer Ankauf: Handel Verkäufer Händler Verkäufer (d) Käufer-Handel Produzent Händler Konsument Käufer Verkauf: Handel Produzent Konsument «occure n ce» Handel Käufer Verkäufer Abbildung 19: UML Kompositionsstrukturdiagramm mit Parts und Kollaborationen 32 2.2 UML Mit dem Anwendungsfalldiagramm oder Use Case Diagram wird das Systemverhalten aus der Sicht des Anwenders dargestellt. Dabei wird die Funktionalität stark abstrahiert, sodass nicht gezeigt wird, wie etwas funktioniert. Stattdessen wird nur aufgezeigt, was möglich ist. Das Diagramm besteht hauptsächlich aus Akteuren und Anwendungsfällen (use cases) sowie einigen Beziehungen untereinander. Ein Akteur kann dargestellt werden als Klasse mit Online-Shop Produkt suchen Kunde Produkt bestellen Produkt versenden «include» Sachbearbeiter Bestellung bearbeiten Produkte verwalten Mitarbeiter Lagerist Abbildung 20: UML Anwendungsfalldiagramm Stereotyp «actor», als eine kleine Grafik (z.B. ein Computer) oder als Strichmännchen, wobei unterschiedliche Strichmännchen für verschiedene Arten von Akteuren möglich sind (vergleiche [UML2Toolkit]). Anwendungsfälle werden visualisiert durch eine Ellipse oder in Form einer Klasse mit einer kleinen Ellipse als Icon. Sowohl Akteur als auch Anwendungsfall können Bezeichnungen und Generalisierungen haben. Es gibt zwei Abhängigkeiten zwischen Anwendungsfällen. Mit «extend» wird ein Anwendungsfall durch einen anderen erweitert und bei «include» ist ein Anwendungsfall vollständig in einem anderen enthalten. Aktivitätsdiagramme (Activity Diagram) dienen der Geschäftsprozessmodellierung und werden typischerweise zur Modellierung und Verfeinerung von Anwendungsfällen benutzt. In gewisser Hinsicht sind sie eine objektorientierte Adaption von Flussdiagrammen und haben eine Semantik, die den Petri-Netzen ähnelt. Aktivitätsdiagramme haben üblicherweise einen oder mehrere Start- (kleiner schwarzer Kreis) und Endpunkte (ein Kreis, in dessen Mittelpunkt ein schwarzer Kreis liegt) sowie mehrere Aktivitäten (Rechteck mit abgerundeten Ecken). Sie unterstützen unter anderem Verzweigungen, Parallelität, Exception-Handling und die Teilung des Diagramms in logische Partitionen (hierarchische und mehrdimensionale Partitionierungen sind möglich). 33 2.2 UML Abbildung 21: UML Aktivitätsdiagramm, entnommen aus [UML2Toolkit] Mit dem Zustandsdiagramm (State Machine Diagram) können Zustände und Zustandsübergänge von Objekten, Schnittstellen, Anwendungsfällen u.a. beschrieben werden. Die Zustände werden dargestellt durch ein Rechteck mit abgerundeten Ecken und können in bis zu drei Bereiche unterteilt werden. Im oberen Bereich ist der Name. Im optionalen mittleren Bereich wird das Verhalten als Antwort auf bestimmte Ereignisse aufgelistet. Dabei können eigene Ereignisse definiert werden, es sind jedoch auch schon drei Ereignisse vordefiniert. Mit entry werden Aktionen beim Beginn des Zustands ausgeführt, mit do während des Ausführens und mit exit definiert man die Aktionen beim Verlassen des Zustands. Der optionale untere Bereich ist für interne Zustandsänderungen vorgesehen. Zustandsänderungen (Transitions) werden durch Pfeile dargestellt und können einen Namen und zusätzliche Bedingungen haben. Hat man mehrere Zustandsdiagramme, dann können sich diese gegenseitig durch einen Nachrichtenaustausch beeinflussen. Dargestellt wird dies durch einen gestrichelten sm Geldautomat defekt {final} defekt Karte prüfen {final} Karte angenommen Betrag wählen Karte ausgeben {final} Betrag gewählt Trasaktion bestätigen {final} Abbildung 22: UML Zustandsdiagramm 34 2.2 UML Pfeil von einem Quellobjekt zu einem Zielobjekt (Objekt bedeutet in diesem Zusammenhang ein Zustand oder einen Zustandsübergang) zusammen mit einer Nachricht, die vom Zielobjekt verstanden werden muss. Desweiteren gibt es den History Indicator. Dieser wird benutzt, um interne Zustände zu speichern, falls zu einem späteren Zeitpunkt zu diesem Zustand zurückgekehrt werden muss (entsprechend einem Abbruch in einem Algorithmus). Dargestellt wird er durch einen kleinen Kreis in dessen Mittelpunkt ein „H“ bzw. ein „H*“ steht, je nachdem ob der Zustand dieses Diagramms oder rekursiv die Zustände aller umschließenden Zustandsdiagramme wiederhergestellt werden sollen. Weitere Elemente sind Anfangs- (kleiner schwarzer Kreis) und Endpunkt (Kreis mit schwarzem Kreis in der Mitte), Verzweigungen (leeren Raute) und Abbruchpunkte (Kreis mit „X“). Im Sequenzdiagramm (Sequence Diagram) steht die Zusammenarbeit und der Nachrichtenaustausch zwischen Objekten in Abhängigkeit von der Zeit im Zentrum der Betrachtung. Vertikal verläuft die Zeit und horizontal werden die Objekte aufgetragen. Jedes Objekt hat eine sogenannte Lebenslinie (gestrichelte Linie), die vertikal unter dem Objekt angebracht ist. Wird ein Objekt für die weitere Betrachtung nicht mehr benötigt, dann endet die Lebenslinie mit einem großen Kreuz. Nachrichten zwischen Objekten werden durch Pfeile zwischen deren Lebenslinien dargestellt. Wird eine Nachricht an ein Objekt gesendet, dann beginnt eine Aktionssequenz, die durch ein schmales Rechteck über der Lebenslinie kenntlich gemacht wird. Die komplette Sequenz ist eingefasst in ein Abbildung 23: UML Sequenzdiagramm, entnommen aus [UML2Toolkit] 35 2.2 UML großes Rechteck, in dessen linker oberer Ecke ein Operator und optional eine Beschreibung angebracht ist. Der Operator gibt die Art der Sequenz an. Dabei steht beispielsweise „sd“ für sequence diagram und „alt“ für Alternative. Letzteres kann in vielen Programmiersprachen mittels einer „if“ - Anweisung realisiert werden. Weitere Operatoren sind verfügbar (loop, neg, critical, ...). Zeitangaben sind ebenso möglich und werden durch Constraints realisiert. Das Interaktionsübersichtsdiagramm (Interaction Overview Diagram) zeigt den Zusammenhang zwischen verschiedenen Interaktionen in Form eines Aktivitätsdiagramms. Dabei sind die einzelnen Aktivitäten Interaktionen wie beispielsweise Sequenz- oder Kommunikationsdiagramme, die mit den aus dem Aktivitätsdiagramm bekannten Modellelementen miteinander verbunden werden. iod Geldautomat ref PIN-Eingabe [PIN == falsch] sd :Authentifizierungs:Display system ref Karteneinschub [PIN == richtig] ref Geldtransaktion ref Karteneinzug anzeigen („Karte wird eingezogen“) ref Kartenauswurf Abbildung 24: UML Interaktionsübersichtsdiagramm Das aus der technischen Informatik bekannte Zeitdiagramm (Timingdiagramm) beschreibt Zustandsänderungen von Klassen, Akteuren, Komponenten und anderen Modellelementen und Diagrammen in Abhängigkeit von der Zeit und als 36 2.2 UML Reaktion auf externe Ereignisse. Dadurch ist eine präzise Darstellung von zeitkritischen Systemen möglich. Die zu betrachtenden Instanzen werden dabei vertikal und die Zeit horizontal angeordnet. Im unteren Bereich ist eine Zeitskala darstellbar und Nachrichten können zwischen den Instanzen durch Pfeile dargestellt werden. : Ampel td Fußgängerampel {4*d} grün rot betriebsbereit gehen nicht gehen : Fußgänger aktivieren aktiv wartend 0 10 20 Sek. 30 Abbildung 25: UML Timingdiagramm Das Kommunikationsdiagramm (Communication Diagram) ist eine Weiterentwicklung des aus frühreren UML-Versionen bekannten Kollaborationsdiagramms. Es beschreibt – wie das Sequenzdiagramm – den Nachrichtenaustausch zwischen Objekten, wobei allerdings die interne Struktur sowie der Nachrichtenaustausch und nicht die zeitliche Abfolge im Vordergrund steht. Um zeitliche Aspekte zu berücksichtigen, können Nachrichten mit einer Nummerierung versehen werden. cd Gassi gehen 1: Gassi gehen Hundehalter Hund 3: entfernen 2.1: schimpfen 1.1: machen 2: reintreten Passant Häufchen Abbildung 26: UML Kommunikationsdiagramm 37 Kapitel 3 Stand der Technik In diesem Kapitel sollen einige graphisch orientierte Programme vorgestellt und getestet werden. Dabei wird untersucht, ob ein vorhandener Quellcode korrekt eingelesen und dargestellt werden kann, ob die Darstellung zum Verständnis geeignet ist und diverse Optionen zulässt – wie beispielsweise das Ausblenden von Details und farbliche Unterstützung – und ob der Quellcode aus der Darstellung richtig erzeugt wird. Weiterhin werden die Programme auf einige nützliche Funktionen hin untersucht, wie das Vorhandensein einer Zoom-Funktion und die Möglichkeit, die komplette graphische Darstellung in einem kleinen Übersichtsfenster anzuzeigen. Weitere Informationen sind in Form einer tabellarischen Zusammenstellung in Anhang B: Stand der Technik zu finden. 3.1 Programm: ClassBuilder 2.4 Alpha 1.7 ClassBuilder ist ein Opensource-Projekt, das auf Sourceforge ([W3-SF]) als Freeware erhältlich ist und unter Windows läuft. Der Sourcecode wird unter der zlib/libpng-Lizenz vertrieben. Die unterstützte Programmiersprache ist C++. Das Reverse Engineering ist über einen speziellen Menüpunkt aufrufbar. Nach der Angabe verschiedener Pfade sucht das Programm nach Dateien mit vorher festgelegten Dateiendungen. Der Sourcecode wird dabei nicht korrekt erkannt, obwohl das Programm keinen Fehler meldet. Einer der Fehler, die gefunden wurden, hat seine Ursache in der Instanzierung von Objekten. In C++ wird ein Objekt mit dem Namen der Klasse und anschließenden Namen des Objekts angelegt. Optional kann das Schlüsselwort „class“ angegeben werden (Beispiel für die Instanzierung des Objekts Paul für die Klasse Mensch: „Mensch Paul;“ bzw. „class Mensch Paul;“). Wird das Schlüsselwort „class“ angegeben, dann wird der Sourcecode ab dieser Stelle nicht mehr richtig erkannt. So ist im Test auch eine Klasse mit dem Namen „)“ erkannt worden, was in C++ sicherlich nicht möglich ist. Weitere Fehler treten bei der Verwendung von Templates auf. Ist der Sourcecode eingelesen, werden die erkannten Klassen mit Attributen und Methoden in einer Baumstruktur dargestellt (siehe Abbildung: linkes Fenster). Dort können Attribute und Methoden von einer Klasse in eine andere Klasse 38 3.1 Programm: ClassBuilder 2.4 Alpha 1.7 verschoben werden. Wird vorhandener Code mehrmals eingelesen, tauchen Attribute und Methoden fälschlicherweise mehrmals in der Baumstruktur auf. Abbildung 27: Screenshot ClassBuilder 2.4 Alpha 1.7 Durch einen Menüpunkt werden Klassen- und Sequenzdiagramme erstellt. Welche Klassen und welche Attribute bzw. Methoden dieser Klassen dargestellt werden, kann individuell für jedes Diagramm ausgewählt werden. Leider werden nur Generalisierungen angezeigt, aber keine Assoziationen. Aggregationen können erstellt werden, haben jedoch keinen Einfluss auf den erzeugten Sourcecode. Automatisches Layout und eine Zoom-Funktion sind vorhanden, jedoch kein Übersichtsfenster. Ein simpler Texteditor ist integriert, bietet aber nur wenige Funktionen. Dokumentationen mit Beschreibungen zu allen Klassen sowie die erstellten Diagramme können in RTF und HTML exportiert werden. Der erzeugte Sourcecode unterscheidet sich beträchtlich vom eingelesenen Code. Überall werden Kommentare mit unklarer Bedeutung eingefügt (z.B. „//@CODE_352“) und es werden neue Methoden mit den Namen 39 3.1 Programm: ClassBuilder 2.4 Alpha 1.7 „ConstructorInclude“ und „DestructorInclude“ erstellt. Der komplette Code wird neu formatiert. Dabei gehen vorhandene Einrückungen verloren. Die Reihenfolge der Methoden geht ebenfalls verloren, da diese alphabetisch sortiert werden. Methodenkörper werden beibehalten. Wird ein Attribut oder eine Methode innerhalb der Baumstruktur in eine andere Klasse verschoben, dann äußert sich dies auch im Sourcecode in der Klassendefinition, allerdings wird dies nicht im Methodenkörper berücksichtigt. Für jede Klasse wird eine separate Datei angelegt, auch wenn im ursprünglichen Code mehrere Klassen in einer Datei waren. 3.2 Programm: Jumli 1.4 Die Studenten der Berufsakademie der Bausparkasse Schwäbisch Hall erstellten Jumli als UML-Editor und Entwicklungsumgebung für Java, C# und C++. Jumli ist primär als Schulungstool konzipiert und kostenlos unter www.jumli.de erhältlich. Externe Compiler und Debugger können in das Programm durch Eingabe des Pfades eingebunden werden. Durch Öffnen der Sourcecode-Dateien und anschließendes Analysieren werden diese in einer Baumstruktur dargestellt (siehe Abbildung: linkes Fenster). Der Sourcecode wird dabei nicht richtig erkannt. So wurde im Test eine Methode als Klasse gefunden. Auch die Sichtbarkeitsidentifizierer „private“ und „protected“ hält das Programm für Klassen. Anscheinend hat Jumli Probleme beim Reverse Engineering von Templates und von Klassen mit Nested Klassen. Es können Klassen-, Sequenz-, Use-Case-, Kollaborations-, Aktivitäts-, Zustandsund Komponenten-Diagramme erstellt werden. Durch einfaches Drag & Drop können Klassen aus der Baumstruktur in das Klassendiagramm übernommen werden. Für einzelne Klassen oder eine Menge von markierten Klassen können Attribute und Methoden wahlweise ausgeblendet und eine Farbe festgelegt werden. Für Aggregationen und Kompositionen gibt es nur eine Darstellung. Diese entspricht der UML-Aggregation. Ein Texteditor ist vorhanden. Dort können einzelne Passagen per Mausklick ein- und auskommentiert werden. Eine Zoom-Funktion ist sowohl für Text als auch für die Diagramme verfügbar. Leider ist bei höheren Zoom-Stufen der Bildschirmaufbau recht langsam. Ein Übersichtsfenster existiert nicht und automatisches Layout ist nicht möglich. 40 3.2 Programm: Jumli 1.4 Abbildung 28: Screenshot Jumli 1.4 Generalisierung und Assoziation können durch einfaches Ziehen einer Linie zwischen zwei Klassen erstellt werden. Aggregationen werden in einem Eigenschaftsfenster der Klasse eingetragen. Der aus dem Modell generierte Sourcecode weist nahezu keine Veränderungen im Vergleich zum ursprünglichen Code auf. Neue Attribute können ohne Probleme erstellt werden und scheinen auch in Code korrekt angelegt zu werden. Nach dem Löschen von Attributen sind diese in Jumli nicht mehr sichtbar, werden jedoch nicht aus dem Sourcecode entfernt. Wird ein Klasse, ein Attribut oder eine Methode umbenannt, dann wird die alte Benennung komplett beibehalten und einfach ein zusätzliches neues Element erzeugt. Die Daten können als XMI und HTML exportiert werden. 41 3.3 Programm: Metamill v3.1 (build 556) 3.3 Programm: Metamill v3.1 (build 556) Metamill gibt es als 30tätige Testversion mit einer Einschränkung von 20 Elementen pro Diagramm. Die unterstützten Programmiersprachen sind sowohl für Reverse Engineering als auch für Forward Engineering Java, C++, ANSI C und C#. Beim Importieren von vorhandenen Sourcecode wurde lediglich ein Fehler gemeldet. Die betreffende Datei wurde dann übersprungen. Alle anderen Klassen, Attribute und Methoden wurden korrekt erkannt. Allerdings sind keine Aggregationen und Kompositionen gefunden worden, sondern nur Generalisierungen. Klassen und Assoziationen werden getrennt in einer Baumstruktur verwaltet. Abbildung 29: Screenshot Metamill v3.1 (build 556) 42 3.3 Programm: Metamill v3.1 (build 556) Metamill unterstützt Use-Case, Klassen-, Sequenz-, Paket-, Statechart, Kollaborations-, Aktivitäts-, Komponenten- und Verteilungsdiagramme, die in den drei Sichten Use-Case, Design und Implementierung angeordnet sind. Klassen können durch Drag & Drop aus der Baumstruktur in ein Klassendiagramm übernommen werden. Eine farbliche Abgrenzung ist vorhanden, kann aber nicht beeinflusst werden. Zoom ist möglich. Zur Darstellung der Klassen gibt es zwei Detailstufen: entweder es werden alle Attribute und Methoden mit allen Details angezeigt oder es werden nur die öffentlichen angezeigt, wobei bei den Methoden die Parameter unterdrückt werden. Diese Einstellung gilt nur für das gesamte Diagramm und ist nicht auf einzelne Klassen anwendbar. Diagramme können als Windows Enhanced Metafile (.emf) gespeichert, und Projekte im XMI-Format importiert und exportiert werden. Generalisierungen, Assoziation, Aggregationen und noch weitere Beziehungen können durch einfaches Ziehen einer Linie von einer Klasse zu einer anderen erstellt werden. Der große Nachteil von Metamill äußert sich im Forward Engineering. Methodenkörper werden nicht in die mit Metamill erstellten Dateien übernommen. Zusätzlich ist der erzeugte Code überfüllt mit kryptischen Kommentaren (z.B. „//#UBLK-BEG-METHOD mm:6384627a-ef21-11d8-9839f3f647b836dc“). Erstellte Aggregationen werden nicht in den generierten Quellcode übernommen und umbenannte Elemente finden sich ebenfalls nicht im Code wieder. 3.4 Programm: ObjectDomain R3 (build 292) Da ObjectDomain in Java programmiert wurde, läuft es auch auf allen Javakompatiblen Plattformen. Dabei werden die Sprachen Java, C++, Python und IDL unterstützt. Im rechten unteren Bereich ist ein Ausgabefenster. Dort werden nach dem Reverse Engineering diverse Exceptions angezeigt (z.B. „java.lang.ArrayIndexOutOfBoundsException“ in „Tokenizer.java“). Tatsächlich wurden einige Attribute und Methoden nicht erkannt. Nach dem Einlesen des Sourcecodes werden erkannte Klassen in der gewohnten Baumstruktur angezeigt und können von dort mittels Drag & Drop in ein erstelltes 43 3.4 Programm: ObjectDomain R3 (build 292) Diagramm übernommen werden. Zoom, Übersichtsfenster und automatisches Layout sind vorhanden. Für jede Klasse kann einzeln ausgewählt werden, ob der Attributebereich oder Methodenbereich angezeigt werden soll. Dies geht jedoch nicht für eine Gruppe von Klassen. Aggregationen und Kompositionen werden als Pfeil dargestellt und durch einfaches Ziehen von einer Klasse zu einer anderen erstellt. Das Umbenennen von Elementen funktioniert. Allerdings werden Kompositionen als Aggregationen erstellt und im erzeugten Sourcecode werden Methodenkörper nicht übernommen. Abbildung 30: Screenshot ObjectDomain R3 (build 292) ObjectDomain unterstützt Use-Case-, Klassen-, Objekt-, Komponenten-, Verteilung-, Statechart-, Aktivitäts, Sequenz- und Kollaborations-Diagramme, die in den Bildformaten png, gif, jpg, ppm, svg und wmf exportiert werden können. Import und Export von XMI und Rational Rose-Dateien (.mdl) ist möglich. 44 3.5 Programm: objectiF 4.7 3.5 Programm: objectiF 4.7 Das von microTOOL angebotene Programm objectiF wird in drei Versionen angeboten. Die objectiF Visual Studio .NET Edition bietet eine Integration in Microsoft Visual Studio .NET und ist speziell für die Entwicklung in Visual Basic .NET optimiert. Die objectiF Eclipse Edition ist für die Java-Entwicklung unter Eclipse bestimmt. Die hier getestete Version ist die objectiF Enterprise Edition, die als Standalone für die Programmiersprachen C++, Java und Visual Basic .NET gedacht ist. Zusätzlich werden die Eclipse Edition und die Visual Studio .NET Edition in einer kostenlosen Personal Edition angeboten, die jedoch auf fünf Diagramme pro Diagrammart begrenzt ist. Objectif definiert mehrere Sichten, denen einzelne Diagramme zugeordnet sind. Die Anforderungssicht enthält Aktivitäts- und Use-Case-Diagramme, für das Design können Aktivitäts-, Klassen-, Paket- und Sequenzdiagramme angelegt werden, die Implementierungssicht benutzt Klassen- und Sequenzdiagramme und die Systemsicht ist aus Paket-, Aktivitäts-, Use-Case-, Klassen- und Abbildung 31: Screenshot objectiF 4.7 45 3.5 Programm: objectiF 4.7 Sequenzdiagrammen aufgebaut. Ergänzend zu einzelnen Klassen können Zustandsdiagramme angelegt werden. Das Reverse Engineering läuft ohne Probleme ab. Lediglich ein Fehler wurde gemeldet und dieser steht in Zusammenhang mit einer speziellen Erweiterung von Microsoft Visual C++, die nicht konform mit dem C++ - Standard ist. Die gefundenen Klassen werden dann in einer Baumstruktur verwaltet und können durch Drag & Drop in ein erstelltes Klassendiagramm eingefügt werden. Nicht alle Beziehungen werden korrekt angezeigt bzw. erkannt. Attribute und Methoden von Klassen können per Mausklick angezeigt und ausgeblendet werden. Zoom ist vorhanden, jedoch fehlt ein automatisches Anordnen. Auch Kompositionen sind nicht vorhanden. Alle Elemente haben einen semantischen Namen und eine Deklaration bzw. einen technischen Namen. Ersteres ist nur für die Darstellung in objectiF, während die Deklaration angibt, wie das Element im Sourcecode benannt wird. Aus einem Klassendiagramm kann ein sogenanntes Kontextdiagramm erstellt werden, d.h. zu einer gegebenen Klasse wird ein neues Klassendiagramm erstellt mit eben dieser Klasse und allen Klassen, die damit in Beziehung stehen. Es ist möglich, den Sourcecode zu manipulieren (allerdings nur an bestimmten Stellen). Änderungen werden aber erst sichtbar, nachdem der Code erzeugt und erneut mittels Reverse Engineering eingelesen wurde. ObjectiF bietet mehrere Refaktorisierungs-Möglichkeiten an. Wird die Funktion „Interface extrahieren“ auf eine Klasse angewendet, so öffnet sich ein Fenster, in dem mehrere Methoden der Klasse ausgewählt werden können. Es wird ein Interface mit diesen Methoden erstellt und eine Beziehung angelegt, die anzeigt, dass die Klasse das Interface realisiert. Durch die Möglichkeit „Superklasse extrahieren“ bzw. „Unterklasse extrahieren“ wird eine neue Klasse mit vorher ausgewählten Methoden erstellt, so dass die vorhandene Klasse von dieser neuen Klasse erbt bzw. als Superklasse für eine neue Unterklasse fungiert. Durch „Klasse extrahieren“ können einzelne Methoden aus einer vorhanden Klasse in eine neue Klasse ausgelagert werden. Durch einen Rechts-Klick auf den Punkt „Dateien“ in der Baumstruktur kann das Forward Engineering gestartet werden. Dadurch wird der Sourcecode neu geschrieben und auch geändert, wenn in objectiF keine Änderungen vorgenommen wurden. So wird die Reihenfolge der Attribute und Methoden verändert, und es werden Kommentare eingefügt. Wurden im ursprünglichen 46 3.5 Programm: objectiF 4.7 Code mehrere Attribute in einer Anweisung definiert (z.B. „int i,j;“), dann werden diese aufgespalten und separat definiert („int i; int j;“). Das Umbenennen von Elementen wird anscheinend korrekt durchgeführt. Dabei werden die Namen überall im Code angepasst. Wird ein Element entfernt, so wird nur seine Deklaration entfernt, jedoch bleiben vorhandene Codezeilen, in denen dieses Element auftritt, erhalten. So erhält der Entwickler die Möglichkeit, beim erneuten Kompilieren anhand der Fehlermeldungen – die durch das nicht definierte Element auftreten – jedes Vorkommen einzeln zu überprüfen. 3.6 Programm: Rational Rose Enterprise Edition (2003.06.00.436.0) Rational Rose wurde ursprünglich von der Firma Rational Software entwickelt, der Geburtsstätte der Unified Modelling Language (UML) und könnte somit als Referenzprodukt gelten. Beim Anlegen eines neuen Projekts hat der Benutzer die Auswahlmöglichkeit zwischen den verschiedenen Sprachen und Dialekten J2EE, J2SE 1.2 - J2SE 1.4, JDK 1.1.6, JDK 1.2, JFC 1.1, Oracle 8 Datentypen, RUP, Visual Basic 6, Visual C++ 6 ATL 3.0 und Visual C++ 6.0 MFC 6.0. Es stehen die Sichten Use Case, Logisch, Komponenten und Verteilung zur Verfügung sowie Use Case-, Klassen-, Komponenten-, Verteilung-, Interaktions- und Zustands-Diagramme. Das Reverse Engineering ist etwas kompliziert zu finden. Dazu muss zuerst eine neue Komponente erstellt und dieser eine Sprache zugewiesen werden. Als Sprachen stehen u.a. ANSI C++, Java, Visual Basic, Ada83, Ada95, CORBA und Oracle8 zur Auswahl, wobei im Test ANSI C++ gewählt wurde. Danach muss im Menü der Punkt „ANSI C++ Spezifikation“ geöffnet werden, wo ein Pfad eingestellt und Dateien hinzugefügt werden können. Dabei werden die angegebenen Dateien bereits nach vorhandenen Klassen untersucht. Anschließend werden über den Menüpunkt „ANSI C++ Reverse Engineer“ die Klassen ausgewählt, die eingelesen und dem Modell hinzugefügt werden sollen. Es ist möglich, automatisch Pakete entsprechend den Unterverzeichnissen, in denen die Klassen definiert sind, anzulegen. Während dem Reverse Engineering wurden keine Fehler gemeldet. Die gefundenen Klassen sowie die Beziehungen zwischen ihnen werden in einer 47 3.6 Programm: Rational Rose Enterprise Edition (2003.06.00.436.0) Baumstruktur angezeigt. Sie können per Drag & Drop in ein erstelltes Klassendiagramm gezogen werden, wobei allerdings jedes Element einzeln dem Diagramm hinzugefügt werden muss. Für das Klassendiagramm stehen Zoom, Übersichtsfenster und ein automatisches Layout zur Verfügung. Die Farbe kann für einzelne Klassen oder für eine Gruppe von Klassen geändert werden. Zur Darstellung kann jederzeit zwischen den Notationen der Methoden Booch, OMT und Unified Method sowie zwischen unterschiedlichen Detaillierungsgraden ausgewählt werden. Das Umbenennen von Klassen, Attributen und Methoden wird direkt im Diagramm vorgenommen. Neue Beziehungen erreicht der Benutzer durch einfaches Ziehen der entsprechenden Linie. Abbildung 32: Screenshot Rational Rose Enterprise Edition Das Forward Engineering wird für einzelne Klassen durchgeführt über das Kontextmenü, das durch einen Rechtsklick auf die entsprechende Klasse (sowohl in der Baumstruktur als auch im Diagramm) erhältlich ist. Für eine Gruppe von 48 3.6 Programm: Rational Rose Enterprise Edition (2003.06.00.436.0) Klassen bedient der Anwender sich des Kontextmenüs der zu Beginn erstellten Komponente. Im erzeugten Sourcecode wird vor jede Klasse, jedes Attribut und jede Methode ein Kommentar gesetzt, beispielsweise „//##ModelId=408338D900FC“. Wurde eine Klasse umbenannt, so wird eine neue Klasse mit dem neuen Namen erstellt. Diese hat alle Methoden und Attribute der Klasse mit dem alten Namen, es werden jedoch nur Methodenrümpfe ohne Code erzeugt. Verweise auf die Klasse werden korrekt auf den neuen Namen gesetzt. Die Klasse mit dem alten Namen kann auf Wunsch gelöscht werden. Wird ein Attribut umbenannt, so wird der Name in der Deklaration korrekt geändert, jedoch wird kein Auftreten des Attributs in irgend einer Methode verändert. Methodenkörper werden also nicht angepasst. 3.7 Programm: WithClass 2000 Enterprise 6.0 Welche Version dieses Programms zum Test benutzt wurde, ist nicht ganz klar. Die Download-Möglichkeit auf der Homepage des Herstellers MicroGold benennt sie mit „WithClass 2000 Enterprise 6.0“. Trotzdem meldet sich das Programm mit einer Version 8.5 (siehe Titelleiste in der Abbildung). WithClass unterstützt eine große Anzahl von unterschiedlichen Methoden und Programmiersprachen. Dabei kommen die Methoden Unified Method, Rumbaugh, Coad/Yourdon, Booch, Shlaer/Mellor und Martin/Odell zum Einsatz, die auf Klassen-, Sequenz-, Statechart-, Use Case-, Kollaborations- und Implementierungs-Diagramme angewendet werden können. Als Sprachen können C++, Java als Quellcode (.java) und im Bytecode (.class), Delphi, Visual Basic, IDL, C# und VB.NET eingelesen werden. Zur Ausgabe stehen Ada, C++, C#, Delphi, Eiffel, IDL, Java, OO Cobol, Smalltalk, SQL, Visual Basic .NET, Visual Basic, HTML und Visual Foxpro zur Verfügung. Das Reverse Engineering ist durch zwei getrennte Menüpunkte durchführbar. Mit „Reverse File“ bzw. „Reverse Directory“ können C++ - Header-Dateien eingelesen werden. Durch den Punkt „Import all Code“ werden danach in einem zweiten Schritt die zugehörigen Methoden verarbeitet. Dabei können optional Fehler ausgegeben werden, die jedoch nicht allzu aufschlussreich sind, da nur das entsprechende Token benannt wird, nicht jedoch Dateiname und Zeilennummer, 49 3.7 Programm: WithClass 2000 Enterprise 6.0 in denen der Fehler auftrat. Die gefundenen Klassen werden in einer Baumstruktur verwaltet. Nested Klassen werden wie normale Klassen behandelt. Nach dem Reverse Engineering wird automatisch ein neues Klassendiagramm mit allen Klassen erstellt. Obwohl dabei ein Layout aller Klassen durchgeführt wird, ist ein automatisches Layout zu einem späteren Zeitpunkt nicht mehr möglich. Lediglich horizontales bzw. vertikales Anordnen sowie ein Anordnen von Elementen durch Verschieben, so dass die Elemente gleichen Abstand besitzen, ist für eine markierte Gruppe von Klassen möglich. Zoom ist ebenfalls möglich, hat aber im Test manchmal zu Problemen geführt, so dass das ganze Betriebssystem Aussetzer von mehreren Minuten hatte. Ein Übersichtsfenster ist vorhanden. Die Farbe, in der die Klassen dargestellt werden, kann für das ganze Diagramm oder nur für einzelne Klassen verändert werden, nicht jedoch für Gruppen von Klassen. Ebenso verhält es sich mit der Anzeige von Details. Hier stehen mehrere Auswahlmöglichkeiten zur Verfügungen, so dass Sichtbarkeit, Typ und weitere Werte der Attribute und Abbildung 33: Screenshot WithClass 2000 Enterprise 6.0 50 3.7 Programm: WithClass 2000 Enterprise 6.0 Methoden beliebig an- und abgeschaltet werden können. Die Anzeige kann auf Elemente mit bestimmter Sichtbarkeit (public, protected, private) beschränkt werden oder auch nur auf den Klassennamen. Weiterhin besteht die Möglichkeit, die Anzahl der angezeigten Attribute und Methoden auf fünf zu beschränken (andere Werte sind nicht möglich). Ein Text-Editor mit Syntax-Highlighting ist vorhanden. Die Rauten am Ende einer Aggregation enden nicht immer direkt an einer Klasse. Ob dies ein Bug oder ein Feature ist, sei dahingestellt. Die benutzte Methode kann jederzeit geändert werden, und das Diagramm wird dann in der entsprechenden Notation neu gezeichnet. Der Export von Diagrammen ist in den Bildformaten bmp, gif, jpg, tiff und wmf möglich. Die Namen von Klassen können direkt im Diagramm geändert werden. Neue Attribute und Methoden erstellt der Benutzer in einem Dialogfenster, das sich durch Doppelklick auf eine Klasse öffnet. Hier können diese Elemente auch umbenannt werden. Beziehungen wie Generalisierung und Aggregation können durch einfaches Ziehen der entsprechenden Linie erstellt werden. Zusätzliche Assoziationen sind vorhanden, werden allerdings nur durch ihr Aussehen beschrieben und sind anscheinend nur zum Design vorhanden. Diese Assoziationen werden ausgewählt durch Icons in der Toolbar. Leider sind diese Icons doppelt vorhanden (einmal in der Toolbar am oberen Rand des Klassendiagramms und einmal in der Toolbar des Programms am linken Fensterrand), so dass die Übersicht darunter leidet. Beim Forward Engineering kann der Anwender zwischen den verschiedenen unterstützten Sprachen auswählen. Dabei ist das Erstellen der Header-Dateien vom Erstellen der Source-Dateien mit den Implementierungen der Methoden getrennt, so dass wie beim Reverse Engineering das Erstellen in zwei separaten Schritten erfolgen muss. Dabei wird für jede Klasse und Struktur eine separate Datei angelegt. Auch Nested Klassen werden in einer separaten Datei untergebracht. Die Sourcecode-Dateien werden umformatiert, so dass vorhandene Einrückungen verloren gehen. Auch vorhandene Kommentare werden nicht in den neu erzeugten Code übernommen. Dafür werden neue Kommentare eingeführt. Wurden Elemente in WithClass umbenannt, so werden sie auch in den entsprechenden Deklarationen umbenannt, der restliche Code wird jedoch nicht angepasst. Enumeratoren werden komplett entfernt. Die „#inlcude“-Anweisungen werden verändert und teilweise entfernt. Einige Attribute werden doppelt erstellt. 51 3.8 Weitere Programme 3.8 Weitere Programme Es wurden noch weitere Programme getestet, die hier aber nur kurz vorgestellt werden sollen: Select Component Factory 5.0 Dieses Programm greift sehr stark in das Betriebssystem ein. So werden u.a. mehrere neue Benutzer unter Windows erstellt und einige speicherresidente Programme gestartet. Select Component Factory kann sich in Visual Age, JBuilder 4 - JBuilder 7, Eclipse und WebSphere Studio Application Developer (WSAD) integrieren und definiert die Diagramme Process Hierarchy, Process Thread, Use Case, Class, Object Collaboration, Object Sequence, Activity, Entity Relationships, Table Relationships, State, General Graphics und Text. Kernstück des Engineering ist ein separates Programm namens „Select C++ Sync“. Dort kann der Benutzer Sourcecode in ein Modell einlesen und nach dem Bearbeiten wieder in Code verwandeln. Bei jedem Start dieses Programms wird der Code neu eingelesen und die gefundenen Elemente den Elementen des Modells gegenübergestellt. Pfeile vom Modell zum Code oder umgekehrt deuten auf eine Veränderung hin, und es kann mit dem Reverse oder Forward Engineering begonnen werden. Dies muss leider für jede Klasse einzeln durchgeführt werden. Wird ein Attribut, eine Klasse oder eine Methode in Select Component Factory umbenannt, dann meldet das Synchronisierungstool ein neues Element im Modell und ein neues Element im Code. Infolge dessen werden immer nur neue Elemente hinzugefügt. Wird ein Attribut oder eine Methode gelöscht, dann ändert sich nichts im Code. Es werden lediglich Beziehungen. Generalisierungen angezeigt, aber keine anderen UMLStudio 7.1 (Build 746) UMLStudio unterstützt die Notationen UML (Klassen-, Use-Case-, Sequenz-, Kollaborations-, Zustand-, Aktivitäts- und Implementierungs-Diagramm), Booch (Klassen, Use-Case, Sequenz, Kollaborations-, Zustand-, Prozess- und ModulDiagramm), und Strukturiert (ER-, Use-Case-, Sequenz-, Kollaborations-, Data 52 3.8 Weitere Programme Flow-, Flow Chart- und Jackson-Diagramm). Es werden Links, Places und Diagramme definiert. Places sind Klassen und Anwendungsfälle u.a. und Links sind die Beziehungen zwischen ihnen. Zu jedem Diagramm kann festgelegt werden, welche Links und Places vorhanden sein dürfen. Das Aussehen der Links ist veränderbar. Roundtrip Engineering wird nicht angeboten. Es kann zwar ein Modell aus vorhandenem Code erzeugt werden, und aus dem Modell kann wieder Sourcecode generiert werden, aber Methodenkörper werden nicht übernommen. Der Hersteller bietet auf der Homepage eine Buch über C++-Programmierung kostenlos zum Download an. Enterprise Architect 4.0 build 729 Dieses Programm unterstützt alle UML 2.0 Diagramme sowie zusätzlich die Diagramme „Extended Class“ und „Analysis“ (vereinfachtes Aktivitätsdiagramm). Es gibt die Sichten Use Case, Dynamisch, Logisch, Komponenten, Verteilung und Benutzerdefiniert. In Enterprise Architect gibt es keine Möglichkeit, vorhandene C++-Dateien einzulesen, außer den Header-Dateien. So werden auch Methodenkörper nicht übernommen. Die Codegenerierung kann minimal angepasst werden durch vorgegebene Generierungs-Templates. Jedes Diagramm kann als Desgin Pattern eingesetzt werden. In Anbetracht der Tatsache, dass nur Header-Dateien verarbeitet werden, gibt es eine große Anzahl von Funktionen mit fragwürdigem Nutzen, wie die Verwaltung eines Glossars und eine Rechtschreibprüfung für Kommentare. 53 3.9 Fazit 3.9 Fazit Keines der hier vorgestellten Programme lief ohne Fehler ab bzw. konnte durch einfaches Reverse Engineering mit anschließendem Forward Engineering den ursprünglichen Sourcecode unverändert wiederherstellen. So wurden Einrückungen geändert, kryptische Kommentare eingeführt oder gar ganze Klassen in separate Dateien ausgelagert. Teilweise ist der Code nach der Verarbeitung nicht mehr kompilierbar. Eine Refaktorisierung sollte mit diesen Programmen also nur vorgenommen werden, wenn der Benutzer eine Änderung der Struktur der Sourcecodes in Kauf nehmen und zusätzliche Zeit investieren kann, um den Code wieder lauffähig zu machen. Zum Erstellen von Dokumentationen und zum Design neuer Komponenten können sie jedoch sehr nützlich sein. Bei allen Programmen werden die Klassen in einer Art Baumstruktur verwaltet. Die graphische Darstellung ist getrennt von dieser Verwaltung. So können in einem Diagramm Beziehungen geändert oder hinzugefügt werden, ohne dass dies Einfluss auf den vorhandenen Code hätte. Dadurch kann der Benutzer seiner Kreativität freien Lauf lassen, um ein vorhandenes Design neu zu gestalten. Die unterschiedlichen Optionen für graphische Darstellungen, wie beispielsweise individuelle Farbeinstellungen und die Unterdrückung von Details durch das Ausblenden von Attributen, Methoden sowie Sichtbarkeit, Typ, Methodenparameter u.a. unterstützen das direkte Erkennen von vernetzten Strukturen, obwohl eine zusätzliche detailliertere Sichtweise mit Berücksichtigung der an speziellen Beziehungen beteiligten Methoden von Vorteil wäre. Weitere Darstellungsmöglichkeiten wie das Sequenzdiagramm werden lediglich in der Erstellung unterstützt, insofern als dass dort für die Nachrichten zwischen einzelnen Objekten eine Auswahl aus den vorhandenen Methoden der Klasse zur Verfügung steht. Eine automatische Erstellung anhand einer Methode mit rekursiver Verzweigung aller aufgerufenen Objekte wäre wünschenswert. 54 Kapitel 4 Diskussion und Konzept UML ist sehr vielseitig und unterstützt die Software-Entwicklung in allen Phasen, jedoch bereitet eine Abbildung in neuen bzw. aus vorhandenem Sourcecode einige Probleme. Der Grund hierfür liegt in der hohen Abstraktion und Allgemeinheit der Modellierungssprache. Als Beispiel sei das Anwendungsfalldiagramm genannt. Dieses kann sicherlich ohne weitere Informationen weder in Code übersetzt noch aus gegebenem Code gewonnen werden. Auch andere Diagramme können teilweise in Sourcecode abgebildet werden, aber eine automatische Erstellung von UML-Diagrammen anhand von Sourcecode ist problematisch oder gar unmöglich. So kann beispielsweise ein endlicher Automat in Code übersetzt werden, aber beim Reverse Engineering in das entsprechende Diagramm taucht u.a. die Frage auf, wie ein Zustand im Programm realisiert wurde (z.B. durch eine Zahl, eine Klasse oder durch Enumeratoren). Dies wird auch im offiziellen UML-Standard deutlich gemacht: „The UML, a visual modeling language, is not intended to be a visual programming language, in the sense of having all the necessary visual and semantic support to replace programming languages“ Auch wenn UML nicht zur graphischen Programmierung konzipiert wurde, können doch viele Konzepte und graphische Darstellungen für diesen Zweck benutzt werden. Und sie sollten auch benutzt werden, da UML ein anerkannter und beliebter Standard ist, der ständig weiterentwickelt wird. Bevor ein Konzept entwickelt wird, sollten zuerst einige Eigenschaften und Probleme der Zielsprache C++ aufgezeigt werden. 4.1 Die Programmiersprache C++ Als Nachfolger der Programmiersprache B ([W3-B72]) wurde 1972 von Dennis Ritchie bei AT&T Bell Laboratories die erste Version der Programmiersprache C entwickelt. 1980 wurde C von Bjarne Stroustrup um objektorientierte Konzepte erweitert. Dies wurde „C mit Klassen“ genannt. Erst 1983 wurde der Name in „C++“ geändert. Dabei steht „++“ für den Imkrement-Operator, der den Wert einer Variablen um eins erhöht. 55 4.1 Die Programmiersprache C++ 1998 wurde C++ von der International Standards Organisation ISO unter der Bezeichnung „ISO/IEC 14882-1998(E)“ standardisiert. Einige der besonderen Eigenschaften von C++, die beim Roundtrip Engineering Probleme bereiten können, sind: C++ enthält C C++ ist eine Erweiterung von C und enthält alle syntaktischen und semantischen Eigenschaften von C. Somit sind noch einige „Altlasten“ vorhanden, die die Verarbeitung komplizieren (siehe hierzu auch Kapitel 5.1 Parser, Präprozessor). Vorverarbeitungsstufe (Präprozessor) Vor dem Parsen wird der Sourcecode durch eine Vorstufe verändert. Weitere Informationen hierzu sind in Kapitel 5.1.2 Präprozessor Zeiger und Pointerarithmetik Pointer oder Zeiger zeigen auf einen Speicherbereich. Dies kann benutzt werden, um nicht den Wert eines Datenelements (z.B. Variable oder Objekt) anzugeben, sondern die Position im Speicher, an der dieses Element abgelegt ist. Mit Pointern kann man auch rechnen. Hat man beispielsweise einen Pointer auf das zweite Element aus einem Array von Bytes und man verändert den Pointer durch einfache Addition mit eins, dann erhält man einen Pointer auf das dritte Element aus dem Array. Die Verwendung von Zeigern als Datentypen wird in Kapitel 4.2.1 Datentypen, Zeiger und Referenzen besprochen. Eigene Datentypen Mit Hilfe des Schlüsselwortes „typedef“ können neue Datentypen definiert werden. Dabei werden lediglich neue Namen für bereits bestehende Datentypen vergeben. 4.2 Die graphische Darstellung Die objektorientierte Programmierung bietet bereits eine gute Abstraktion des Sourcecodes durch die Reduzierung ganzer Klassen auf einen Namen. Jedoch wird eine graphische Darstellung bei mehreren hundert Klassen selbst bei dieser Reduzierung sehr unübersichtlich. Andererseits ist es manchmal von Vorteil, durch die Visualisierung der Verbindungen einzelner Methoden die vernetzten Strukturen sichtbar zu machen. All diese Details in einer einzelnen Graphik 56 4.2 Die graphische Darstellung unterzubringen, ist nicht praktikabel. Aus diesem Grund sollte ein Programm mehrere Diagramme mit unterschiedlichem Detailgrad darstellen können. Auch sollte es einen passenden Kompromiss zwischen Abstraktion und Sprachennähe geben. Orientiert man sich zu sehr an einer den Eigenschaften einer bestimmten Programmiersprache, so kann die graphische Darstellung nicht auf andere Sprachen ausgeweitet werden. Abstrahiert man hingegen zu stark, können Diagramme nicht mehr automatisch erstellt und aktualisiert werden. Dies führt dazu, dass zu viel Zeit investiert werden muss, um eine Graphik zu einem bestimmten Programmteil zu erstellen und diese Graphik bei Programmänderungen mit dem Sourcecode konsistent zu halten. 4.2.1 Datentypen, Zeiger und Referenzen Jede Variable hat einen Datentyp und jede Methode liefert einen Rückgabewert, der einen Datentyp besitzt. Als Datentypen stehen primitive Datentypen (u.a. int, char, float), Objekte und selbst definierte Typen sowie Zeiger (Pointer) und Referenzen auf diese zur Verfügung. Beispiele für Variablen vom Typ int sowie Zeiger darauf: 01 02 int int * Zahl; pZahl; 03 04 05 *pZahl = 4; int * pZahl2 int * pZahl3 06 int ** 07 08 09 int & refZahl = Zahl; int * & refpZahl = pZahl; int ** & refppZahl = ppZahl; = pZahl; = & Zahl; ppZahl; In Zeile (1) wird eine Zahl – d.h. eine Variable mit Datentyp int – definiert. Zeile (2) legt einen Zeiger auf eine Zahl fest. Dabei ist „pZahl“ der Variablenname, der auf eine Position im Speicher zeigt (auch Adresse genannt), an der eine Zahl steht. Diese Zahl kann man ändern, indem man dem Variablennamen ein „*“ voranstellt (Zeile (3)). Will man jedoch den Zeiger verändern, so dass „pZahl“ auf einen anderen Speicherbereich zeigt, dann 57 4.2 Die graphische Darstellung geschieht dies durch Zuweisung einer anderen Adresse. Dazu kann entweder ein anderer Zeiger verwendet werden (Zeile (4)), oder man ermittelt die Adresse einer gegebenen Zahl. Letzteres wird mit Hilfe des sogenannten Adressoperators „&“ realisiert, der die Adresse einer Variablen liefert (Zeile (5)). Nicht nur einfache Zeiger sind möglich. In Zeile (6) wird eine Variable namens „ppZahl“ vom Typ int** definiert, d.h. ppZahl zeigt auf eine Adresse im Speicher, an der ein weiterer Zeiger steht, der dann auf eine Zahl zeigt. Dies kann man beliebig fortführen. Eine weitere Besonderheit in C++ stellen die Referenzen dar. Mit Referenzen kann man einer existierenden Variablen oder Objekt einen zweiten Namen geben, weshalb Referenzen auch Alias genannt werden. Angelegt werden sie mit einem „&“ zwischen Datentyp und Variablennamen und müssen denselben Datentyp haben wie die Variable, die sie referenzieren (Zeile (7) – (9)). Referenzen müssen bei der Definition initialisiert werden. Variablen stehen im Speicher und Zeiger zeigen auf eine Position im Speicher. Zur Darstellung ist also eine Visualisierung des Speichers nützlich. Eine übliche Darstellung dafür ist eine Band mit einzelnen Speicherzellen (siehe Abbildung, Teil (a)). Zeiger können dann wie in (b) gezeigt dargestellt werden. Auch eine kompaktere Form wie in (c) ist möglich. Referenzen sind zusätzliche Namen für bereits existierende Bezeichner. Eine Visualisierung ist in (d) gegeben. (a) (b) int (d) int (c) int Abbildung 34: Zeiger und Referenzen 4.2.2 Module und Komponenten Diese Programmteile stellen die größte Verallgemeinerung dar und sind nur schwierig oder gar nicht aus vorhandenem Programmcode zu extrahieren. Meistens bestehen Komponenten aus mehreren Klassen und Dateien und können auch kleinere Komponenten beinhalten. 58 4.2 Die graphische Darstellung Anhaltspunkte für die Zuordnung von Programmteilen zu einzelnen Komponenten können sein: • • • Unterverzeichnisse Namensräume Logische Aufteilung Bei großen Systemen werden aus Gründen der Übersichtlichkeit meistens die Dateien, die zu einer Komponente gehören, in einem entsprechenden Unterverzeichnis abgelegt. Auch die Verwendung von Namensräumen (in C++ durch den Befehl „namespace“ realisierbar) kann hierzu dienen. Da in beiden Fällen ein eindeutiger Namensraum festgelegt wird, kann die Darstellung mit Hilfe von UML-Paketen erfolgen. Das Programm in logische Komponenten aufzuteilen ist hingegen schwieriger. Hierbei können graphentheoretische Verfahren behilflich sein. Sind beispielsweise zwei Gruppen von Klassen nur durch eine bzw. wenige Klassen oder Assoziation miteinander verbunden, dann kann man eine Komponente vermuten. Die Darstellung ist beispielsweise mit UMLSubsystemen möglich. Abbildung 35: Logische Komponenten Eine automatische Erzeugung nach vorgegebenen Parametern ist möglich, aber ohne Benutzer-Interaktion in Form von Nachfragen und Vorschlägen durch das Programm ist eine sinnvolle Unterteilung schwierig und nicht eindeutig. 4.2.3 Klassen Klassen können direkt aus einem objektorientiert programmierten Sourcecode extrahiert werden. Zur Darstellung bieten sich UML-Klassendiagramme an. Zusätzlich können UML-Pakete zur Visualisierung der Namensräume und Subsysteme zur Darstellung der Unterverzeichnisse, in denen sich die Klassen befinden, herangezogen werden. Das Ausblenden von Details – wie das Unterdrücken der Anzeige von Attributen bzw. Methoden – sowie die farbliche Unterscheidung von Gruppen von Klassen dienen der einfachen Erkennung. Modifiziert werden kann der Sourcecode und das Diagramm durch Hinzufügen von Klassen und Assoziationen. Hierbei gibt es mehrere Möglichkeiten. Es könnte 59 4.2 Die graphische Darstellung eine Toolbar geben, in der man zwischen Klassen und diversen Assoziationen wählen kann. Wählt man eine Klasse aus, dann wird sie angelegt durch einfachen Mausklick auf einen freien Bereich im Diagramm. Will man eine Assoziation anlegen, dann geschieht dies durch Ziehen einer Linie mit der Maus von einer Klasse zu einer anderen. Es könnten auch (möglicherweise erst nach einem Druck einer bestimmten Taste) am Rand des Rechtecks, das eine Klasse darstellt, mehrere Icons eingeblendet werden. Sinnvoll wären hier beispielsweise ein Symbol für eine Generalisierung am oberen Rand oder Aggregationen/Kompositionen am Rand des Attribut-Bereichs. Klickt man auf eines dieser Symbole und zieht es auf eine andere Klasse, dann wird die entsprechende Assoziation angelegt. Einige Abweichungen zum UML-Standard könnten in Betracht gezogen werden. So ist die Zuordnung von einzelnen Zeichen zur Beschreibung der Sichtbarkeit („+“, „-“ und „#“) zwar sehr einfach, aber man muss die UML-Notation gut kennen, um diese Darstellung in den richtigen Kontext zu bringen. Hier wäre eine Verwendung von kleinen Graphiken angebracht. Häufig anzutreffen sind zwei kleine Symbole: ein geschlossenes Vorhängeschloss für private, ein kleiner Schlüssel für protected. Für public wird kein Symbol verwendet. Auch die Unterscheidung von Aggregationen und Kompositionen ist nicht sehr offensichtlich, so dass man sich manchmal fragt, ob nun Aggregation bzw. Komposition durch eine ausgefüllte Raute oder durch eine nicht ausgefüllte Raute dargestellt wird. Hier bietet sich eine Darstellung wie für Variablen aus dem Kapitel 4.2.1 Datentypen, Zeiger und Referenzen an. Da diese Darstellung jedoch sehr detailreich ist, ist sie für das Klassendiagramm nur bedingt geeignet und eine Umschaltung auf UML-Notation sollte möglich sein. Ein weiterer Mangel an UML-Klassendiagrammen ist die Darstellung von Funktionsaufrufen. Es gibt eine allgemeine Assoziation, aber diese ist nur zwischen zwei Klassen definiert. Allgemein sind alle Beziehungen nur zwischen zwei Klassen definiert, aber es wird keine genaue Aussage darüber gemacht, welcher Teil einer Klasse an dieser Beziehung beteiligt ist. So werden alle Assoziationen, Aggregationen/Kompositionen und Generalisierungen als Linien gezeichnet, die irgendwo am Rechteck der einen Klasse anfangen und irgendwo am Rechteck der anderen Klasse aufhören. Um die Zuordnung einzelner Klassenelemente zu diesen Assoziationen zu verdeutlichen, sollten die Linien auch an den entsprechenden Elementen anfangen und aufhören. So könnte eine Generalisierung immer am oberen Rand der spezialisierten Klasse beginnen und 60 4.2 Die graphische Darstellung am unteren Rand der allgemeineren Klasse enden. Aggregationen und Kompositionen werden direkt mit den entsprechenden Attributen verbunden. Und Funktionsaufrufe – dargestellt durch gerichtete Assoziationen – gehen von einer Methode zu der Methode, die durch sie aufgerufen werden. Denkbar ist auch, dass Funktionsaufrufe immer von einem rechten Rand an einen linken Rand gehen, entsprechend für die Methode, die eine andere Methode aufruft. In diesem Fall wird das Diagramm aber im Ganzen gesehen unübersichtlicher, da es oft vorkommen kann, dass eine Assoziation nur über Umwege gezeichnet wird. Ein Beispiel dazu: Ein Fenster (a) Button Window (Klasse „Window“) hat als Element einen Button (Klasse draw() draw() „Button“). Beide haben eine Funktion namens „Draw“, mit der sie dargestellt werden, (b) Button Window wobei die Draw-Funktion des Fensters die Draw-Funktion draw() draw() des Buttons aufruft. In UML kann dies mit einer gerichteten Assoziation dargestellt Abbildung 36: Funktionsaufrufe in Klassendiagrammen werden, deren Anfangs- und Endpunkt nur durch den Rand der Klassen festgelegt sind. Aussagekräftiger ist hier jedoch die Verbindung der beiden Draw-Funktionen. Entsprechend der im deutschen Sprachraum üblichen Leserichtung von links nach rechts sollte die Assoziation aus der Klasse „Window“ am rechten Rand austreten und in die Klasse „Button“ am linken Rand eintreffen. Da die Klassen evtl. durch andere Randbedingungen des Klassendiagramms – wie beispielsweise ihre Beziehungen zu anderen Klassen oder Paketen – an ihre Position gebunden sind, muss die Assoziation für den Funktionsaufruf unter Umständen mit einem Umweg gezeichnet werden. Tritt dieser Fall öfter auf, dann kann das Diagramm recht unübersichtlich werden, es ist aber in Detailfragen aussagekräftiger. Werden die Funktionsaufrufe korrekt dargestellt, dann ist eine Ablaufverfolgung möglich, d.h. es werden ausgehend von einer Methode rekursiv alle Verbindungen zu den aufgerufenen Methoden mit einer bestimmten Farbe kenntlich gemacht. Dadurch erhält man einen schnellen Überblick über alle an einer Methode beteiligten Elemente. Das Problem, das dabei auftaucht, sind virtuelle Methoden. Dies sind Methoden, bei denen zur Laufzeit das Objekt auf eine überschriebene Methode hin untersucht wird und die Methode der tiefsten abgeleiteten Klasse 61 4.2 Die graphische Darstellung aufgerufen wird. Beispiel: 01 02 03 04 05 06 07 08 09 10 11 12 class Basis { .. virtual void out() { printf("Basis"); } }; class Sub : Basis { .. void out() { printf("Sub"); } }; Basis *obj = new Sub(); obj->out(); Es werden zwei Klassen definiert: eine Basisklasse „Basis“ und eine abgeleitete Klasse „Sub“. Eine Methode „out“ ist in Basis definiert und wird in „Sub“ überschrieben. Anschließend wird eine Variable „obj“ vom Typ „Basis“ erstellt und ihr ein Objekt der Klasse „Sub“ zugewiesen. Dieses Konzept wird Polymorphie genannt. Ruft man nun die Methode „out“ auf, dann ist das Verhalten abhängig davon, ob die Methode virtuell definiert wurde. Ist sie nicht virtuell, dann wird Basis::out aufgerufen, denn die Variable „obj“ ist vom Typ „Basis“. Ist die Methode jedoch virtuell definiert, dann wird zur Laufzeit ermittelt welche Klasse vorliegt, und dann die abgeleitete Methode aufgerufen. In diesem Fall wäre das Sub::out, denn der Variablen „obj“ wurde ein Objekt der Klasse „Sub“ zugewiesen. Ist eine Variable definiert als Objekt einer Klasse A, dann können dieser Variablen Objekte von allen Klassen zugewiesen werden, die von A abgeleitet sind. Es ist also bei Klassen mit virtuellen Methoden nicht möglich, anhand des Datentyps einer Variablen die aufzurufende Methode festzustellen. In diesem Fall müssen bei einer Ablaufverfolgung alle überschriebenen Methoden gekennzeichnet werden. 62 4.2 Die graphische Darstellung 4.2.4 Methoden Zur Darstellung der Methodenkörper eignen sich Programmablaufpläne, die auch Flussdiagramme genannt werden. Diese stammen bereits aus den 60er Jahren und sind in der DIN 66001 ([W3-DIN-PAP]) genormt. Besonders hilfreich ist dies bei der Darstellung von verschachtelten Strukturen. Die wesentlichen Elemente sind: • • • • • Rechteck: Operation (process, z.B.: „i = 10;“) Rechteck mit doppelten vertikalen Linien: Unterprogramm (predefined process) Oval: Beginn, Ende sowie weitere Grenzstellen (terminal oder interrupt, beispielsweise return, break, continue) Kreis: Übergangsstelle (connector) Raute: Verzweigung (decision, z.B.: if, switch) Zur Verdeutlichung vernetzter Zusammenhänge sollten am Rand – oder auch in der Darstellung selbst – die aufgerufenen Methoden in Form eines UMLKlassensymbols mit Klassennamen und dem Namen der Methode angezeigt werden. Zur Unterscheidung der Zusammenhänge der Elemente und der vernetzten Aufrufe sollten Verbindungspfeile in unterschiedlichen Farben dargestellt werden. Start i= sort Button 1 draw() 2 return sonst i = 1; Abbildung 37: Methoden 63 4.3 Synchronisation mit Sourcecode 4.3 Synchronisation mit Sourcecode Durch die graphische Darstellung werden viele Informationen mit den Sprachelementen verknüpft, die nicht in der Sprache vorgesehen sind. Dies sind Informationen über das Vorhandensein in einzelnen Diagrammen, die Koordinaten innerhalb der Diagramme, Daten über die angezeigten Details und weiteres. Es gibt mehrere Möglichkeiten, dies im Sourcecode unterzubringen: • Makros Werden die Makros auf eine leere Zeichenkette abgebildet, dann wird das Programm dadurch nicht verändert. So kann beispielsweise das Funktionsähnliche Makro „VISBLE_IN_DIAGRAM“ innerhalb einer Klasse mit dem Namen eines Diagramms als Parameter Informationen über das Vorhandensein dieser Klasse in unterschiedlichen Diagrammen enthalten. • Methoden und Variablen Wie Makros können auch Variablen und Methoden, die ansonsten nicht im Sourcecode genutzt werden, Daten enthalten. • Einrückungen Durch Einrückungen und Abstände zwischen Sprachelementen können Informationen kodiert werden. Allerdings wird der Code dadurch unleserlich und nur eine kleine Änderung kann unerwartete Auswirkungen auf die graphische Darstellung haben. • Kommentare Kodierung der Informationen in Kommentaren. Weitere Möglichkeiten sind denkbar. In allen Fällen jedoch wird der Sourcecode geändert. Er wird erweitert, teilweise sogar unleserlich und unübersichtlich. Aus diesem Grund ist es sinnvoll, die genannten Informationen separat zu speichern. Wird dann der Sourcecode außerhalb des Programms verändert, ist eine Synchronisation mit den vorhandenen Informationen nötig. Hier ist eine Gegenüberstellung sinnvoll. Die vorhandenen Daten werden mit den neu gewonnen Informationen aus den modifizierten Dateien verglichen und Änderungen dargestellt. Dies kann beispielsweise durch zwei Baumstrukturen realisiert werden, wobei alle Inkonsistenzen aufgezeigt werden. Der Benutzer 64 4.3 Synchronisation mit Sourcecode erhält dadurch die Möglichkeit, alte Bezeichnungen den außerhalb des Programms umbenannten Elementen zuzuordnen und gelöschte bzw. neu angelegte Elemente in das Modell zu übernommen. 4.4 Design Neben der Implementations-Sicht sollte es noch eine weitere Sicht geben: die Design-Sicht. Hier können neue Elemente oder Schablonen für häufig benutzte Konstruktionen entworfen werden ohne diese direkt der Implementierung hinzuzufügen. Dazu können alle vorherigen Diagramme genutzt werden. Die Namen können mit speziellen Sonderzeichen ausgestattet werden, die dann mittels Nachfrage durch einen anderen Text ersetzt werden. Ein Beispiel dazu: Das Sonderzeichen zur Textersetzung sei „?“. Es sind die Klassen „C?View“ und „C?Document“ mit diversen Methoden und Attributen in einem Design gegeben. Durch die Eingabe eines Namens durch den Benutzer – beispielsweise „Text“ werden die Klassen „CTextView“ und „CTextDocument“ dem Programm hinzugefügt. 65 Kapitel 5 Implementierung In diesem Kapitel werden sowohl Möglichkeiten zur effizienten Realisierung als auch die konkrete Implementierung der prototypischen Umsetzung behandelt. 5.1 Parser, Präprozessor Eine der wichtigsten Komponenten ist der Parser (engl. to parse = analysieren). C++ ist eine formale Sprache und kann somit durch eine Chomsky-Grammatik ausgedrückt werden. Obwohl der Sprachumfang nicht durch eine kontextfreie Sprache beschrieben werden kann, wird doch – wie bei vielen anderen Programmiersprachen – eine kontextfreie Grammatik angegeben. Der Grund hierfür ist in der effizienten Analyse dieser Grammatiken zu finden. In einem anschließenden Schritt werden kontextsensitive Konzepte verarbeitet. Das Erstellen eines Ableitungsbaums für die Grammatik für einen gegebenen Sourcecode ist die Aufgabe des Parsers. Er verarbeitet nacheinander mehrere Dateien oder Übersetzungseinheiten (translation unit). Dies geschieht in mehreren Schritten: 1. Zuordnung von Zeichen, Trigraph-Sequenzen Die Zeichen des Sourcecodes werden einer internen Darstellung zugeordnet. Dadurch können bestimmte ausländische Zeichen verarbeitet werden. Weiterhin werden Trigraph-Sequenzen aufgelöst. Dies sind Zeichenfolgen, die als Ersatz für ein bestimmtes Zeichen stehen. Trigraph-Sequenzen stammen noch aus einer Zeit, in der noch nicht alle benötigten Zeichen auf den Tastaturen vorhanden waren. Im einzelnen sind dies: Trigraph Trigraph ??= Entstehendes Zeichen # ??< Entstehendes Zeichen { ??/ \ ??> } ??' ^ ??! | ??( [ ??- ~ ??) ] 66 5.1 Parser, Präprozessor 2. Zusammensetzen von Zeilen Alle Zeilen, die mit einem Backslash („\“) enden, bei denen also der Backslash unmittelbar von einem Zeilenumbruchzeichen gefolgt wird, werden mit der folgenden Zeile zusammengefasst. So werden aus physikalischen Zeilen logische Zeilen. Der Backslash und das Zeilenumbruchzeichen werden dabei gelöscht. 3. Bilden von Präprozessor-Token Der Sourcecode wird aufgeteilt in Leerzeichen und Präprozessor-Token. Ein Token ist allgemein die kleinste sinngebende Einheit einer Programmiersprache. In diesem Schritt werden allerdings zuerst PräprozessorToken gebildet, die von einer Vorstufe – dem sogenannten Präprozessor – verarbeitet werden. In Schritt 7 werden diese dann in Token umgewandelt. Die Bildung von Präprozessor-Token ist kontextabhängig. So wird beispielsweise das Zeichen „<“ abhängig vom Kontext einmal als Punktuator und einmal als Teil einer „#include“-Anweisung aufgefasst. Präprozessor-Token können sein: Präprozessor-Token Beispiele Header-Name <stdio.h> Identifier ich_bin_1_variable Zeichen 'c' Zeichenketten „Resistance ist futile“ Präprozessor Punktuator oder Operator ## Präprozessor-Nummer 0ash12.E+ < > { ... 12e-OFFSET alles andere Die Präprozessor-Nummer (pp-number) wird später in eine Zahl umgewandelt (Integer, Oktal, Hexadezimal, Gleitkommazahl). Tatsächlich ist aber weit mehr als nur gültige Zahlen darstellbar (z.B: „0ash12.E+“). In diesem Punkt unterscheidet sich der Standard von einigen Compilern wie beispielsweise Visual C++, bei denen nur dann eine Präprozessor-Nummer gebildet wird, wenn diese auch in eine gültige Zahl übersetzt werden kann. Die Erstellung von Token wird mit einem Programmteil durchgeführt, das Scanner oder Lexer genannt wird. 67 5.1 Parser, Präprozessor Laut Standard werden in diesem Schritt auch Kommentare durch Leerzeichen ersetzt. Da Kommentare aber nützliche Informationen sind, die in einer (graphischen) Darstellung nicht fehlen sollten, wird an dieser Stelle vom Standard abgewichen. Zeilenumbruchzeichen werden beibehalten. 4. Vorverarbeitung Präprozessor-Direktiven werden ausgeführt und Makros werden expandiert (siehe hierzu auch Kapitel 5.1.2 Präprozessor). Bei einer #includeAnweisung werden die Schritte eins bis vier rekursiv für die entsprechende Datei durchgeführt. 5. Zeichensatzzuordnung Zeichen und Zeichenketten werden verändert. Escape-Sequenzen (z.B. „\n“) werden verarbeitet. 6. Zeichenfolgenverkettung Aufeinander folgende Zeichenketten werden zusammengefasst zu einer einzigen Zeichenkette. 7. Analyse Präprozessor-Token werden in Token umgewandelt. Der entstandene Tokenstrom wird syntaktisch und semantisch analysiert (siehe hierzu Kapitel 5.1.3 Analyseverfahren). 5.1.1 Alternative Darstellungen Für einige Operatoren und Punktuatoren existieren alternative Darstellungen. Die Bedeutung ist dieselbe, aber die Schreibweise ist unterschiedlich: alternativ <% primär { alternativ bitand primär & alternativ not primär ! %> } and && not_eq != <: [ bitor | and_eq &= :> ] or || or_eq |= %: # xor ^ xor_eq ^= %:%: ## compl ~ 68 5.1 Parser, Präprozessor 5.1.2 Präprozessor Präprozessor-Anweisungen (Direktiven) sind Befehle, die den Sourcecode vor dem Erstellen des Ableitungsbaums verändern. Die Fähigkeiten des Präprozessors beschränken sich auf das Ersetzen von Text und stellen trotzdem ein sehr mächtiges, aber auch fehleranfälliges Werkzeug dar. Jede Anweisung steht in einer einzigen Zeile, die mit dem Zeichen '#' begonnen werden muss. Der Präprozessor kann in folgende Bereiche eingeteilt werden: • • • • Symbolische Konstanten und Makros Dateien einfügen Bedingte Kompilierung Weitere Direktiven 5.1.2.1 Symbolische Konstanten und Makros Hierfür sind die Direktiven #define und #undef zuständig. Beispiele: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 #define #define #define #define #define DEBUG PI 3.14159 SUB(a, b) a - b TOSTRING(x) #x VAR(y) variable ## y int main() { int variable1, variablexy; std::cout << PI std::cout << SUB(10, 5) * 2 std::cout << TOSTRING(Hallo) std::cout << VAR(1) std::cout << VAR(xy) } << << << << << std::endl; std::endl; std::endl; std::endl; std::endl; Mit #define kann ein Name und ein Ersatztext angegeben werden, wobei jedes Auftreten des Namens durch den Ersatztext ersetzt wird. Es hat sich eingebürgert, dass Makronamen in Großbuchstaben geschrieben werden. In Zeile (1) wird lediglich ein Name angegeben, der in der bedingten Kompilierung abgefragt werden kann und im Sourcecode durch eine leere Zeichenkette ersetzt wird. Zeile 69 5.1 Parser, Präprozessor (2) definiert eine symbolische Konstante (auch „Objekt-ähnliches Makro“ genannt) und Zeile (3) ein Makro (auch „Funktions-ähnliches Makro“ genannt). Bei letzterem ist darauf zu achten, dass direkt auf den Namen eine geöffnete Klammer folgt, da sonst die Definition eines Objekt-ähnlichen Makros angenommen wird. Für Makros existieren zwei Operatoren. Der stringize-Operator „#“ in Zeile (4) sorgt dafür, dass das folgende Makro-Argument durch eine Zeichenkette (String) ersetzt wird, d.h. mit Anführungszeichen versehen wird. Mit dem concat-Operator „##“ in Zeile (5) werden zwei gegebene Token zu einem neuen Token verschmolzen. Der Präprozessor liefert für obiges Beispiel folgendes Ergebnis: 07 08 09 10 11 12 13 14 15 int main() { int variable1, variablexy; std::cout << 3.14159 << std::cout << 10 - 5 * 2 << std::cout << "Hallo" << std::cout << variable1 << std::cout << variablexy << } std::endl; std::endl; std::endl; std::endl; std::endl; Hier wird ein Problem deutlich, das auf den ersten Blick nicht erkennbar war. In Zeile (11) war offensichtlich beabsichtigt, zwei Zahlen zu subtrahieren und das Ergebnis dann mit zwei zu multiplizieren. Wird das Makro jedoch an dieser Stelle extrahiert, dann erscheint ein anderes Ergebnis. Der Grund hierfür liegt in den beschränkten Fähigkeiten des Präprozessors, der lediglich eine Textersetzung durchführt. Um solchen Fehlern vorzubeugen ist es ratsam, so viel wie möglich zu Klammern. Mit #undef kann die Definition von Makros wieder aufgehoben werden. 70 5.1 Parser, Präprozessor Es existieren mehrere vordefinierten Makros: Makroname Bedeutung __LINE__ Zeilennummer in der momentanen Sourcecode-Datei (Dezimalzahl). Kann durch #line beeinflusst werden. __FILE__ Dateiname des momentanen Sourcecodes (Zeichenkette). Kann durch #line beeinflusst werden. __DATE__ Datum der Übersetzung (Zeichenkette) __TIME__ Zeitpunkt der Übersetzung (Zeichenkette) __cplusplus Momentan festgelegt auf die Zahl „199711L“. Dies wird voraussichtlich in künftigen C++-Versionen geändert. 5.1.2.2 Dateien einfügen Mit Hilfe der #include-Anweisung können andere Dateien in die gerade bearbeitende Datei eingefügt werden. Dabei wird ein Dateiname angegeben: #include <stdio.h> #include "MyApp.h" Steht der Dateiname in spitzen Klammern, dann wird die Datei in den StandardInclude-Verzeichnissen gesucht, die beispielsweise in Umgebungsvariablen des Betriebssystems oder in speziellen Einstellungen des jeweiligen Compilers zu finden sind. Wird der Dateinamen in Hochkommas eingeschlossen, dann wird zuerst im aktuellen Verzeichnis nachgesehen. Wird die gesuchte Datei nicht gefunden und die gerade bearbeitende Datei ist auch nur eine Include-Datei in einer anderen Datei, dann wird als nächstes im Verzeichnis dieser anderen Datei gesucht u.s.w.. Falls die Datei weiterhin nicht gefunden wurde, wird auch hier im StandardInclude-Verzeichnissen gesucht. Dateinamen unterliegen der Makroersetzung. 71 5.1 Parser, Präprozessor 5.1.2.3 Bedingte Kompilierung Durch die bedingte Kompilierung können ganze Code-Passagen ausgeblendet werden. Dabei stehen mehrere Direktiven zur Auswahl: Direktive Bedeutung #if Verzweigt in Abhängigkeit von einem Ausdruck #ifdef Verzweigt, wenn ein Makro mit dem angegebenen Namen existiert #ifndef Verzweigt, wenn ein Makro mit dem angegebenen Namen nicht existiert #else Alternative, wenn #if, #ifdef oder #ifndef nicht zutreffen #elif Alternative, wenn #if, #ifdef oder #ifndef nicht zutreffen in Abhängigkeit von einem Ausdruck #endif Abschluß für #if, #ifdef oder #ifndef Äußerst nützlich sind diese Anweisungen für Header-Dateien, in denen üblicherweise die Klassen definiert werden. Diese Dateien werden oft mittels #include in andere Dateien eingefügt. Werden sie mehrfach eingefügt – wie es beispielsweise bei zyklischen Dateieinfügungen möglich ist – dann kann der komplette Inhalt der Datei ausgeblendet werden. Beispiel: #ifndef _STDIO_H #define _STDIO_H ... #endif 5.1.2.4 Weitere Direktiven Die Direktive #error gibt einen Fehler aus und beendet die Kompilierung. Mit #pragma werden Compiler-spezifische Anweisungen gegeben. Durch #line wird die im Compiler intern verwaltete Zeilennummer sowie der Dateiname geändert. Dies kann durch die vordefinierten Makros „__LINE__“ und „__FILE__“ abgefragt werden. 72 5.1 Parser, Präprozessor 5.1.2.5 Unterstützung durch das Programm Ein Programm, das C++-Code verarbeitet, sollte auf jeden Fall mit PräprozessorAnweisungen umgehen können. Hier tauchen jedoch auch einige Probleme auf. Ist beispielsweise der Name einer Klasse durch ein Makro entstanden und dieser Name soll geändert werden, dann gibt es zwei Möglichkeiten: man kann das Makro verändern oder man kann das Makro expandieren und in der expandierten Zeichenkette den Namen ändern. Im ersten Fall müssen weitere Abhängigkeiten berücksichtigt werden. Wird das Makro auch an anderen Positionen im Sourcecode verwendet, dann würden diese Stellen ebenfalls verändert, was zu unbeabsichtigten Resultaten führen kann. Wird hingegen der Text, der durch Makroexpansion entstanden ist, modifiziert, kann es zu Problemen kommen, wenn mehrere Makros an dieser Expansion beteiligt sind, oder wenn das Makro noch andere Anweisungen enthält, die dann nicht mehr zur Verfügung stehen. Beispiel: #define HEAD(end) MyClass ## end : public Base class HEAD(1) {..}; class HEAD(2) {..}; Das Ergebnis wäre: class MyClass1 : public Base {..}; class MyClass2 : public Base {..}; Nach dem Ändern des Namens der ersten Klasse von „MyClass1“ in „YourClass1“ im jeweiligen Fall und zurückschreiben als Sourcecode sieht das Ergebnis folgendermaßen aus: Fall 1: Makro verändern: => #define HEAD(end) YourClass ## end : public Base class HEAD(1) {..}; class HEAD(2) {..}; 73 5.1 Parser, Präprozessor Mit dem Ergebnis: class YourClass1 : public Base {..}; class YourClass2 : public Base {..}; Fall 2: Expandierten Text verändern: #define HEAD(end) MyClass ## end : public Base => class YourClass1 : public Base {..}; class HEAD(2) {..}; Mit dem Ergebnis: class YourClass1 : public Base {..}; class MyClass2 : public Base {..}; In diesem Fall stehen die übrigen Anweisungen im Makro (im Beispiel die Angabe der Basisklassen) für die umbenannte Klasse nicht mehr zur Verfügung. Auch wenn eine solche Verwendung von Präprozessor-Direktiven vermieden werden sollte, ist sie dennoch möglich und muss berücksichtigt werden. Da ein Programm eine solche Entscheidung nicht adäquat treffen kann, sollte in diesen Fällen der Benutzer gefragt werden. Auch die Verwendung von Makronamen in der (graphischen) Darstellung sollte man überdenken. Natürlich könnten alle Makros expandiert und nur der expandierte Text dargestellt werden. Oft sind Makronamen jedoch eine sinnvolle Umschreibung nichts sagender Zahlenwerte. Ein Beispiel dafür sind die ID's der Ressourcen eines Windows-Programms. Ist ein Dialog als Ressource erstellt worden, kann man auf ihn zugreifen durch die Angabe seiner IdentifikationsNummer (ID). Nun ist die Angabe eines Präprozessor-Makronamens „IDD_ABOUTBOX“ sicherlich aussagekräftiger als die simple Zahl „100“ (beim Erstellen solcher Ressourcen beispielsweise mit Visual C++ werden solche Makros automatisch angelegt). In solch speziellen Fällen kann man auf die Makroexpansion verzichten und das Makro wie die Definition einer globalen Variable betrachten. Zusätzliche Farben, Formatierungen oder Icons könnten die Herkunft dieses Namens kenntlich machen. 74 5.1 Parser, Präprozessor Unter [W3-CRefact] läuft zur Zeit ein Projekt zur Erlangung des akademischen Grades Ph.D. Dort werden Möglichkeiten zur Refaktorisierung von C-Code untersucht durch die Einbeziehung von Präprozessor-Direktiven in den Ableitungsbaum. 5.1.3 Analyseverfahren Zur Erstellung eines Ableitungsbaumes gibt es mehrere Verfahren. Nähere Informationen hierzu sind beispielsweise in [HMU02] zu finden. Mögliche Verfahren sind u.a.: • Younger Der Cocke-Younger-Kasami-Algorithmus (CYK) verarbeitet beliebige kontextfreie Sprachen. Die Grammatik muss allerdings ε-frei und in ChomskyNormalform sein. Dabei wird eine quadratische Tabelle aufgebaut, an dessen unterem Ende das Wort ist. Zu jedem Hilfszeichen, das in einer Zelle der Tabelle steht, gilt, dass dieses Hilfszeichen das Teilwort unter der Zelle ableiten kann. Je weiter oben in der Tabelle ein Hilfszeichen steht, desto größer ist das ableitbare Teilwort. Steht ganz oben das Startsymbol, dann kann dieses das ganze Wort ableiten. Der Zeitbedarf ist O(n3). • Earley Mit dem Earley-Verfahren können beliebige kontextfreie Grammatiken geparst werden. Der Zeitbedarf ist O(n3). Dabei werden sukzessiv sogenannte Itemlisten erstellt, die jeweils den Anfang der Eingabe korrekt erkennen. • Valiant Dieses Verfahren nutzt ε-freie Grammatiken in Chomsky-Normalform. Valiant reduziert die Berechnung auf die Multiplikation boolescher Matrizen. Der Zeitbedarf dafür liegt momentan bei etwa O(n2,5). Die berechneten Matrizen entsprechen im Wesentlichen der Tabelle des Younger-Verfahrens. • LL(k)-Parser Ein Top-Down-Verfahren, bei dem k Zeichen vorausgesehen wird (Lookahead), um die Entscheidung für die jeweilige Reduktion zu treffen. Der Zeitbedarf ist linear, allerdings gilt das Verfahren nicht für beliebige kontextfreie Sprachen und Grammatiken. Wenn LL(k) die Menge der durch einen LL(k)-Parser erkennbaren Sprachen ist, dann gilt: LL(1) É .. É LL(k) 75 5.1 Parser, Präprozessor • LR(k)-Parser Ein Bottom-Up-Verfahren, bei dem k Zeichen vorausgesehen wird, um die Entscheidung für die jeweilige Reduktion zu treffen. Auch hier ist der Zeitbedarf linear. Es können nicht alle kontextfreien Sprachen und Grammatiken verarbeitet werden, allerdings ist die Menge der erkennbaren Grammatiken größer als bei LL(k)-Verfahren. Es gilt: LL(k) É LR(1) = LR(2) = .. = LR(k) • Rekursiv absteigender Parser Bei diesem Verfahren wird für jedes Hilfszeichen eine Funktion erstellt. In dieser Funktion werden nacheinander die verschiedenen rechten Seiten der Regeln der Grammatik ausprobiert und dabei rekursiv andere Funktionen aufgerufen. Somit terminiert das Verfahren nicht bei linksrekursiven Hilfszeichen, denn dann würde eine Funktion sich immer wieder rekursiv selbst aufrufen, ohne dass ein Zeichen von der Eingabe konsumiert wird. Der Zeitbedarf ist im Allgemeinen exponentiell, aber das Verfahren ist einfach zu implementieren. Der Zeitbedarf der Verfahren, die beliebige kontextfreie Sprachen erkennen können, ist zu groß, als dass sie sinnvoll eingesetzt werden könnten. Hier bietet sich die Verwendung eines LR(k)-Parsers mit linearem Zeitbedarf an. Die Grammatik für C++ ist im offiziellen Standard gegeben und mittels automatischer Parsergeneratoren kann ein Parser generiert werden. Beliebte Generatoren sind YACC (Yet Another Compiler Compiler) und dessen GNU-Version Bison ([W3-Bison]). Beide erstellen LALR(1)-Parser, wobei LALR eine Teilmenge von LR ist. Benutzt man jedoch die Original-Grammatik, dann werden mehrere Konflikte gemeldet. Konflikte treten auf, wenn der Parser auch mit Lookahead keine eindeutige Entscheidung treffen kann, wie er fortfahren soll. Es werden zwei Arten unterschieden: shift/reduce und reduce/reduce-Konflikte. Shift/reduce-Konflikte tauchen auf, wenn der Parser nicht weiß, ob er eine Regel reduzieren oder die Eingabe weiterlesen soll. Ein bekanntes Beispiel dafür stellen geschachtelte „if“-Anweisungen dar, wie sie schon in C verwendet wurden. 76 5.1 Parser, Präprozessor Die Regeln der Grammatik sind: 1 2 3 4 5 6 statement: selection_statement | .. selection_statement: 'if' '(' condition ')' statement | 'if' '(' condition ')' statement 'else' statement Es wird folgendes Programm betrachtet: if (..) if (..) .. else .. Der Parser liest den Sourcecode Token für Token von links nach rechts. Das erste Token ist das Schlüsselwort „if“. Dieser Präfix kommt in zwei unterschiedlichen Regeln vor, die in den Zeilen (4) und (5) definiert sind. Da zu diesem Zeitpunkt keine Unterscheidung zwischen diesen beiden Regeln getroffen werden kann, wird der Präfix in einen Zwischenspeicher (Keller oder Stack) geschoben. Dies wird shift genannt. Anschließend wird die Eingabe (der Sourcecode) weiter gelesen und verarbeitet, bis der Parser am Schlüsselwort „else“ angekommen ist. An dieser Stelle taucht der Konflikt auf, denn der Parser kann nicht entscheiden, zu welcher der beiden „if“-Anweisungen das „else“ gehört. Es besteht also die Möglichkeit, die zweite Regel aus Zeile (5) anzuwenden und weiterzulesen. Der Parser kann allerdings auch mit der ersten Regel aus Zeile (4) eine Reduzierung (reduce) durchzuführen. Im ersten Fall (shift) würde das „else“ zu inneren (d.h. zu zweiten) und im zweiten Fall (reduce) zur äußeren (d.h. zur ersten) „if“-Anweisungen gehören. Da keine Unterscheidung zwischen shift und reduce getroffen werden kann, wird dieser Konflikt shift/reduce-Konflikt genannt. Gelöst wird dies durch die Parsergeneratoren meistens zugunsten von shift, was üblicherweise auch der Sprache entspricht (in diesem Fall gehört das „else“ zur inneren „if“-Anweisung). 77 5.1 Parser, Präprozessor Der andere Konflikt tritt auf, wenn zwei unterschiedliche Regeln zur Reduzierung verwendet werden können. Beispiel: 01 02 03 04 05 06 07 08 09 10 11 12 using_directive: 'using' 'namespace' namespace_name ';' namespace_name: original_namespace_name | namespace_alias original_namespace_name: identifier namespace_alias: identifier Hat der Parser bereits die beiden Schlüsselwörter „using“ und namespace“ gelesen, dann folgt als nächstes ein identifier und anschließend ein Semikolon. Der identifier kann jedoch mit zwei unterschiedlichen Regeln reduziert werden: mit „original_namespace_name“ aus Zeile (8) und mit „namespace_alias“ aus Zeile (11). Der Parser kann auch mit Lookahead – das in beiden Fällen das Semikolon liefert – keine Entscheidung über die anzuwendende Regel treffen. Dieser Konflikt wird reduce/reduce-Konflikt genannt. Die Parsergeneratoren lösen diesen Konflikt, indem sie die textuell erste Regel anwenden (in diesem Fall die Regel aus Zeile (8)). Der Parsergenerator Bison meldet bei der C++-Grammatik aus dem offiziellen Standard über 60 shift/reduce und über 700 reduce/reduce-Konflikte. Diese müssten eigentlich einzeln per Hand gelöst werden. Es stellt sich die Frage, ob es nicht bereits fertige Parser für diese Aufgabe gibt. Tatsächlich existiert eine recht große Anzahl im Internet und diese können teilweise auch frei heruntergeladen und genutzt werden. Allerdings wurde kein Parser gefunden, der den Anforderungen entspricht, fehlerfrei arbeitet und alle Konzepte der Programmiersprache unterstützt. Die Anforderung, die am häufigsten fehlt, ist, dass der Eingabetext (also die Sourcecode-Datei) in Token umgewandelt wird, ohne dass die Position der Token innerhalb der Datei festgehalten wird. Will man also ein Token ändern – beispielsweise weil man eine Variable umbenennen will – dann hat man keine Informationen über deren 78 5.1 Parser, Präprozessor Position in der Datei. Eine reine Textersetzung aller Vorkommen des betreffenden Namens kommt nicht in Frage, da ein solcher Name in einem anderen Kontext unterschiedlich definiert sein könnte. Man könnte die Speicherung der Positionen der einzelnen Token auch umgehen, indem man sämtliche Zeichen innerhalb des Syntaxbaumes repräsentiert. Dies ist jedoch aufgrund des Präprozessors und der üblicherweise recht früh stattfindenden Ausfilterung von sogenannten Whitespaces (Leerzeichen, Kommentare,..) nicht möglich. Das Endergebnis des Parsers muss also in einer Beziehung stehen zwischen den fertig bearbeiteten Token und deren Position in der Ursprungsdatei. Die einfachste Möglichkeit hierzu ist, in einer ersten Stufe den einzelnen Zeichen Positionsangaben zuzuordnen. Diese Angaben werden dann während der Ausführung des Präprozessors und des Parsers beibehalten. Will man jedoch fertige Parser um diese Funktionalität erweitern, muss man genau wissen, wo der Sourcecode des Parsers modifiziert werden muss. Da kein Parser mit ausreichender Dokumentation gefunden wurde, kann dies in einer regelrechten Sisyphus-Arbeit enden. 5.2 Automatisches Layout Bei der Erstellung eines Klassendiagramms existieren Informationen über die vorhandenen Elemente – beispielsweise durch Interaktion mit dem Benutzer – aber die Positionen der Elemente innerhalb des Diagramms sind noch unbekannt. Muss der Benutzer diese Elemente anordnen, nimmt dies unnötige Zeit in Anspruch. Eine hilfreiche Funktion ist also das automatische Layouten, das in diesem Abschnitt beschrieben werden soll. Layoutalgorithmen basieren auf graphentheoretischen Verfahren. Zur Vereinfachung und Verallgemeinerung ist die Definition eines Graphs beschränkt auf eine Menge von Knoten und eine Menge von Kanten, wobei eine Kante (u,v) die beiden Knoten u und v verbindet. Die Knoten entsprechen im konkreten Fall den Klassen und Paketen, während die Kanten eine Repräsentation aller Verbindungen zwischen Knoten sind (u.a. Assoziationen, Generalisierungen, Aggregationen). 79 5.2 Automatisches Layout 5.2.1 Ästhetik Ein Layoutalgorithmus sollte ein möglichst verständliches Diagramm erzeugen, so dass Vererbungshierarchien und vernetzte Strukturen auf einen Blick erfasst werden können. Dazu müssen bestimmte Ästhetikkriterien erfüllt werden. Dies umfasst sowohl syntaktische als auch semantische Kriterien. Als syntaktische Kriterien gelten solche, die nicht die Bedeutung der Elemente berücksichtigen, beispielsweise die Anzahl der Kreuzungspunkte von Kanten. Im Gegensatz dazu stehen die semantischen Kriterien wie die Darstellung der Vererbungshierarchie. Als Ästhetikkriterien gelten: • Überschneidungen von Kanten Sind zu viele Überschneidungen vorhanden, ist eine gute Lesbarkeit nicht gewährleistet. • Abstand zwischen Knoten Die Knoten sollten sich nicht überlappen und ihr Abstand sollte nicht zu groß sein. • Länge der Kanten Ist der Abstand zwischen zwei Knoten gering, so resultiert dies nicht notwendigerweise in einer kurzen Verbindungslinie. Die Länge der entsprechenden Kante kann groß werden, wenn sie einen anderen Knoten umgehen muss, oder wenn die Anzahl der Kantenkreuzungen minimiert wird. • Abstand zwischen Kanten bzw. zwischen Knoten und Kanten Zur besseren Unterscheidung sollte eine Kante nicht zu dicht an einer anderen Kante oder einem Knoten sein. • Anzahl der Kantenknicke Bei Kantenknicken kann der Linienverlauf nicht so gut erfasst werden. Dies ist besonders wichtig bei orthogonalen Graphen, d.h. bei Graphen, in denen einzelne Kantensegmente entweder horizontal oder vertikal verlaufen. Bei Direktverbindungen und Kurven können keine Knicke auftreten. • Größe und Form des Diagramms Die zur Verfügung stehende Zeichenfläche sollte berücksichtigt werden. Kriterien hierfür sind die maximale Größe (zur Darstellung auf einem bestimmten Ausgabemedium), das Verhältnis der Höhe zur Breite sowie eine gleichmäßige Verteilung. 80 5.2 Automatisches Layout • Relative Position der Knoten Es hat sich eingebürgert, bei Generalisierungen die allgemeinere Klasse über der spezielleren Klasse zu platzieren. • Darstellung der Vererbungshierarchie Oft wird bei mehreren abgeleiteten Klassen der shared target style (siehe Abbildung 10) verwendet, um Klassenzusammenhänge besser zu visualisieren. Dabei treffen sich die Kanten aller abgeleiteten Klassen an einem Punkt, von dem aus eine Linie zur Basisklasse geht. 5.2.2 Layoutalgorithmen Es existieren mehrere Ansätze für ein automatisches Layout, die sich in Effizienz und Aufwand der Implementierung unterscheiden. Dabei werden verschiedene Kriterien berücksichtigt. Der Topology-Shape-Metrics-Ansatz Dieser Ansatz dient der Darstellung orthogonaler Graphen. Es werden drei grundlegende Eigenschaften unterschieden: • Topologie (Topology): Mit der Topologie wird die Anordnung der Elemente beschrieben. Zwei Graphen haben dieselbe Topologie, wenn ein Graph durch Umformung in den anderen überführt werden kann, ohne die Reihenfolge der Kanten zu ändern, die einzelne Innenflächen (faces) umschließen. • Form (Shape): Zwei Graphen haben dieselbe Form, wenn sie dieselbe Topologie besitzen und ein Graph durch Änderung der Kantenlängen in den anderen überführt werden kann. • Metriken (Metrics): Zwei Graphen haben dieselben Metriken, wenn sie dieselbe Topologie und Form besitzen und in Bezug auf Translation und Rotation kongruent sind. Metriken beschreiben die Größe der Knoten und die Länge der Kanten. Das Verfahren zur Erstellung des Layouts baut auf diesen drei Eigenschaften auf: Schritt 1: Planarisation (planarisation) Es wird eine planarer Graphen gesucht, d.h. ein Graph ohne Kantenkreuzungen. Kann eine Kantenkreuzung nicht vermieden werden, wird ein neuer temporärer Knoten an der Kreuzung eingeführt 81 5.2 Automatisches Layout und die Kanten entsprechend geändert. Dadurch wird die Topologie festgelegt. Schritt 2: Orthogonalisierung (orthogonalization) Mit der Topologie wird die Form ermittelt. Als Ergebnis entsteht für jede Kante eine Liste mit Kantenknicken. Schritt 3: Kompression (compaction) Es werden minimale Kantenlängen berechnet und somit das endgültige Layout festgelegt. Die in Schritt 1 eingeführten temporären Knoten werden wieder entfernt. Der Hierarchische Ansatz Hierbei wird auf hierarchische Strukturen – wie Vererbung – Rücksicht genommen durch die Aufteilung in mehrere horizontale Ebenen. Als Voraussetzung wird ein azyklischer gerichteter Graph angenommen. Das Verfahren arbeitet in den folgenden drei Schritten: Schritt 1: Ebenen-Zuweisung (layer assignment) Jeder Knoten wird einer Ebene E1, E2, ... En zugewiesen, so dass für alle Kanten (u,v) mit u Î Ei und v Î Ej gilt: i < j. Anschaulich besitzt jeder Knoten einer Ebene dieselbe y-Koordinate. Schritt 2: Kreuzungs-Reduktion (crossing reduction) Die Reihenfolge der Knoten jeder Ebene wird bestimmt, so dass die Anzahl der Kreuzungen minimiert ist. Schritt 3: X-Koordinaten-Zuweisung (x-coordinate assignment) Jedem Knoten wird eine x-Koordinate zugewiesen. Die geforderte Voraussetzung eines azyklisch gerichteten Graphen ist in der Praxis nicht immer vorhanden. Um zyklische gerichtete Graphen zu verarbeiten, wird für eine möglichst kleine Menge von Kanten die Richtung geändert, so dass der Graph azyklisch wird. Nach der Berechnung durch den Hierarchischen Ansatz wird die ursprüngliche Richtung wiederhergestellt. Ist der Graph ungerichtet, wird ähnlich verfahren und der Graph temporär in einen gerichteten transformiert. 82 5.2 Automatisches Layout Der Visibility-Ansatz In diesem Ansatz werden Kanten in Form von aufeinander folgenden Liniensegmenten beliebiger Richtung und Länge dargestellt. Auch dieser Ansatz ist in drei Schritte gegliedert: Schritt 1: Planarisation (planarization) Dieser Schritt entspricht dem ersten Schritt des Topology-ShapeMetrics-Ansatzes. Schritt 2: Sichtbarkeit (visibility) Jeder Knoten wird auf ein horizontales Segment (eine horizontale Linie beschränkter Länge) und jede Kante auf ein vertikales Segment abgebildet, so dass es keine Überschneidungen gibt. Schritt 3: Ersetzung (Replacement) Horizontale Segmente werden wieder durch Knoten und vertikale Segmente durch Kanten ersetzt. Der erste Schritt ist mit dem Topology-Shape-Metrics-Ansatz identisch. Der zweite Schritt ähnelt der Ebenen-Zuweisung des Hierarchischen Ansatzes. Somit kann dieses Verfahren als Kombination der beiden Ansätze aufgefasst werden. Der Vergrößerungs-Ansatz Hierbei werden neue Knoten und Kanten hinzugefügt und der Graph vergrößert, so dass ein Graph entsteht, der geradlinig gezeichnet werden kann: Schritt 1: Planarisation (planarization) Dieser Schritt entspricht dem ersten Schritt des Topology-ShapeMetrics-Ansatzes. Schritt 2: Vergrößerung (augmentation) Es werden Knoten und Kanten hinzugefügt, bis ein maximal planarer Graph entsteht. Dieser zeichnet sich dadurch aus, dass jede Innenfläche von genau drei Kanten umschlossen wird. Schritt 3: Dreiecks-Zeichnung (triangulation drawing) Jede Fläche wird in ein Dreieck umgewandelt. Die in Schritt 2 hinzugefügten Knoten und Kanten werden wieder entfernt. 83 5.3 Das Programm DiaClassma 5.3 Das Programm DiaClassma Das erstellte Programm trägt den Namen DiaClassma, da es Klassen in Form von Diagrammen darstellt. Es soll im Folgenden vorgestellt werden. 5.3.1 Übersicht DiaClassma besteht aus den in Abbildung 38 gezeigten Komponenten. Die Graphische Benutzungsoberfläche besteht aus den Klassen zur Darstellung von Fenstern mit Menüs sowie Ausgabefenstern und dem Klassendiagramm. Die Verbindung zum Parser und zu den vom Parser erstellten Daten geschieht über eine Klasse namens CDataDoc. Auf diese Weise können GUI oder Parser/Datenverwaltung ausgetauscht werden, ohne im gesamten Programm Verweise ändern zu müssen. Es gibt zwei globale Klassen, d.h. Klassen, die im gesamten Programm verfügbar sind und benutzt werden können: CGlobal und CLanguage. CGlobal bietet eine einfache Funktion, um einzelne Textzeilen zu speichern und abzurufen und ist gedacht für die Speicherung von Fehlermeldungen, die innerhalb bestimmter Komponenten auftauchen und an anderer Stelle ausgegeben werden. Momentan wird es benutzt, um Fehlermeldungen während des Parsens zu verwalten. In der Klasse CLanguage werden alle sprachspezifischen Texte festgehalten, wobei sich „Sprache“ hierbei auf natürliche Sprachen (deutsch, englisch, ..) bezieht. GUI CDataDoc Parser + Ausgabe Klassendiagramm Datenverwaltung Global CGlobal CLanguage Abbildung 38: Die Komponenten von DiaClassma 84 5.3 Das Programm DiaClassma Wann immer ein Text in einem Menü, einem Dialog o.ä. erscheint, wird er aus dieser Klasse geholt. Auf diese Weise kann das Programm einfach um die Unterstützung zusätzlicher Sprachen erweitert werden. Zusätzlich zu diesen beiden Klassen gibt es zwei Enumeratoren, die im gesamten Programm verwendet werden können. Dies ist zum Einen enAccSpec (Kurzform für „enum Access Specifier“), das für die Spezifizierung der Sichtbarkeit (public, private, protected) zuständig ist, und zum Anderen enItem, das den Typ eines Datenelements darstellt (u.a. Methode, Klasse, Datei, Enumerator). 5.3.2 Externe Komponenten Es wurden zwei Komponenten von CSizingControlBar und CMemDC. anderen Autoren verwendet: CSizingControlBar ist ein kleines Fenster, das sich an den Rändern des Programmfensters andocken oder auch als eigenständiges Fenster existieren kann. Es hat eine Titelleiste, kann in der Größe verändert werden und kann ein beliebiges Fenster in seiner Mitte darstellen. In Diaclassma wird es benutzt zur Darstellung der Baumstruktur, in der die Klassen und Diagramme verwaltet werden, sowie für ein Ausgabefenster. CSizingControlBar kann unter [W3-SiCoBa] gefunden werden. CMemDC erstellt anhand eines gegebenen Gerätekontextes einen kompatiblen Kontext im Speicher. Ein Gerätekontext ist eine Datenstruktur in Windows, die Informationen über das Zeichen mit einem bestimmten Gerät (z.B. Monitor, Drucker..) enthält. Will man etwas auf dem Bildschirm ausgeben, dann geschieht dies mit Hilfe eines solchen Gerätekontextes. Beim Zeichnen eines Bildes wird zuerst das komplette Bild mit der Hintergrundfarbe überschrieben und anschließend komplett neu erstellt. Wird dies direkt auf dem Bildschirm ausgegeben, dann kommt es zum Flimmern. Benutzt man CMemDC, dann kann das Bild zuerst vollständig im Speicher vorberechnet und gezeichnet werden und wird erst danach auf dem Bildschirm ausgegeben. CMemDC wurde für Diaclassma um die Unterstützung von Zeichensätzen erweitert. Es wird unter [W3-CodePro] zum Download angeboten. 85 5.3 Das Programm DiaClassma 5.3.3 Graphische Benutzungsoberfläche Die GUI ist in drei Bereiche unterteilt. Im unteren Bereich ist ein Ausgabefenster, in dem jederzeit Meldungen ausgegeben werden können. Durch die Verwendung der Komponente CSizingControlBar ist dieses Fenster nicht an die vorgegebene Position gebunden. Am linken Rand befindet sich der Explorer. Dies ist ein Fenster, in dem die Elemente des Projekts in einer Baumstruktur verwaltet werden. Der restliche Bereich ist für die Diagramme vorgesehen. Zur Zeit können nur Klassendiagramme erstellt werden. Über den Menüpunkt Datei – Öffnen können neue Sourcecode-Dateien hinzugefügt und mit Diagramm – Neues Klassendiagramm ein neues Klassendiagramm erzeugt werden. Entsprechende Symbole sind in der Toolbar vorhanden. Dort sind ebenfalls drei Symbole für eine Zoom-Funktion untergebracht, die alle die Form einer Lupe haben. Das erste Symbol – mit einem „+“ in der Mitte – dient dem Vergrößern. Mit dem zweiten Symbol – in dessen Mitte ein „-“ angezeigt wird – wird das Diagramm verkleinert. Das letzte Symbol stellt die Originalgröße wieder her. Abbildung 39: Screenshot DiaClassma 86 5.3 Das Programm DiaClassma 5.3.3.1 Explorer Der Explorer ist für die Verwaltung sämtlicher Elemente des Programms zuständig. Dies sind die per Reverse Engineering gefundenen oder in DiaClassma angelegten Sprachelemente wie Klassen, Methoden u.a. sowie die zugehörigen Sourcecode-Dateien und Diagramme, die alle in einer Baumstruktur dargestellt werden. Durch die Verwendung der Komponente CSizingControlBar kann der Explorer sich an den Rand des Programms andocken oder als frei schwebendes Fenster fungieren. Jedes Element der Baumstruktur wird durch ein vorangestelltes Icon visualisiert. Diese Icons sind im Folgenden aufgelistet. Sprachelemente: public protected private Klasse Attribut Methode Konstruktor Destruktor Enumerator Namespace Verwaltungselemente: Implementierungssicht: Alle Elemente, die direkt mit dem Sourcecode abgeglichen werden. Designsicht: Elemente, die keinen direkten Bezug auf den Sourcecode haben. Sprachelemente entsprechend obiger Tabelle (Klassen, Methoden, ...) Dateien Diagramme Einzelne Datei mit Dateinamen Klassendiagramm 87 5.3 Das Programm DiaClassma 5.3.3.2 Klassendiagramm Das Klassendiagramm wird über das Kontextmenü gesteuert. Durch den ersten Punkt „Angezeigte Klassen auswählen“ öffnet sich ein Dialogfenster, in dem alle Klassen dargestellt werden. Die Ansicht entspricht der des Explorers, allerdings werden nur Klassen und Namensräume ohne Methoden und Attribute dargestellt. Hier können die Klassen ausgewählt werden, die in dem Diagramm angezeigt werden sollen. Die nächsten vier Punkte sind nur verfügbar, wenn im Diagramm mindestens eine Klasse selektiert wurde. Die Funktionalität gilt dann für alle selektierten Klassen. Mit „Farbe ändern“ kann eine individuelle Farbe zugewiesen werden. Der Punkt „Layout“ ist untergliedert in die Funktionen „horizontal ausrichten“, „horizontal gleichmäßig verteilen“, „vertikal ausrichten“ und Abbildung 40: Das Kontextmenü „vertikal gleichmäßig verteilen“. Bei der ersten im Klassendiagramm von Funktion wird von allen selektierten Klassen die DiaClassma minimale Position in vertikaler Richtung ermittelt und alle Klassen auf diese Position gesetzt. Die Elemente werden also an einer horizontalen Linie ausgerichtet. Durch die Funktion „horizontal gleichmäßig Verteilen“ werden die Abstände zwischen den selektierten Klassen in horizontaler Richtung gleichgesetzt. Entsprechendes gilt für die vertikale Richtung. Die Menüpunkte „Attribute“ und „Methoden“ enthalten jeweils die Auswahlmöglichkeiten „Anzeigen“, „Verbergen“ und Sortieren“ und sind selbsterklärend. Der anschließende Menüpunkt „Exportieren“ speichert das Diagramm als Graphik-Datei. Dabei wird die Programmierschnittstelle GDI+ (graphics design interface) von Windows benutzt, um Dateien in unterschiedlichen Formaten zu speichern (beispielsweise .bmp, .jpg oder .png). Mit dem letzten Menüpunkt „Eigenschaften“ lassen sich die Größe des Diagramms und eine Bezeichnung festlegen. 5.3.4 Parser Der Parser ist für das Reverse Engineering zuständig. Alle diesbezüglichen Klassen haben den Präfix „CCPP“, wobei das erste Zeichen für „Class“ steht und 88 5.3 Das Programm DiaClassma die nächsten drei Zeichen für die zu analysierende Programmiersprache C++.Der erste Schritt des Parsens ist das Einlesen einer Sourcecode-Datei, das Erstellen von Token und das Ausführen von Präprozessor-Direktiven. Dafür ist die Klasse Abbildung 41: Der Parser in DiaClassma, erstellt mit DiaClassma CCPPLexer zuständig. Nach dem Lesen der Datei werden Trigraph-Sequenzen ersetzt, Zeilen zusammengefügt und Token erstellt. Dabei bedient sich der Lexer der Klasse CCPPToken, die Informationen über die verschiedenen Arten von Token, Schlüsselwörter sowie Punktuatoren und Operatoren enthält. Jedes so entstandene Token wird durch ein Objekt der Struktur stCPPToken gespeichert und anschließend durch den Präprozessor verarbeitet. Zur Definition von Makros sowie zur Makroersetzung zeichnet sich die Klasse CCPPMacro verantwortlich, die eng mit CCPPLexer verzahnt ist. Jedes Makro wird durch die nested Struktur stCPPMakro repräsentiert, die Informationen über Namen, Parameter und Ersatztext enthält. Eine boolesche Variable gibt an, ob das Makro noch aktiv ist. Dies wird benötigt für den Fall, dass es durch eine #undef-Direktive ungültig wurde. Für die Verarbeitung von #include-Direktiven wurde ein spezielles Token eingeführt, über das auf ein weiteres Objekt der Klasse CCPPLexer zugegriffen werden kann. Wird eine entsprechende Direktive gefunden, dann wird rekursiv ein solches Objekt erstellt und in die Liste der Token eingefügt. Es entsteht eine Baumstruktur, in der jeder Knoten Abbildung 42: Baumstruktur des Lexers entweder ein Token der Sprache oder ein weiteres CCPPLexer-Objekt darstellt (siehe Abbildung 42). Nach dem Erstellen der Token und dem Ausführen des Präprozessors wird der 89 5.3 Das Programm DiaClassma Code auf Konstruktionselemente hin untersucht und die gefundenen Elemente einer verwaltenden Datenstruktur hinzugefügt. Dies wird durch die Klasse CCPPAnalyzer realisiert, die die Token von CCPPLexer erhält. Da die Token der Form stCPPToken, die in CCPPLexer gespeichert sind, für die Analyse nicht ausreichen, wurde ein erweitertes Token erstellt. stCPPTokenEx aggregiert stCPPToken und enthält zusätzlich den Namen der Datei, aus der das Token stammt. Bei der Analyse wird von Lookaheads variabler Länge Gebrauch gemacht, d.h. CCPPAnalyzer liest voraus, bis eine Entscheidung getroffen werden kann, welches Konstrukt vorliegt. Aufgrund der Schwierigkeiten mit der Grammatik des C++-Standards wurde auf das Aufbauen eines Ableitungsbaumes verzichtet und direkt nach den Sprachelementen Methode, Attribut, Klasse u.a. gesucht. 5.3.5 Datenverwaltung Durch das Reverse Engineering erhält man die Konstruktionselemente, also u.a. Klassen, Methoden, Attribute. Die Verwaltung dieser Elemente wird durch die Datenstruktur realisiert, die in Abbildung 43 aufgezeigt wird. Sie besteht aus mehreren Klassen, deren Namen alle mit CData beginnen. Zusätzliche nested Strukturen (stFilePos, stPosAndRef und stClassAndAccess) sind zur Speicherung einzelner Informationen konzipiert und enthalten keine Methoden, sondern lediglich einige Attribute. Die Strukturen haben keine öffentliche Sichtbarkeit; der Zugriff auf sie ist durch Methoden gekapselt. Alle Konstruktionselemente werden in Klassen gespeichert, die von CDataItem abgeleitet sind. CDataItem enthält grundlegende Informationen zur Beschreibung des Typs des Items, zur Speicherung des Namens und einer eindeutigen Identifikationsnummer (ID). Weiterhin werden Referenzen verwaltet, die alle Positionen im Sourcecode angeben, an denen der Name dieses Items auftaucht. Die Gemeinsamkeiten von Variablen bzw. Attributen und Funktionen bzw. Methoden werden durch die Klasse CDataVariableOrMethod realisiert. Dies betrifft den Datentyp im Fall einer Variablen bzw. eines Attributs und den Datentyp des Rückgabewertes im Fall einer Funktion bzw. einer Methode. Ist der Datentyp eine Klasse, dann wird auch ein Verweis auf die entsprechende Klasse gesetzt. Variablen und Attribute werden durch die Klasse CDataVariable verwaltet, die von CDataVariableOrMethod abgeleitet ist und dessen 90 5.3 Das Programm DiaClassma Funktionalität um einen Initialwert sowie Informationen über Aggregation und Komposition erweitert. Für Funktionen und Methoden ist die Klasse CDataMethod zuständig, die die Funktionalität von CDataVariableOrMethod um Funktionsparameter erweitert, die in Form einer doppelt verketteten Liste von Variablen gespeichert werden. Abbildung 43: Die Datenverwaltung in DiaClassma, erstellt mit DiaClassma Analog zu dieser Vorgehensweise werden die Gemeinsamkeiten von Klassen und Namespaces durch eine separate Klasse realisiert: CDataContainer. Hier werden Informationen über enthaltene Enumeratoren, Variablen, Methoden und enthaltene Container sowie eine Liste von Alias-Namen verwaltet. Die definierten Subklassen sind CDataClass und CDataNamespace. Letzteres erweitert die Funktionalität um die Benutzung anderer Namespaces, die in C++ durch die using-Direktive mit dem Schlüsselwort using festgelegt werden können. CDataClass erweitert die Funktionalität um Informationen über Basisklassen und abgeleitete Klassen. In CDataFile werden Dateien und in CDataEnum Enumeratoren gespeichert. Sämtliche Daten werden in CData gespeichert. Diese Klasse enthält eine Liste mit Dateien sowie einen Zeiger auf einen globalen Container, der alle anderen Sprachelemente enthält. Weiterhin werden in CData IDs verwaltet. Durch die Verwendung von statischen Variablen wird keine ID doppelt vergeben. 91 Kapitel 6 Bewertung Es gibt viele Möglichkeiten zur graphischen Darstellung von Sourcecode; als Standard hat sich inzwischen UML etabliert. Einige der Diagramme dieser Notation lassen sich jedoch nicht ohne weitere Informationen des Benutzers in Sourcecode abbilden oder aus vorhandenem Sourcecode gewinnen. Manche Modellelemente können überhaupt nicht in Code übersetzt werden. Da unterschiedliche Modellelemente in verschiedenen Diagrammenarten vorkommen können, ist auch die Übersetzung dieser Diagramme problematisch. Dies wird auch in der Analyse zum Stand der Technik deutlich. Die getesteten Systeme sind für die Analyse einer konkreten Aufgabenstellung, das Design von neuem Sourcecode sowie für die Erstellung von informativen Graphiken zur nachträglichen Dokumentation konzipiert worden, und hier werden auch ihre Stärken deutlich. Bei der Unterstützung der eigentlichen Programmierung, also der Visualisierung von Sourcecode und der vernetzten Strukturen der Sprachelemente während der Programmierung, offenbaren sich bei den getesteten Programmen einige Mängel. Oft wird ein plattformunabhängiges Modell (PIM: plattform independant model) erstellt und plattformabhängige Informationen – wie das Layout des Sourcecodes – vernachlässigt. Das Resultat ist ein veränderter Code, auch wenn im Modell keine oder nur minimale Änderungen vorgenommen wurden. Zusätzlich eingefügte Kommentare und Veränderungen in der Struktur der SourcecodeDateien (Klassen werden in separate Dateien ausgelagert, Einrückungen verschwinden) verfremden das Aussehen des Codes und erschweren ein weiterarbeiten. Ein solches Ergebnis ist jedoch nicht wünschenswert. Sinnvoller ist hier eine Erweiterung von Texteditoren um graphische Elemente. Parallel zur Bearbeitung des Sourcecodes in Textform sollte die graphische Darstellung der Sprachelemente berechnet werden, die auf Wunsch des Benutzers wahlweise angezeigt oder ausgeblendet werden kann. So ist sowohl die Bearbeitung des Textes als auch die Bearbeitung der Graphik möglich und die Konsistenz bleibt gewährleistet. Zur graphischen Darstellung können einige Elemente von UML benutzt werden, auch wenn sie nicht für diesen Zweck gedacht sind. Kleinerer Modifikationen – 92 Kapitel 6 Bewertung wie die Benutzung von Symbolen anstatt Textzeichen für die Sichtbarkeit – erleichtern das visuelle Erfassen von Sachverhalten. Die prototypische Implementierung in Form des Programms DiaClassma ist aufgrund der beschriebenen Probleme bezüglich der Zielsprache C++ leider nicht sehr weit fortgeschritten, weshalb das erarbeitete Konzept nicht im Praxiseinsatz getestet werden konnte. Auch ein Vergleich zu den im Kapitel 3 Stand der Technik beschriebenen Programmen sollte nicht gezogen werden aufgrund des frühen Entwicklungsstadiums von DiaClassma, und weil das Programm mehrfach mit dem Sourcecode getestet wurde, der auch bei der Untersuchung der Merkmale der Testprogramme zum Einsatz kam. 93 94 Anhang A Glossar Anhang A Glossar Abstrakt Eine Methode ist abstrakt, wenn sie nur einen Methodenrumpf ohne Implementierung hat. Eine Klasse ist abstrakt, wenn sie eine abstrakte Methode hat. Eine solche Klasse kann nicht instanziert werden. Aggregation In der objektorientierten Programmierung ist die Aggregation eine spezielle Beziehung zwischen zwei Klassen, wobei eine Klasse ein Teil einer anderen Klasse ist. Beispiel: Klasse Auto aggregiert die Klasse Reifen. siehe Komposition Basisklasse siehe Superklasse CASE Computer Aided Software Engineering CASE bezeichnet die Computer seitige Unterstützung während der SoftwareEntwicklung. Compiler Ein Programm zur Übersetzung eines Quellcodes einer Sprache in eine Zielsprache ohne die Semantik zu verändern. Oft ist die Zielsprache Maschinensprache. Design Pattern Ein Muster bzw. eine Schablone für ein Design. Einfachvererbung Kann in einer objektorientierten Programmiersprache eine Klasse von nur einer anderen Klasse erben, so spricht man von Einfachvererbung. siehe Mehrfachvererbung, Vererbung Forward Engineering Durch das Forward Engineering wird aus Klassen mit Attributen und Methoden Sourcecode erzeugt. siehe Reverse Engineering, Roundtrip Engineering 95 Anhang A Glossar Generalisierung Eine Beziehung von einem spezielleren Objekt zu einem allgemeineren Objekt. In der objektorientierten Programmierung besteht eine Generalisierung von einer Subklasse zu seiner Superklasse. Die entgegengesetzte Richtung wird Spezialisierung genannt. siehe Subklasse, Superklasse GUI Graphical User Interface (Graphische Benutzungsoberfläche) Eine graphische Schnittstelle, durch die ein Benutzer mit dem Computer kommunizieren kann. GUIs werden meist mit Fenstern, Menüs und graphischen Symbolen realisiert. Einen weit verbreiteter Typ von GUIs bezeichnet man als WIMP-Interface (Windows, Icons, Mouse, and Pointer). Intrinsische Datentypen siehe primitive Datentypen Kapselung Ein Zugriff auf die interne Struktur ist nur über klar definierte Schnittstellen möglich. So kann es beispielsweise setter- bzw. getter-Methoden für ein Attribut einer Klasse geben, so dass das Setzen eines Attributs bzw. die Rückgabe des Wertes eines Attributs nur über diese Methoden erlaubt ist. Komposition Die Komposition stellt eine besondere Form der Aggregation dar. Dabei kann das Teil nicht ohne das Ganze existieren. Beispiel: die Klassen Haus und Zimmer. siehe Aggregation MDA Model Driven Architecture Standard der OMG zur plattformneutralen Erstellung von Software-Systemen. siehe OMG, UML Mehrfachvererbung Kann in einer objektorientierten Programmiersprache eine Klasse von mehreren anderen Klassen erben, so spricht man von Mehrfachvererbung. siehe Einfachvererbung, Vererbung 96 Anhang A Glossar MOF Meta-Object Facility Standard der OMG, die das MOF-Modell definiert. Dies ist ein Meta-Metamodell, das eine gemeinsame Grundlage verschiedener Metamodelle beschreibt. siehe OMG, 2.2.2.1 Die Four Layer Modeling Architecture Multiplizität Bei einer Beziehung zwischen zwei Dingen gibt die Multiplizität (Vielfachheit) an, wie viele Elemente der einen Sorte mit wie vielen Elementen der anderen Sorte von Dingen in Beziehung stehen. Nested Klassen Wird eine Klasse innerhalb einer anderen Klasse definiert, dann nennt man diese „nested“ (engl. für eingenistet). OCL Object Constraint Language OCL wird in UML benutzt, um Einschränkungen und Zusicherungen zu definieren. OCL ist ein Standard der OMG. siehe UML, OMG OMG Object Management Group Non-for-Profit Konsortium, das u.a. UML standardisiert. siehe UML, MDA, XMI, OCL OOPSLA Object-Oriented Programming, Systems, Languages and Applications Eine von der amerikanischen Informatiker-Vereinigung ACM jährlich durchgeführte Konferenz zum Thema Objekt-Orientierung und SoftwareEngineering. Polymorphie Von Polymorphie spricht man, wenn verschiedene Klassen auf die gleiche Nachricht unterschiedlich reagieren. Das Verhalten ist dann nicht vorhersagbar, sondern polymorph (vielgestaltig). Dies ist der Fall, wenn eine Klasse eine Methode definiert, die von mehreren abgeleiteten Klassen überschrieben und um spezifische Funktionalität erweitert wird. 97 Anhang A Glossar primitive Datentypen Datentypen einer Programmiersprache, die ohne vorherige Definition benutzt werden können (z.B. int, char, float). Andere Bezeichnung: intrinsische Datentypen Punktuator Punktuatoren sind Zeichen einer Programmiersprache, die keinen Wert darstellen, aber eine syntaktische und semantische Bedeutung haben. In C++ sind dies u.a. die folgenden Zeichen: { } [ ] ^ + ; , . Refaktorisierung Bei der Refaktorisierung (engl.: Refactoring) wird der Quellcode einer Software umstrukturiert, ohne seine Semantik und Funktionalität zu verändern. Reverse Engineering Durch das Reverse Engineering (engl. für umgekehrt entwickeln, rekonstruieren) werden aus einem fertigen System (hier: ein Sourcecode in Textform) die Konstruktionselemente wie Klassen mit Attributen und Methoden gefunden. siehe Forward Engineering, Roundtrip Engineering Roundtrip Engineering Ist eine Kombination aus Reverse und Forward Engineering, bei der aus Sourcecode die Konstruktionselemente (Klassen, Methoden u.a.) gefunden und evtl. verändert werden, um dann wieder in Sourcecode umgewandelt zu werden. Wird nichts an den Konstruktionselementen verändert, dann sollte im Idealfall der ursprüngliche Code wieder entstehen. siehe Forward Engineering, Reverse Engineering Schnittstelle Eine Schnittstelle (engl: Interface) ist ein Teil eines Systems zum Austausch von Informationen. In einer Programmiersprache ist eine Schnittstelle eine Klasse, die nur abstrakte Methoden hat. Eine abgeleitete Klasse muss diese Methoden um Funktionalität erweitern. siehe abstrakt Spezialisierung siehe Generalisierung 98 Anhang A Glossar Subklasse Bei der Vererbung der objektorientierten Programmierung erbt die Subklasse alle Attribute und Methoden von der Superklasse. Auch Unterklasse oder abgeleitete Klasse genannt. siehe Superklasse Superklasse Bei der Vererbung der objektorientierten Programmierung erbt die Subklasse alle Attribute und Methoden von der Superklasse. Auch Oberklasse oder Basisklasse genannt. siehe Subklasse Token Die kleinste sinngebende Einheit einer Programmiersprache. UML Unified Modeling Language UML ist eine graphische Sprache zur Visualisierung, Spezifizierung, Konstruktion und Dokumentation von Software-Systemen. Standardisiert von OMG. siehe OMG, UML, MDA, XMI, OCL Vererbung Ein Konzept aus der objektorientierten Programmierung. Dabei „erbt“ eine Klasse alle Attribute und Methoden einer anderen Klasse. Whitespace Die Zeichen eines Textes, die im Texteditor nicht sichtbar sind. Dies sind u.a. Leerzeichen, Tabulator und das Zeilenumbruchzeichen. In vielen Programmiersprachen werden sie in einer recht frühen Phase der Übersetzung durch den Compiler ausgeblendet. In der Whitespace-Programmiersprache hingegen besteht der Quellcode ausschließlich aus Whitespace-Zeichen. XMI XML Metadata Interchange XMI beschreibt, wie Instanzen der Elemente des MOF-Modells auf XML Definitionen abgebildet werden können. Somit ist ein Austausch von UMLModellen möglich. siehe OMG, UML 99 Anhang B Stand der Technik Anhang B Stand der Technik Informationen Titel Hersteller Homepage Sprachen Testversion Preis ClassBuilder 2.4 Alpha 1.7 http://sourceforge.net/ C++ Vollversion 0 Enterprise Architect 4.0 (729) C++, Java, C#, 30 Tage Sparx Systems VB.Net, www.sparxsystems.com.au Delphi, PHP 125 $ - 225 $ Jumli 1.4 Schwäbisch Hall AG www.jumli.de/ C++, Java, C# Vollversion 0 Metamill v3.1 (build 556) Metamill www.metamill.com C++, Java, ANSI C, C# 30 Tage, max. 20 Elemente pro Diagramm 125 $ Windows 75 $ Linux Max. 30 Klassen 995 $ 1245 $ ObjectDomain R3 (build 292) C++, Java, ObjectDomain Python, IDL www.objectdomain.com ObjectiF 4.7 (Enterprise) microTOOL www.microtool.de C++, Java, VB Vollversion 0 3828 € Rational Rose Enterprise Edition 2003 IBM www.ibm.com C++, Java, VB, 14 Tage Ada83, Ada95, CORBA, Oracle8 Select Component Factory 5.0 C++, Java, VB, Max. 1 Select Business Solutions C#, SQL, „and Diagramm www.selectbs.com many other“ pro Typ 1795 $ 3787 € Download 3884 € Post 100 Anhang B Stand der Technik Titel Hersteller Homepage Sprachen Testversion Preis UMLStudio 7.1 (Build 746) PragSoft www.pragsoft.com Java, C++, CORBA IDL, Ada95 (nur Forward: Forte TOOL, Maxim) „cannot save large projects“ WithClass 2000 Enterprise 6.0 C++, Java, 30 Tage MicroGold Delphi, VB, www.microgold.com IDL, Perl, PHP, C# 500 $ Commercial 250 $ Private, 125 $ Educational 600 $ für Studenten, 800 $ sonst Merkmale Bemerkungen: ü vorhanden m nicht vorhanden Hierarchie: Bietet das Programm ein automatisches Layout an, dann zeigt diese Spalte, ob die Vererbungsbeziehung zwischen Klassen in hierarchischer Form (ü), oder ob sie wie jede andere Beziehung (m) dargestellt werden. Datei pro Klasse: Gibt an, ob beim Forward Engineering für jede Klasse/Struktur/Union eine separate Datei angelegt wird. Vererbung, Aggregation, Komposition: Zeigt die Darstellung dieser drei Konzepte der objektorientierten Programmierung nach dem Reverse Engineering oder „m“, falls die entsprechende Beziehung nicht aus vorhandenem Sourcecode gewonnen werden konnte. 101 Komposition Aggregation Vererbung Roundtrip Datei pro Klasse Forward ü m ü ü ü ü ü ü ü ü ü Enterprise Architect ü ü m ü ü m ü ü m m m Jumli ü ü m m ü m ü ü ü m m m Metamill ü ü m m ü m ü ü m m m m m m ü ü ü ü ü m m ü ObjectiF ü m m m ü m ü ü ü m m Rational Rose ü m m ü ü ü ü ü ü m m Select Component Factory ü m m m ü m ü ü ü m m UMLStudio ü m m ü1) ü ü ü ü m m ü WithClass 2000 ü m m ü1) ü m ü ü ü ObjectDomain R3 ü ü 2) 2) Zoom ClassBuilder Linux Reverse Hierarchie Engineering Übersicht Automatisches Layout Open Source Name Windows Anhang B Stand der Technik ü 1) m m m m ü 1) Nur beim automatisch erstellten ersten Diagramm 2) Läuft auf allen Java-kompatiblen Systemen 102 Anhang C Inhalt der beigefügten CD-ROM Anhang C Inhalt der beigefügten CD-ROM Bilder Abbildungen Zeichnungen aus der Diplomarbeit DiaClassma Screenshots des Programms DiaClassma Stand der Technik Screenshots der Programme zur Analyse „Stand der Technik“ DiaClassma Das erstellte Programm in ausführbarer Form DiaClassma Source Sourcecode des Programms DiaClassma CPPParser Komponente Parser Data Klassen zur Datenverwaltung Debug DiaClassma, kompiliert als Debug-Version extern Komponenten von anderen Autoren sizebar Komponente CSizingControlBar hlp Hilfe-Dateien von DiaClassma Release DiaClassma, kompiliert als Release-Version res Ressourcen (Icons sowie Bilder für Toolbar und Explorer ) Dokumente Diplomarbeit in den Formaten OpenOffice und PDF 103 Anhang D Abbildungsverzeichnis Anhang D Abbildungsverzeichnis Abbildung 1: Objektorientierte Methoden und Notationen im historischen Kontext........................................................................................... 9 Abbildung 2: Die drei Amigos: Grady Booch, James Rumbaugh, Ivar Jacobsen (v.li.n.re.)...................................................................................... 11 Abbildung 3: Four Layer Modeling Architecture von UML.................................13 Abbildung 4: Core Pakete der UML Infrastructure ..............................................14 Abbildung 5: Icons für die Stereotypen Control, Entity, Boundary...................... 15 Abbildung 6: Beispiel für Constraints...................................................................16 Abbildung 7: UML Diagramm mit Kommentar................................................... 18 Abbildung 8: UML Klassen und ein Objekt......................................................... 21 Abbildung 9: UML Assoziationen........................................................................ 22 Abbildung 10: UML Aggregation und Komposition............................................ 23 Abbildung 11: UML Generalisierung................................................................... 24 Abbildung 12: UML Abhängigkeiten .................................................................. 25 Abbildung 13: UML Schnittstellen und Ports.......................................................26 Abbildung 14: UML Parametrisierte Klassen (Templates)...................................27 Abbildung 15: UML Pakete.................................................................................. 29 Abbildung 16: UML Model (a) und Subsystem (b).............................................. 30 Abbildung 17: UML Komponenten...................................................................... 30 Abbildung 18: UML Verteilungsdiagramm.......................................................... 31 Abbildung 19: UML Kompositionsstrukturdiagramm mit Parts und Kollaborationen............................................................................ 32 Abbildung 20: UML Anwendungsfalldiagramm.................................................. 33 Abbildung 21: UML Aktivitätsdiagramm, entnommen aus [UML2Toolkit]....... 34 Abbildung 22: UML Zustandsdiagramm.............................................................. 34 Abbildung 23: UML Sequenzdiagramm, entnommen aus [UML2Toolkit]..........35 Abbildung 24: UML Interaktionsübersichtsdiagramm......................................... 36 Abbildung 25: UML Timingdiagramm.................................................................37 Abbildung 26: UML Kommunikationsdiagramm.................................................37 Abbildung 27: Screenshot ClassBuilder 2.4 Alpha 1.7.........................................39 Abbildung 28: Screenshot Jumli 1.4..................................................................... 41 Abbildung 29: Screenshot Metamill v3.1 (build 556) ..........................................42 Abbildung 30: Screenshot ObjectDomain R3 (build 292).................................... 44 Abbildung 31: Screenshot objectiF 4.7................................................................. 45 Abbildung 32: Screenshot Rational Rose Enterprise Edition............................... 48 Abbildung 33: Screenshot WithClass 2000 Enterprise 6.0................................... 50 Abbildung 34: Zeiger und Referenzen.................................................................. 58 104 Anhang D Abbildungsverzeichnis Abbildung 35: Abbildung 36: Abbildung 37: Abbildung 38: Abbildung 39: Abbildung 40: Abbildung 41: Abbildung 42: Abbildung 43: Logische Komponenten................................................................ 59 Funktionsaufrufe in Klassendiagrammen..................................... 61 Methoden...................................................................................... 63 Die Komponenten von DiaClassma..............................................84 Screenshot DiaClassma.................................................................86 Das Kontextmenü im Klassendiagramm von DiaClassma........... 88 Der Parser in DiaClassma, erstellt mit DiaClassma......................89 Baumstruktur des Lexers...............................................................89 Die Datenverwaltung in DiaClassma, erstellt mit DiaClassma.... 91 105 Anhang E Literaturverzeichnis Anhang E Literaturverzeichnis [BaEaTaTo99] Guiseppe Di Battista, Peter Eades, Roberto Tamassia, Ioannis G. Tollis: „Graph Drawing: Algorithms of the Visualisation of Graphs“, Prentice Hall PTR, Upper Saddle River, New Jersey, 1999 [Beck99] Kent Beck: „Extreme Programming Explained: Embrace Change“, Addison-Wesley Professional, ISBN: 0201616416, 1999 [BeCu89] Kent Beck, Ward Cunningham: „A Laboratory For Teaching Object-Oriented Thinking“, OOPSLA 1989 Paper [Booch91] Grady Booch: „Object-Oriented Design with Applications“, Benjamin /Cummings Publishing Company Inc. Redwood City, California, 1991 [Booch94] Grady Booch: „Object-Oriented Analysis And Design with Application“, 2nd Edition, Benjamin / Cummings Publishing Company Inc. Redwood City, California, 1994 [Chen76] Peter Pi-Shan Chen: „The Entity-Relationship Model. Toward a Unified View of Data“, ACM Transactions on Database Systems, 1976 ACM-Press ISSN 0362-5915 [ChLeFa93] Dennis Champeaux, Doug Lea, Penelope Faure: „ObjectOriented System Development“, Addison-Wesley Professional, ISBN 0-201-56355-X, 1993 [Cole94] D. Coleman, P. Arnold, S. Bodoff, C. Dollin, H. Gilchrist, F. Hayes, P. Jeremes: „Object-Oriented Development - The FUSION Method“, Prentice Hall, Englewood Cliffs, New Jersey, 1994 [Cook94] Steve Cook, John Daniels: „Designing Object Systems: ObjectOriented Modeling with Syntropy“, Prentice Hall, New York, 1994 [CoPa96] M. K. Coleman, D. S. Parker: „Aesthetics-based Graph Layout for Human Consumption“, Software - Practice & Experience, 26 (12):1415-1438, 1996 106 Anhang E Literaturverzeichnis [CoYo91a] P. Coad, E. Yourdon.: „Object-Oriented Analysis“, 2nd Edition., Prentice Hall, Englewood Cliffs, New Jersey, 1991 [CoYo91b] P. Coad, E. Yourdon.: „Object-Oriented Design“, Prentice Hall, Englewood Cliffs, New Jersey, 1991 [DiTa93] T. Dillon, P.L. Tan: „Object-Oriented Conceptual Modeling“, Prentice Hall of Australia Pty Ltd. Sydney, 1993 [Eich02] H. Eichelberger: „Aesthetics of Class Diagrams“, Proc. of the First IEEE International Workshop on Visualizing Software for Understanding and Analysis, Vissoft 2002 , S. 23-31, 2002 [Fire93] D.G. Firesmith: „Object-Oriented Requirements Analysis And Logical Design: A Software Engineering Approach“, John Wiley & Sons New York, New York, 1993 [Graham95] Ian S. Graham, Aan Graham: „Migrating to Object Technology“, Addison-Wesley, ISBN: 0201593890, 1995 [HeEd94a] B. Henderson-Sellers, J. Edwards: „The Working Object: ObjectOriented Software Engineering: Methods and Management“, Prentice-Hall, 1994 [HeEd94b] B. Henderson-Sellers, J.M. Edwards: „Book Two of ObjectOriented Knowledge: The Working Object: Object-Oriented Software Engineering: Methods and Management“, Prentice Hall, ASIN: 0130939803, 1994 [Hend96] B. Henderson-Sellers: „The OPEN methodology“, Object Magazine (Nov 1996), 6(9), 56-59, 1996 [HMU02] John E. Hopcroft, Rajeev Motwani, Jeffrey D. Ullman: „Einführung in die Automatentheorie, Formale Sprachen und Komplexitätstheorie“, Pearson Studium, 2002. [Jaco92] Ivar Jacobson, M. Christerson, P. Jonsson, G. Övergaard: „Object-Oriented Software Engineering: A Use Case Driven Approach“, Addison Wesley, Wokingham, England, 1992 [Jeckle04] Mario Jeckle, Vortragsfolien „UML 2.0 - Die neue Version der Standardmodellierungssprache“, http://www.jeckle.de, 2004 [Kruch98] Philippe Kruchten: „The Rational Unified Process“, AddisonWesley Pub Co, ASIN: 0201604590, 1998 107 Anhang E Literaturverzeichnis [Lewi90] J. Lewi, E. Steegmans, S. Van Baelen: „EROOS: EntityRelationship Based Object-Oriented Specifications“, Department of Computer Science, K.U.Leuven, CW Report 111, Leuven, B, 1990 [Meyer97] Bertrand Meyer: „Object-Oriented Software Construction“, 2nd Edition, Prentice Hall, ISBN 0-13-629155-4, 1997 [OpJo90] William Opdyke, Ralph Johnson: „Refactoring: An aid in designing application frameworks and evolving object-oriented systems“, Proceedings of Symposion on Object-Oriented Programming Emphasizing Practical Applications (SOOPPA), September 1990 [Robi92] P. J. Robinson: „Hierarchical Object-Oriented Design“, Prentice Hall International (UK) Ltd, 1992 [RuGo92] K. S. Rubin, A. Goldberg: „Object Behaviour Analysis“, Communications of the ACM, 35(9): 48-62, 9 1992 [Rumb91] James Rumbaugh, Michael Blaha, William Premerlani, Frederick Eddy, William Lorensen: „Object-Oriented Modeling And Design“, Prentice Hall, Englewood Cliffs, New Jersey, 1991 [SeGuWa94] Bran Selic, Garth Gullekson, Paul T. Ward: „Real-Time ObjectOriented Modeling“, Wiley, ISBN: 0471599174, 1994 [ShMe88] Sally Shlaer, Stephen J. Mellor: „Object-Oriented System Analysis - Modeling the World in Data“, Prentice Hall, Englewood Cliffs, New Jersey, 1988 [ShMe92] Sally Shlaer, Stephen J. Mellor: „Object-Lifecycles- Modeling the World in States“, Prentice-Hall, Englewood Cliffs, New Jersey, 1992 [SofDevUML2] Olaf Kath, Eckhardt Holz, Marc Born: „Softwaredevelopment mit UML 2“, Addison-Wesley, ISBN: 3-8273-2086-0, 2003 [Sully93] P. Sully: „Modelling the World with Objects“, Prentice Hall International (UK) Ltd. London, 1993 [TaBaBa88] R. Tamassia, Guiseppe Di Battista, C. Batini: „Automatic graph drawing and readability of diagrams“, IEEE Transactions on Systems, Man and Cybernetics, 18(1):61-79, 1988 108 Anhang E Literaturverzeichnis [UML2Toolkit] Hans-Erik Eriksson, Magnus Penker, Brian Lyons, David Fado: „UML 2 Toolkit“, Wiley Publishing, ISBN: 0-471-46361-2, 2004 [W3-B72] Lucent Technologies Inc.: Users' Reference to B; http://cm.bell-labs.com/cm/cs/who/dmr/kbman.html; Zugriff: 25.09.2004 [W3-Bison] Free Software Foundation, Inc.: Bison (13.08.2004); http://www.gnu.org/software/bison/bison.html; Zugriff: 29.09.2004 [W3-CodePro] Code Project: The Code Project – Freier Sourcecode und Anleitungen (25.09.2004); http://www.codeproject.com; Zugriff: 25.09.2004 [W3-CRefact] Alejandra Garrido: CRefactory – Refaktorisierungstool für C mit Unterstützung von Präprozessor-Anweisungen (07.09.2003); https://netfiles.uiuc.edu/garrido/www/CRefactory.html; Zugriff: 25.09.2004 [W3-DIN-PAP] DIN 66001: Sinnbilder für Datenfluss- und Programmablaufpläne; http://www.fhjena.de/~kleine/history/software/DIN66001-1966.pdf; Zugriff: 28.09.2004 [W3-JECKLE] Mario Jeckle; www.jeckle.de; Zugriff: 25.09.2004 [W3-Manhatt] VERTIGO Engineering: Manhatten – See your code from the Sky; http://www.vertigoeng.com/manhattan; Zugriff: 25.09.2004 [W3-OMG] Object Management Group, Inc. (OMG) (24.09.2004); www.omg.org; Zugriff: 25.09.2004 [W3-OOSE] oose.de Dienstleistungen für innovative Informatik GmbH (24.09.2004); www.oose.de; Zugriff: 25.09.2004 [W3-RefThum] Sven Gorts, Philipe T'Seyen: Refactoring Thumbnails; http://www.refactoring.be; Zugrif:: 27.09.2004 [W3-SF] OSTG Open Source Technology Group: Sourceforge - Open Source Software Development Website (24.09.2994); http://sourceforge.net; Zugriff: 25.09.2004 109 Anhang E Literaturverzeichnis [W3-SiCoBa] Data Mekanix: Hompage von CSizingControlBar (31.03.2002); http://www.datamekanix.com; Zugriff: 25.09.2004 [W3-UML] Object Management Group, Inc.: Unified Modeling Language UML (29.03.2004); www.uml.org; Zugriff: 25.09.2004 [WaNe95] Kim Waldén, Jean-Marc Nerson: „Seamless Object-Oriented Software Architecture“, Prentice Hall, Englewood Cliffs, New Jersey, ASIN: 0130313033, 1995 [WiNa95] Wilkinson, M. Nancy: „Using CRC Cards - An Informal Approach to Object-Oriented Development“, SIGS Books, New York, 1995 [Wirf90] Rebecca Wirfs-Brock, Brian Wilkerson, Lauren Wiener: „Designing Object-Oriented Software“, Prentice Hall Englewood Cliffs, New Jersey, 1990 110 Eidesstattliche Erklärung Hiermit erkläre ich an Eides statt, dass die vorliegende Diplomarbeit ohne unzulässige Hilfe und nur unter Verwendung der angegebenen Literatur angefertigt wurde. Die Arbeit wurde bisher keiner anderen Prüfungsbehörde vorgelegt und auch noch nicht veröffentlicht. Frankfurt am Main, den 30. September 2004 ______________________________ Carsten Rudolf Stocklöw