Erste Implementation eines praxisbezogenen Analyse- Tools für Softwarequalität und ein Ansatz zur Erkennung von Zustandsvariablen

Größe: px
Ab Seite anzeigen:

Download "Erste Implementation eines praxisbezogenen Analyse- Tools für Softwarequalität und ein Ansatz zur Erkennung von Zustandsvariablen"

Transkript

1 Erste Implementation eines praxisbezogenen Analyse- Tools für Softwarequalität und ein Ansatz zur Erkennung von Zustandsvariablen Master Thesis Wintersemester 2005/2006 Fachbereich Informatik von Benedikt Feller Referent: Prof. Dr. Joachim Wietzke Korreferent: Prof. Dr. Peter Wollenweber

2

3 Eidesstattliche Erklärung Hiermit erkläre ich an Eides statt, daß ich die vorliegende Master Thesis selbständig und ausschließlich unter Verwendung der angegebenen Hilfsmittel und Referenzen angefertigt habe. Benedikt Feller Rödermark, den 19. April 2006 I

4 Danksagung An dieser Stelle möchte ich einigen Personen danken, die mich während der Entstehung dieser Arbeit begleitet und unterstützt haben. So gilt mein Dank Prof. Dr. J. Wietzke für die Übergabe des Themas und die Betreuung dieser Master Thesis sowie seine Unterstützung bei sämtlichen während ihrer Entstehung auftretenden Fragen. Prof. Dr. P. Wollenweber danke ich für die Übernahme des Korreferendariats. Des weiteren danke ich meinen Eltern für das Stellen eines provisorischen kleinen Büros während meiner DSL-losen Zeit, insbesondere meiner Mutter für die fürstliche Bewirtung und meinem Vater für das engagierte Durcharbeiten dieses für ihn teils schwerverständlichen Textes auf der Suche nach Flüchtigkeits- und Kommafehlern. Meiner Freundin Andrea danke ich für ihren seelischen Beistand in Phasen der Schreibblockade. Abschließend möchte ich widerstrebend der Firma Arcor danken, die es nach 15 Monaten tatsächlich ermöglichte, unsere Wohnung mit einem in der Bandbreite eingeschränkten DSL- Anschluß zu versorgen. II

5 Inhalt Kapitel 1: Einleitung Motivation Problematik Vorgehensweise Leitfaden... 2 Kapitel 2: Softwarequalität Definition Messung von Softwarequalität Statische Analyse Dynamische Analyse Auswahl konventioneller Metriken Lines of Code Zyklomatische Komplexität Halstead-Metriken Maintainability Index Objektorientierte Metriken Weighted Methods per Class (WMC) Depth of Inheritance Tree (DIT) Number of Children (NOC) Coupling between Object Classes (CBO) Response for a Class (RFC) Lack of Cohesion in Methods (LCOM) Softwarequalitätsdefizite in der Praxis Vagabundierende Zeiger Speicherlecks, Fragmentierung und Speicherfresser Copy-Konstruktor und Zuweisungsoperator Mängel im Design Multiprocessing / -threading Verwendung von Zustandsvariablen III

6 Kapitel 3: Existierende Software zur Qualitätsmessung McCabe IQ CMT Lint CodeCheck Kapitel 4: Implementation des Tools Idee Parser Parser allgemein Parsen von C Untersuchung von Parsern / Generatoren Kandidaten Synopsis Entwurf Die Benutzerschnittstelle von Synopsis Ablauf der Analyse Hilfsklasse IdentifierGatherer Erkennung von Zustandsvariablen Vorgehensweise Lokale Deklarationen Manipulationen Zuweisung Übergabe an eine Funktion Manipulation via Zeiger/Referenz Inkrement/Dekrement Konditionen if-anweisung switch-anweisung Konditionalausdruck Zustandsobjekte Objektorientierte Metriken Relationsgraph Ermittlung der Datenbasis Generierung des Graphen Struktur Depth of Inheritance Tree Number of Children IV

7 4.6. Benutzeroberfläche Vorgehensweise Verbinden der Quellcode-Dateien Nachrichten und Code-Browser Darstellung des Relationsgraphen Kapitel 5: Fazit und Ausblick Fazit Graphischer Relationsgraph Erkennung kritischer Abschnitte Anhang...58 A. Konzept des shared_ptr der Boost-Bibliothek...58 Referenzen...61 V

8 Abbildungsverzeichnis Abbildung 2.3.1: Beispiel eines Kontrollflußgraphen... 6 Abbildung 2.3.2: McCabe-Maß bei aufeinanderfolgenden und verschachtelten Konditionen... 7 Abbildung 2.3.3: Zerlegung eines Programms in Operatoren und Operanden nach Halstead... 8 Abbildung 2.4.1: Ableitungsbaum mit Mehrfachvererbung Abbildung 2.4.2: Beispiel für starke Kopplung Abbildung 2.4.3: Beispiel für starke Kohäsion Abbildung 2.4.4: Beispielklasse Abbildung 2.5.1: Speicherfragmentierung Abbildung 2.5.2: Beispiel eines Zustandsdiagramms Abbildung 3.1.1: Kontrollflußgraphen in McCabe IQ Abbildung 4.2.1: Phasen eines Compilers Abbildung 4.2.2: Beispiel eines Parse-Baums Abbildung 4.2.3: Parse-Baum und AST Abbildung 4.3.1: Beispiel eines Parse-Baums in Synopsis Abbildung 4.3.2: Klassendiagramm des Tools (ohne GUI) Abbildung 4.3.3: IdentifierGatherer Abbildung 4.4.1: Klassen zur Ermittlung von Zustandsvariablen...34 Abbildung 4.4.2: Parse-Baum einer Deklaration Abbildung 4.4.3: ManipulationScanner Abbildung 4.4.4: Aufbau eines Zuweisungsausdrucks Abbildung 4.4.5: Aufbau eines Funktionsaufrufs VI

9 Abbildung 4.4.6: FunctionScanner Abbildung 4.4.7: Aufbau einer if-anweisung Abbildung 4.4.8: Aufbau einer switch-anweisung Abbildung 4.4.9: Aufbau eines Konditionalausdrucks Abbildung : Parse-Bäume von Konditionalausdruck und if-anweisung Abbildung 4.5.1: Klassen zur Ermittlung von Klassendefinitionen Abbildung 4.5.2: An der Erzeugung des Relationsgraphen beteiligte Klassen...46 Abbildung 4.5.3: Beispiel eines Relationsgraphen Abbildung 4.5.4: Ableitungsbaum mit Mehrfachvererbung Abbildung 4.6.1: FileInfo und Verbunddatei Abbildung 4.6.2: Oberfläche des Tools mit Code-Browser...53 Abbildung 4.6.3: Nachrichtenliste Abbildung 4.6.4: textueller Relationsgraph im Tool Abbildung 4.6.5: graphischer Relationsgraph Abbildung A.1: Direkt ausgegliederte Referenzzählung Abbildung A.2: Indirekt ausgegliederte Referenzzählung Abbildung A.3: Beispiel eines Rings mit shared_ptr VII

10

11 KAPITEL 1: EINLEITUNG Kapitel 1: Einleitung 1.1. Motivation Seit den ersten Bugs in Computern 1 ist die Suche nach Fehlern ein fester Bestandteil der Programmerzeugung. Dies erwies sich mit immer größeren und komplexeren Projekten als unlösbare Aufgabe für den Einzelnen, woraus die Entwicklung von höheren Programmiersprachen und neuen -paradigmen zur Vermeidung von Fehlern resultierte sowie der Einsatz von Tools, um die Suche nach ihnen zu automatisieren. Neben dem wissenschaftlichen Ansatz in Form der Definition von Softwaremetriken, stellen eben jene Tools zur gezielten Fehlersuche, die z.b. mit Lint bereits gegen Ende der 70er Jahre einen gewissen Standard erreichten, heute eine gängige Möglichkeit zur Identifikation problematischer Codestellen dar. Die Motivation für die vorliegende Arbeit entstand aus der Erfahrung von Prof. Dr. Wietzke, daß im Bereich von Infotainment-Systemen wiederholt gleichartige Fehler gefunden werden und der Idee, sie durch statische Codeanalyse automatisiert aufdecken zu können Problematik Die meisten Tools zur Bewertung der Qualität einer Software basieren auf der Messung oft einer Vielzahl an Metriken, von denen jede ihre eigenen Mängel besitzt und nicht alle Facetten der Komplexität und damit potentiellen Fehlerquellen erfassen kann. Tools wie Lint, die Quellcode gezielt auf bekannte sprachenbezogene Programmierfehler hin untersuchen, sind wiederum zu allgemein, um beispielsweise die problematische Verwendung von Zustandsvariablen zu ermitteln. Der Implementation einer eigenen Lösung wiederum steht die Schwierigkeit der Analyse von Code in C++ gegenüber. Die Entwicklung eines Parsers für diese Multiparadigmensprache mit mehrdeutiger Grammatik ist schwierig und bildete das Thema für eigenständige Arbeiten wie [Gagnon98] oder [Willink01]. Entsprechend wenige freie und voll 1 Beim womöglich ersten Computer-Bug handelte es sich um eine Motte, die Grace Hopper 1946 in einem Relay im Mark II der Havard Faculty fand [Danis97]. 1

12 KAPITEL 1: EINLEITUNG funktionsfähige Parser existieren, auf denen ein Analyse-Tool aufsetzen könnte. Ist ein Parser gefunden, steht die Analyse vor dem Problem der Flexibilität der Sprache und sich daraus ergebene verschiedene Möglichkeiten, um z.b. eine Variable zu manipulieren Vorgehensweise Zunächst sollte ein Parser gefunden werden, der einen Parse- oder abstrakten Syntaxbaum erzeugt, anhand dem wiederum die Analyse durchgeführt werden kann. Der Entwurf des Tools mußte sich dazu an der vom Parser zur Verfügung gestellten Objektstruktur orientieren und konnte nicht gleich zu Beginn stattfinden. Parallel dazu wurde der Ansatz zur Erkennung von Zustandsvariablen entwickelt, der bezüglich der in der Codeanalyse notwendigen Schritte konkretisiert und nach dem Entwurf des Tools direkt umgesetzt wurde. Anhand während dieser Arbeit aus anderen Projekten bekannt gewordener Fehler [Wietzke05b] wurden weitere Ansätze zur Fehlererkennung geplant, wobei eine Idee zur Erkennung kritischer Abschnitte in dieser Arbeit vorgestellt wird. Weiterhin wurde die allgemeine Darstellung des Ableitungsbaums realisiert, um eventuelle Designmängel durch manuelle Inspektion zu erleichtern Leitfaden Diese Arbeit ist grob in zwei Hauptteile gegliedert. Kapitel 2 behandelt das Thema Softwarequalität. Ausgehend von einer Definition dieses Begriffs werden eine Auswahl konventioneller und objektorientierter Metriken beschrieben. Abschließend werden einige Probleme aus der Praxis geschildert, die sich nicht oder nur teilweise mit den genannten Softwaremetriken erkennen lassen. In Kapitel 3 werden einige bereits existierende Tools zur Qualitätsmessung von in C++ geschriebenen Projekten vorgestellt. Im darauffolgenden Kapitel 4 wird die Implementation des im Rahmen dieser Arbeit entwickelten Tools beschrieben. Beginnend mit einem Abschnitt über Parser für C++ werden zunächst der Entwurf, sowie anschließend die beiden Hauptbestandteile des Tools erklärt: die Erkennung von Zustandsvariablen und die Erstellung eines Ableitungsbaums, bzw. Relationsgraphen. Das Kapitel schließt mit der Beschreibung der Benutzeroberfläche. Das Ende dieses Dokuments stellt das Kapitel 5 dar, in dem sich ein Fazit sowie der Ausblick befinden. In letzterem wird noch ein Ansatz zur Erkennung von kritischen Abschnitten in parallelen Umgebungen geschildert. 2

13 KAPITEL 2: SOFTWAREQUALITÄT Kapitel 2: Softwarequalität In diesem Kapitel werden die Grundlagen bezüglich Softwarequalität behandelt von der in dieser Arbeit verwendeten Definition über Möglichkeiten zu deren Messung bis zu einer Betrachtung etablierter Softwaremetriken. Abschließend werden einige relevante Probleme aus der praktischen Softwareentwicklung vorgestellt, an denen sich die in Kapitel 4 geschilderte Implementation des Tools orientiert Definition Qualität. Beschaffenheit einer Einheit bezüglich ihrer Eignung, festgelegte und vorausgesetzte Erfordernisse zu erfüllen. [DIN T11] Der Qualitätsdefinition der DIN entsprechend wird der Begriff Softwarequalität in der Literatur stets als Oberbegriff für eine Reihe von Bedingungen, die an die Software gestellt werden, verstanden. So definiert beispielsweise [Barbacci95] die vier Attribute Performance, Dependability, Security und Safety, die sich nur in begrenztem Maß vereinbaren lassen und Entscheidungen gefällt werden müssen, die zugunsten des einen, aber auf Kosten des anderen Attributs ausgehen. Die Merkmale, die zussamenfassend die Softwarequalität ausmachen, sind nicht fest definiert und werden auch häufig als Ilities bezeichnet [Voas03], worunter beispielsweise Reliability, Availability, Maintainability und Testability verstanden werden. Im Rahmen dieser Arbeit wurde ein Tool entwickelt, welches bei der Identifikation von aus der Praxis bekannten Problemen bei der Softwareentwicklung helfen soll. Daher wird in dieser Arbeit der Begriff Softwarequalität weniger umfassend verwendet und bezeichnet insbesondere die Fehlerfreiheit. Qualitätsdefizite, wie in Kapitel 2.5 erwähnt, stellen daher potentielle Fehlerquellen dar. 3

14 KAPITEL 2: SOFTWAREQUALITÄT 2.2. Messung von Softwarequalität Die Messung von Softwarequalität bedeutet also, gemäß der Definition des letzten Abschnitts, die Suche nach potentiellen Fehlerquellen. Nicht alle Aspekte, die in der Praxis zu Problemen führen können, lassen sich durch bloße Quellcodeinspektion erfassen. Daher wird hier zwischen statischer und dynamischer Codeanalyse unterschieden Statische Analyse Bei der statischen Analyse wird ausschließlich der Quellcode betrachtet. Dabei werden entweder, wie bereits erwähnt, konkrete Problemstellen identifiziert oder, z.b. durch Zählen bestimmter Konstrukte, Maßzahlen errechnet, nämlich Softwaremetriken. Analog zu anderen Wissenschaften sind Softwaremetriken der gängige Ansatz der Informatik zur Quantifizierung von Codequalität. Da die meisten heute bekannten Metriken von direkten Anwendern, also Softwareentwicklern definiert wurden, oder auf empirische Studien zurückgehen [Abran03], finden sie auch in der Praxis häufigen Einsatz und wurden in einer Vielzahl von Analyse-Tools umgesetzt. Auf Basis der statischen Analyse arbeitet auch das im Rahmen dieser Arbeit entwickelte Tool, weshalb in den folgenden Kapiteln die Codeinspektion bzw. Softwaremetriken im Vordergrund stehen Dynamische Analyse Die dynamische Analyse beschränkt sich nicht auf die bloße Analyse von Quellcode, sondern betrachtet das Verhalten der Software zur Laufzeit. Wohl am weitestverbreiteten ist die dynamische Codeanalyse beim Testen von Software mit Hilfe eines Debuggers. Debugger ermöglichen durch zusätzliche Symbolinformationen z.b. die Darstellung von Variableninhalten, Speicherbereichen oder einer Historie von Funktionsaufrufen zur Laufzeit. Neben dem Testen können solche zusätzlichen Informationen oder injizierten Codeabschnitte in sogenannten Profilern zur Performanzmessung oder Überprüfung, ob z.b. alle Pfade einer Funktion durchlaufen wurden, herangezogen werden [Graham82] Auswahl konventioneller Metriken In diesem Kapitel wird eine Auswahl konventioneller Softwaremetriken vorgestellt, die etabliert und anerkannt sind. Es werden ihre Einsatzbereiche geschildert und darüberhinaus ihre Grenzen bzw. Probleme beim Einsatz dieser Metriken aufgezeigt. 4

15 KAPITEL 2: SOFTWAREQUALITÄT Lines of Code Lines of Code (LOC) waren ein früher Ansatz zur Bestimmung der Größe von Quellcode, um daraus Schätzungen über den Projektverlauf abzuleiten oder z.b. weitere Maße wie den Maintainability Index zu berechnen. Das Zählen von LOC war insbesondere bei Sprachen wie Assembler gängige Praxis, da dort eine Zeile im Programmcode stets genau einer Anweisung entsprach [Kan02]. Moderne Programmiersprachen gewähren im Gegensatz dazu einen höheren Grad an Freiheit bezüglich der Formatierung des Quelltextes. Damit einhergehend ist die Messung der LOC abhängig von der Programmiersprache und dem Schreibstil des Programmierers. Dazu hier ein Beispiel in C/C++. // LOC = 2 for (int i = 0; i < max; ++i) if (array[i] < 0) break; // LOC = 5 for (int i = 0; i < max; ++i) { if (array[i] < 0) break; } Um diese Abhängigkeit teilweise zu entkräften, werden meist Leer- sowie Kommentarzeilen ignoriert. Des weiteren werden oft firmen- bzw. projektintern Notationsregeln festgelegt oder Tools zur Formatierung von Quellcode eingesetzt, um bei der Messung von LOC vergleichbare Ergebnisse zu erhalten [Wietzke05b]. Gleichzeitig ist üblich, festzulegen, welche Zeilen genau gezählt werden, da in höheren Sprachen, anders als in Assembler, eine Zeile mehreren Anweisungen oder auch gar keiner (Präprozessor-Direktiven in C/C++) entsprechen kann [Kan02]. Abhängig dieser Notationsregeln bzw. Zählstrategien sind LOC oft nicht direkt vergleichbar. Heute findet die Messung der Lines of Code z.b. bei der Beobachtung der personenbezogenen Produktivität von Mitarbeitern Anwendung sowie zur Vorhersage von Entwicklungsdauer und Umfang von Projekten anhand bereits ermittelter Produktivitätsdaten [Wietzke05b]. Zudem deutet eine Veränderung der LOC eines Mitarbeiters unter denselben Voraussetzungen (also vergleichbare Komplexität der Aufgabenstellung) auf eine Änderung seines Arbeitsverhaltens hin Zyklomatische Komplexität Die Zyklomatische Komplexität ist eine von Thomas J. McCabe im Jahre 1976 eingeführte Metrik [VanDoren97a], um die Komplexität und damit die Verständlichkeit bzw. Testbarkeit von Programmcode zu messen. Obwohl McCabe eine Vielzahl von Softwaremetriken definiert hat, erhielt die Zyklomatische Komplexität die größte Aufmerksamkeit und wird daher auch als das McCabe-Maß bezeichnet. 5

16 KAPITEL 2: SOFTWAREQUALITÄT Das McCabe-Maß berechnet sich ausgehend vom sogenannten Kontrollflußgraphen zu einer Funktion, wobei Befehle und Kontrollstrukturen als Knoten angesehen und entsprechend dem Programmablauf die Kanten gezogen werden (siehe hierzu Abbildung 2.3.1), gemäß der folgenden Formel. v G = E N p wobei v(g): Zyklomatische Komplexität E: Kanten im Graph N: Knoten im Graph p: Zusammenhangskomponenten Als Zusammenhangskomponenten werden dabei die Funktionen der Software angesehen, allerdings wird bei der praktischen Anwendung das McCabe-Maß meist für jede Funktion einzeln ermittelt und nicht das Softwareprojekt als Ganzes betrachtet. Um die Betrachtung einer Funktion als Zusammenhangskomponente graphentheoretisch zu rechtfertigen, wird eine virtuelle Kante vom Funktionsaustiegs- zu deren Einstiegspunkt gezogen, die als Konstante in die Berechnung einfließt. In den meisten Implementationen des McCabe-Maßes wird daher die Formel v G = E 1 N 1 bzw. v G = E N 2 verwendet. Hier ein Beispiel des Kontrollflußgraphen einer C++ Funktion und deren zyklomatische Komplexität. int Node::getValueAsInt() const { if (value_.empty()) throw no value ; stringstream int to_int; ret_val; v(g) = E N + p = = 3 for (ValueType::const_iterator it = value_.begin(); it!= value_.end(); ++it) to_int << *it; to_int >> ret_val; } return ret_val; Abbildung 2.3.1: Beispiel eines Kontrollflußgraphen Das McCabe-Maß spiegelt somit die Anzahl der Kontrollflüsse, also der linear unabhängigen Pfade, einer Funktion wider und liefert damit theoretisch das Minimum an Testdurchläufen, um sie als fehlerfrei ansehen zu können [VanDoren97a]. In C++ und auch anderen modernen Programmiersprachen wie etwa Java ist dies zwar nicht ganz richtig, da durch die Einführung von Exceptions die Zahl der Ausführungspfade stark anwachsen kann [Sutter00], dennoch Komplexität v(g) simples Programm, 1-10 geringes Risiko komplexer, moderates Risiko komplex, hohes Risiko untestbares Programm, >50 sehr hohes Risiko 6

17 KAPITEL 2: SOFTWAREQUALITÄT findet diese Metrik in der Praxis noch immer häufig Anwendung, und ein hoher Wert identifiziert auch recht zuverlässig eine problematische Funktion, die es zumindest zu überprüfen gilt [Wietzke05b]. Die nebenstehende Tabelle gibt Erfahrungswerte zur Interpretation des McCabe-Maßes an [VanDoren97a]. aufeinanderfolgend 0 void samplefunc1() { v(g) 1 if (x[1] < a) 2 x[1] = a; 3 if (x[2] < b) 4 x[2] = b; 5 if (x[3] < c) 6 x[3] = c; 7 if (x[4] < d) 8 x[4] = d; 9 } = E N + p = = 5 verschachtelt void samplefunc2() 0 { 1 if (x[1] > a) 2 if (x[2] > b) 3 if (x[3] > c) v(g) 4 if (x[4] > d) 5 return; 7 else x[4] = d; 8 else x[3] = c; 9 else x[2] = b; 10 else x[1] = a; 11 } = E N + p = = 5 Abbildung 2.3.2: McCabe-Maß bei aufeinanderfolgenden und verschachtelten Konditionen Allerdings ist das McCabe-Maß nicht ganz unumstritten. In seiner Berechnung werden alle Kanten mit 1 gewichtet, weshalb die Zyklomatische Komplexität verschachtelte und linear aufeinanderfolgende Konstrukte gleich bewertet, wobei allerdings erfahrungsgemäß ersterer Fall für den Menschen schwieriger zu verstehen ist (siehe Abbildung 2.3.2). Darüber hinaus ist schon die bloße Aufsummierung von Kanten, Knoten und Zusammenhangskomponenten an sich nicht einwandfrei, da eine Addition von verschiedenen Einheiten numerisch ungültig ist [Abran04]. Des weiteren ließ McCabe den Begriff Komplexität und damit die Maßeinheit seiner Metrik, im Zusammenhang mit Programmcode, undefiniert [Abran04] Halstead-Metriken Maurice Halstead definierte 1977 eine Reihe von Metriken zur Messung von Softwarekomplexität durch statische Codeanalyse [VanDoren97b]. Sein Ansatz besteht in der Einteilung eines Programms in Operatoren und Operanden, anhand derer seine Maße errechnet werden. Generell stellen Operatoren dabei zur Programmiersprache gehörige Konstrukte und Operanden benut Anzahl untersch. Operatoren n 1 Anzahl untersch. Operanden n 2 Operatoren insgesamt N 1 Operanden insgesamt N 2 Programmlänge N = N 1 N 2 Größe des Vokabulars n=n 1 n 2 Volumen Schwierigkeit Programmieraufwand V =N log 2 n D= n 1 N 2 2 n 2 E= D V 7

18 KAPITEL 2: SOFTWAREQUALITÄT zerdefinierte Bezeichner, wie z.b. Variablen, dar. Die Halstead-Metriken ergeben sich dann aus den nebenstehenden Formeln [VanDoren97b]. Dazu ein Beispiel in der folgenden Abbildung int countnumbers(const string& input) { int num = 0; for (int i = 0; i < input.length(); ++i) { char c = input[i]; if (c >= '0' && c <= '9') ++num; } return num; } Operatoren int const & = for. ++ [] >= <= () string {} ; < length char if && return N 1 : 34 N 2 : 16 n 1 : 20 n 2 : 8 Operanden countnumbers input num 0 i c Abbildung 2.3.3: Zerlegung eines Programms in Operatoren und Operanden nach Halstead Daraus ergeben sich für die einzelnen Metriken Programmlänge N 50 entsprechend die nebenstehenden Werte. Programmlänge, Größe des Vokabulars und Volumen dienen Größe des Vokabulars n 28 Volumen V 240,37 dabei der Bestimmung der Größe eines Programms Schwierigkeit D 20 die Programmlänge als ein mit den LOC vergleichbarer Programmieraufwand E 4807,35 Wert. Die Einbeziehung verschiedener Operatoren/ Operanden jedoch kann darüberhinaus für eine Aussage über die Verständlichkeit eines Programms herangezogen werden, da z.b. bei ähnlichen oder gar redundanten Konstrukten das Vokabular auch mit wachsender Länge weitgehend konstant bliebe. Im Programmieraufwand wurde versucht, aus den gewonnenen Werten eine zeitliche Aussage über die Implementationsdauer zu treffen. Halstead definierte mehr als die fünf von [VanDoren97b] aufgeführten Metriken. Zu diesen zählen beispielsweise noch das potentielle Volumen oder das sich daraus ergebende Programmlevel. Wegen Unklarheiten in deren Definition und Abhängigkeiten zu den bereits genannten Metriken 3 [AlQutaish05], beschränkt man sich in dieser Arbeit auf die hier aufgeführten. 2 Generell werden Schlüsselwörter und der Sprache zugehörige Operatoren auch im Sinne der Halstead- Metriken als Operatoren angesehen. Im Falle von string und length() im Beispiel ist diese Unterscheidung nicht so offensichtlich, da die Bezeichner zwar zur Standardbibliothek gehören, dadurch aber nicht unbedingt als zur Sprache zugehörig anzusehen sind. 3 Das Level entspricht z.b. dem Kehrwert der Schwierigkeit eines Programms. 8

19 KAPITEL 2: SOFTWAREQUALITÄT Halsteads Metriken sind einiger Kritik ausgesetzt. Zum einen ist die Unterscheidung zwischen Operatoren und Operanden nicht intuitiv offensichtlich, wie von Halstead beschrieben, sondern können zu unterschiedlichen Zählstrategien führen, die wiederum verschiedene Meßergebnisse zur Folge haben. Zum anderen sind die Einheiten der Maßzahlen nicht klar definiert, worin [AlQutaish05] ein Schlüsselkriterium im Entwurf ingenieurswissenschaftlicher und physikalischer Maße sieht Maintainability Index Der Maintainability Index (MI) ist eine hybride Metrik, die sich aus gewichteten voneinander unabhängigen Metriken zusammensetzt. Der MI bezieht sich auf das gesamte Programm die einzelnen Metriken werden für jedes Modul separat berechnet und anschließend deren arithmetischen Mittel gebildet. Die ursprüngliche Definition des bereits validierten Maintainability Index (MI) bestand aus folgender Formel [Welker95]. MI =171 3,42ln avee 0,32ln avev G 16,2 ln aveloc [ 0,99aveCM ] Konkret handelt es sich dabei um die durchschnittlichen Wartbarkeit schwierig <65 Werte pro Modul des Halstead-Aufwandes avee, der mäßige Wartbarkeit Erweiterten Zyklomatischen Komplexität 4 avev(g), der hohe Wartbarkeit >85 Lines of Code aveloc, die bereits in den vorherigen Abschnitten beschrieben wurden, sowie der Anzahl Kommentarzeilen avecm (optional). Der MI wird zur Bewertung der Wartbarkeit von Software herangezogen, wenn z.b. die Entscheidung ansteht, ob sich die Erweiterung eines alten Softwareprodukts noch lohnt, oder eine Neuentwicklung die günstigere Alternative wäre. Hewlett-Packard ermittelte anhand empirischer Untersuchungen die nebenstehenden Grenzwerte. Mißtrauen gegenüber dem von Halstead definierten Programmieraufwand, sowie eine zu starke Gewichtung der Kommentierung 5 zogen eine Anpassung der Formel für den MI nach sich. 4 Diese Erweiterung betrachtet logische Verknüpfungen (AND/OR) im Bedingungsteil von z.b. if-anweisungen als eigene Ablaufpfade [Andersson04], liefert also tendenziell höhere Werte. 5 Auskommentierter Code, überflüssige Kommentare oder gar Inkonsistenzen, die entstehen, sobald Code geändert wurde, die Kommentierung jedoch erhalten blieb, tragen nicht zur Wartbarkeit bei, sind dieser u.u. sogar abträglich [Welker95]. 9

20 KAPITEL 2: SOFTWAREQUALITÄT MI =171 5,2ln avev 0,23ln avev G 16,2ln aveloc[ 50sin 2,4 percm ] Statt des Halstead-Aufwandes und der Anzahl an Kommentarzeilen werden nun Durchschnittswerte für jedes Programmodul des Halstead-Volumens avev und des prozentualen Anteils von Kommentarzeilen percm verwendet. Auch weiterhin ist die Einbeziehung der Kommentierung optional es soll im Ermessen des Anwenders liegen, inwiefern Kommentare der Wartbarkeit dienlich sind, was deren Analyse durch den Menschen erfordert [Welker95]. Die Kritik, das McCabe-Maß messe nur strukturelle und die Halstead-Metriken nur lexikalische Komplexität [VanDoren97b], wandelt sich in deren Verbindung im Maintainability Index zu einem Vorteil, da so verschiedene Aspekte von Softwarekomplexität in einen Wert einfließen. Jedoch ist die Aussagekraft des MI über die Wartbarkeit eines Projekts als Ganzes heutzutage geringer, da z.b. durch Objektorientierung oder Parallelität neue Formen von Komplexität entstanden sind Objektorientierte Metriken Die im letzten Abschnitt beschriebenen Softwaremetriken finden, wie bereits erwähnt, auch heute noch Anwendung. Sie sind einiger Kritik ausgesetzt, die meist aus einer fehlenden oder nicht einwandfreien theoretischen Basis bzw. unklaren Maßeinheiten resultiert. Darüber hinaus entstanden seit der Definition dieser Metriken neue Formen von Softwarekomplexität, z.b. durch das Paradigma der Objektorientierung. Die Arbeit von [Chidamber94] entstand in der Intention, dieser Kritik zu begegnen und gleichzeitig eine Reihe von Metriken mit Hinblick auf die Objektorientierung zu definieren, die im folgenden beschrieben sind. Dabei wurden in den Überschriften die englischen Bezeichnungen, aufgrund der Verbreitung der darauf bezogenen Abkürzungen, beibehalten Weighted Methods per Class (WMC) Die WMC-Metrik dient der Bestimmung der Komplexität einer Klasse und orientiert sich dazu an der Anzahl ihrer Methoden. Errechnet werden die WMC nach folgender Formel: n WMC= c i i=1 10

21 KAPITEL 2: SOFTWAREQUALITÄT Dabei entspricht c i der Komplexität der jeweiligen Methode und n der Anzahl an Methoden. Die Definition dieser Komplexität wurde von [Chidamber94] bewußt nicht weiter spezifiziert, um dem Anwender die Möglichkeit zu geben, eine für sein System passende Metrik, wie etwa das McCabe-Maß, zu wählen. Alternativ kann c i = 1 angenommen werden, womit WMC der Anzahl an Methoden entspricht, dann allerdings eine Größen- und keine Komplexitätsmetrik mehr darstellt [Andersson04]. Das durch WMC ermittelte Maß ist insofern interessant, da die Anzahl bzw. Komplexität von Methoden bereits während des Entwurfs zur Abschätzung des Entwicklungsaufwands der Klasse herangezogen werden kann. Desweiteren haben Klassen mit vielen Methoden eine größere Auswirkung auf abgeleitete Klassen, da diese sämtliche Methoden erben, und sind wahrscheinlich spezifischer auf eine Aufgabenstellung zugeschnitten und damit schlechter wiederverwendbar als Klassen mit wenigen Methoden. Einen Kritikpunkt sieht [Reißing01] in der fehlenden Differenzierung, welche Methoden bei der Ermittlung der WMC gezählt werden und unterscheidet daher in seinem Vorschlag für Objektorientierte Metriken zwischen öffentlichen, vererbbaren und lokalen Attributen (public, protected, private) Depth of Inheritance Tree (DIT) Die Tiefe des Ableitungsbaums DIT definiert [Chidamber94] als den längsten Weg vom aktuellen Knoten zur Wurzel. Diese Definition impliziert die Möglichkeit mehrerer Wege und damit Mehrfachableitung. Mit wachsender Tiefe eines Ableitungsbaums wachsen die Anzahl involvierter Klassen und das Potential einer Klasse, Methoden zu erben, wodurch die Komplexität des Entwurfs als Ganzes steigt. Darüber hinaus wird es wahrscheinlicher, daß eine geerbte Methode verwendet wird, je tiefer sich eine Klasse im Baum befindet. Um diese Problematik zu erkennen, mißt DIT den längsten Weg in einem Ableitungsbaum, woraus im Beispiel von Abbildung eine Tiefe von 3 resultiert. Die Wahl des längeren Pfades ignoriert dabei jedoch die durch die mehrfache Ableitung entstandene zusätzliche Komplexität ein Baum mit derselben Tiefe ohne Mehrfachvererbung erhält denselben Wert für DIT. Aus diesem Grund wird in solchen Fällen alternativ auch die Zahl aller Ahnenklassen (Number of Super Classes, NSC) zu einem Knoten im Ableitungsbaum bestimmt [Reißing01]. Base Derived1 Derived2 Derived3 MultiplyDerived Abbildung 2.4.1: Ableitungsbaum mit Mehrfachvererbung 11

22 KAPITEL 2: SOFTWAREQUALITÄT Number of Children (NOC) Mit der Number of Children werden die Klassen gezählt, die direkt von der betrachteten Klasse ableiten. Laut [Chidamber94] deutet eine große Anzahl an Kindsklassen auf eine große Wiederverwendbarkeit hin, jedoch wirkt sich die Mutterklasse in diesem Fall außerdem auf weite Teile des Systems aus, woraus ein erhöhter Testbedarf von deren Methoden resultiert. Zusätzlich wird eine große Anzahl an Kindsklassen als Indiz für eine ungünstige Abstrahierung der Basisklasse angesehen. [Reißing01] betont hierbei, daß daraus nicht ersichtlich ist, ob für NOC ein hoher oder ein tiefer Wert erstrebenswert ist. Da die Zahl der direkten Kinder keine auf das Gesamtsystem bezogene Aussage über die Wiederverwendbarkeit der Basisklasse liefert, wird auch das Zählen aller Nachfolger (Descendent Classes) vorgeschlagen [Andersson04] Coupling between Object Classes (CBO) Diese Metrik gibt für jede Klasse die Zahl Objekt derjenigen Klassen an, die mit ihr gekoppelt Attribut sind. Kopplung definiert [Chidamber94] als die Verwendung mindestens eines Attributs (Methoden, Instanzvariablen) einer Klasse durch eine andere Klasse (für ein Beispiel siehe Abbildung 2.4.2). Aggregation und Ableitung sind dabei als besondere Formen der Kopplung zu betrachten, denn die nur lokale Verwendung eines Objekts in einer Abbildung 2.4.2: Beispiel für starke Kopplung Methode zählt auch dazu. Eine starke Kopplung ist der Wiederverwendbarkeit von Klassen abträglich, da sie ihre Abhängigkeiten mit in das neue System tragen. Zudem erschwert sie Wartung und Test eines Systems, da gekoppelte Klassen nicht isoliert betrachtet werden können. Während der Entwurfsphase sind Kopplungen zu vermeiden, jedoch werden zu diesem Zeitpunkt meist nicht alle erkannt [Reißing01]. CBO eignet sich demnach insbesondere zur Bewertung, bzw. Wartung eines Systems unter Anwendung der statischen Codeanalyse. 12

23 KAPITEL 2: SOFTWAREQUALITÄT Response for a Class (RFC) Das RFC-Maß ermittelt sich durch die Größe der sogenannten Response Set 6 Klasse, welches sich folgendermaßen zusammensetzt. RS einer RS ={M } all i {R i } Wobei {R i} die Menge aller von der Methode i aufgerufenen Methoden und {M} die Menge aller Methoden der Klasse sind. RFC ist also die Summe aus den Anzahlen aller eigenen und aller potentiell aufgerufenen Methoden (auch anderer Klassen) einer Klasse. [Chidamber94] sieht in der RFC ein Komplexitätsmaß für Klassen, wobei ein großer Wert einen erhöhten Testaufwand nach sich zieht, und geht dabei noch weiter: A worst case value for possible responses will assist in appropriate allocation of testing time. [Chidamber94] Dabei wird allerdings die Definition des Worst case -Wertes nicht näher definiert, wobei dieser prinzipiell die Anzahl aller Methoden eines Systems umfassen kann. [Reißing01] sieht in der Anwendbarkeit von RFC dasselbe Problem wie bei CBO, nämlich, daß zu dessen Ermittlung detailierte Informationen zu den Methoden einer Klassen vorausgesetzt werden, die zum Zeitpunkt des Entwurfs meist noch nicht existieren. Desweiteren merkt er an, daß [Chidamber94] die Menge {M} unzureichend spezifiziert und nicht erwähnt, ob auch geerbte Methoden als dazugehörig zu betrachten sind Lack of Cohesion in Methods (LCOM) [Chidamber94] sieht die Kohäsion einer Klasse im Gegensatz zur Kopplung als wünscheswertes Attribut an, da sie das Prinzip der Kapselung fördert (vgl. Abbildung 2.4.3), wodurch die Wiederverwendbarkeit einer Klasse steigt. Desweiteren wird durch einen Mangel an Kohäsion (Lack of Cohesion) die Klasse komplexer, was die Wahrscheinlichkeit für Fehler im Entwicklungsprozeß erhöht. Disparitäten von Methoden deuten außerdem auf Designmängel einer Klasse hin, was beispielsweise zur Aufteilung der Klasse in mehrere Unterklassen nach sich ziehen kann. 6 Der Begriff Response wurde hier beibehalten, da keine Übersetzung eindeutig die passende zu sein scheint. So kann Response in diesem Kontext als Verantwortung einer, aber ebenso als Resonanz auf eine Klasse verstanden werden. 13

24 KAPITEL 2: SOFTWAREQUALITÄT Zur Erkennung, ob es einer Klasse an Objekt Kohäsion mangelt, führt [Chidamber94] die Attribut Ähnlichkeit von Methoden ein, welche die Schnittmenge der von den Methoden verwendeten Instanzvariablen darstellt. Je größer die Anzahl ähnlicher Methoden einer Klasse, desto größer deren Kohäsion. Konkret definiert [Chidamber94] die LCOM- Metrik als die Differenz von der Anzahl an sich nicht ähnlichen Methodenpaaren Abbildung 2.4.3: Beispiel für starke Kohäsion (Menge P) und jener, die gemeinsame Instanzvariablen verwenden, sich also ähnlich sind (Menge Q). Ausgenommen davon sind die Fälle, wenn Q mehr Elemente besitzt als P und wenn keine Methode Instanzvariablen verwendet 7 in beiden Fällen wird LCOM = 0. LCOM = P Q,wenn P Q, sonst=0 Zur Veranschaulichung dazu im folgenden ein Beispiel. Angenommen, die Methoden m1() und m2() der in Abbildung gezeigten Klasse SampleClass verwenden deren Attribute a, b und c. Die Methode m3() verwendet nur d und m4() überhaupt keine Instanzvariable. Anhand dessen lassen sich im Rahmen der Definition der LCOM-Metrik nach [Chidamber94] die zu den Methoden gehörigen Mengen I 1 ={a, b, c}, I 2 ={a, b, c}, I 3 ={d } und I 4 = bilden. Für alle Paare dieser Mengen, SampleClass -a: int -b: string -c: float -d: int +m1() +m2() +m3() +m4() Abbildung 2.4.4: Beispielklasse müssen nun Schnittmengen gebildet werden, um die oben genannten Mengen P und Q ermitteln zu können: I 1 I 2 ={a,b,c} I 1 I 3 = I 1 I 4 = I 2 I 3 = I 2 I 4 = I 3 I 4 = 7 In diesem Fall wird P =0, was auch LCOM = 0 impliziert. Diese Definition ist jedoch problematisch, wie weiter unten erläutert. 14

25 KAPITEL 2: SOFTWAREQUALITÄT Daraus ergibt sich für P={ I 1, I 3, I 1, I 4, I 2, I 3, I 2, I 4, I 3, I 4 } und für Q={ I 1,I 2 }, bzw. als Wert für die Metrik LCOM = P Q =5 1=4. Diese Definition von der Kohäsion einer Klasse ist nicht unproblematisch, wie bereits [Chidamber94] selbst feststellt in der Bemerkung, daß im Falle von P = Q LCOM = 0 ist, dies aber nicht maximale Kohäsivität impliziert, sondern Klassen mit höherer Kohäsion existieren können, deren LCOM ebenfalls Null ist. Darüber hinaus merkt [Etzkorn97] an, daß das Setzen von P =0 und damit LCOM = 0, wenn n I n =, eine scheinbar sehr kohäsive Klasse vorliegt, wobei im Gegensatz dazu, wenn lediglich eine Methode Instanzvariablen verwendet, LCOM einen sehr großen Wert annimmt. In beiden Fällen jedoch handelt es sich um eine Klasse mit minimaler Kohäsion, da kein Methodenpaar dieselben Instanzvariablen verwendet. Desweiteren wurde in [Chidamber94] nicht festgelegt, ob auch geerbte Attribute oder Konstruktoren/Destruktoren 8 bei der Berechnung von LCOM zu berücksichtigen sind, was, je nach Auslegung, bei der Ermittlung der Metrik zu unterschiedlichen Werten führen kann Softwarequalitätsdefizite in der Praxis In den letzten Abschnitten wurden konventionelle Metriken sowie solche, die objektorientierte Konstrukte berücksichtigen, vorgestellt. In diesem Unterkapitel findet sich nun eine Auswahl von Fehlerquellen, die, obwohl teilweise ebenfalls schon sehr lange bekannt, nach wie vor von praktischer Relevanz sind und sich nicht oder nur unvollständig durch Metriken erfassen lassen Vagabundierende Zeiger Zeiger werden als vagabundierend bezeichnet, wenn sie auf kein gültiges Objekt zeigen, was meist in Verbindung mit dessen Zerstörung geschieht. Das Problem an vagabundierenden Zeigern ist das aus ihrer weiteren Verwendung resultierende undefinierte Verhalten, womit dadurch auftretende Fehler oft schleichender Art sind. Im linken Beispiel wird das Programm womöglich eine 3 ausgeben und ohne Laufzeitfehler weiterlaufen, obwohl der entsprechende Integer mit Verlassen seines Gültigkeitsbereichs automatisch zerstört wurde und der Zeiger 8 [Etzkorn97] bemerkt hierbei, daß unter Berücksichtigung des Konstruktors die Klasse als kohäsiv angesehen würde, sobald der Konstruktor sämtliche Instanzvariablen verwendet (was aufgrund von Initialisierungen nicht unwahrscheinlich ist), selbst wenn alle anderen Methoden unterschiedliche Variablen verwenden. 15

26 KAPITEL 2: SOFTWAREQUALITÄT somit ungültig ist. Sollte in der Zwischenzeit jedoch der Stack für weitere Operationen verwendet worden sein, kann die Zuweisung auch in einer Zugriffsverletzung resultieren; kurz: Das Verhalten ist nicht definiert. int* i = 0;... if (!i) { int j = 5; i = &j; }... *i = 3; cout << *i; int& func() { int var; return var; }... int& i = func(); Gegen die Entstehung von vagabundierenden Zeigern haben sich einige Vorsichtsmaßnahmen etabliert, wie die konsequente Initialisierung von Zeigervariablen (mit Null, falls noch kein Objekt existiert), der Zuweisung von Null an den Zeiger bei Zerstörung des Objekts 9 und der Vermeidung von Zeigern zugunsten von Smart-Pointer-Objekten (siehe Anhang) oder Referenzen. Letztere haben den Vorteil, daß sie eine Initialisierung erzwingen und anschließend nur noch Änderungen am Objekt zulassen und nicht an der Referenz selbst [Stroustrup00]. Jedoch können auch ungültige Referenzen entstehen, wenn z.b. eine Funktion eine Referenz auf ein lokales Objekt zurückgibt [Meyers98], wie aus dem rechten Beispiel ersichtlich. Durch einen Vergleich der Gültigkeitsbereiche von i und j ließe sich im linken Beispiel zwar herausfinden, daß i ungültig ist, aber allgemein lassen sich vagabundierende Zeiger durch statische Codeanalyse nicht erkennen. Da sie meist in Verbindung mit dynamischer Speicherallokation auftreten, ist man auf die Einhaltung von Programmiertechniken angewiesen, wie sie oben genannt wurden. Tools könnten jedoch dabei unterstützen, wie einige Compiler, die z.b. bei Verwendung von nichtinitialisierten Variablen eine Warnung ausgeben Speicherlecks, Fragmentierung und Speicherfresser Vagabundierende Zeiger entstehen, wenn Objekte zu früh zerstört werden, darauf verweisende Zeiger also noch verwendet werden. Speicherlecks sind das Gegenstück dazu und entstehen, wenn die Handles, also Zeiger/Referenzen auf ein Objekt, bereits zerstört wurden, das Objekt selbst sich aber noch im Speicher befindet. Speicherlecks treten bei der dynamischen Allokation im Freispeicher auf und sind insbesondere bei Software mit langer 9 Gemeint ist die explizite Zerstörung eines Objekts im Freispeicher mit delete, da Objekte auf dem Stack automatisch zerstört werden. 16

27 KAPITEL 2: SOFTWAREQUALITÄT Laufzeit, wie Server-Applikationen oder Embedded Systemen, ein Problem, da dort bereits die Entstehung sehr kleiner Lecks mit der Zeit die Erschöpfung des Speichers zur Folge haben kann. Möglichkeiten zur Vermeidung von Speicherlecks sind die Verwendung von ihren eigenen Speicher verwaltenden Containern und Smart-Pointern (siehe Anhang) oder der Einsatz eines Garbage Collectors. Im Gegensatz zu anderen Sprachen ist letzterer im aktuellen Standard von C++ nicht vorgeschrieben. Ein Grund dafür ist die Möglichkeit der Verwendung von getarnten Zeigern, die sich durch Casts erzeugen lassen. Hier das Beispiel dazu aus [Stroustrup00]: void f() { int* p = new int; long i1 = reinterpret_cast<long>(p)&0xffff0000; long i2 = reinterpret_cast<long>(p)&0x0000ffff; p = 0; // Hier existiert kein Zeiger mehr auf den int. p = reinterpret_cast<int*>(i1 i2); // Hier existiert wieder ein Zeiger auf den int. } nach teilweiser Freigabe Abbildung 2.5.1: Speicherfragmentierung Neben Speicherlecks gibt es noch verwandte Speicherprobleme, die zwar weniger kritisch, aber insbesondere in der Embedded-Welt Schwierigkeiten bereiten. Zum einen ist dies die Fragmentierung von Speicher, die bei dynamischer Speicherallokation und -freigabe für viele kleine Objekte entsteht. Stark fragmentierter Speicher hat zur Folge, daß für große Blöcke unter Umständen nicht genug zusammenhängender Speicher vorhanden ist (vgl. Abbildung 2.5.1). Nun kann z.b. ein Garbage Collector um eine Möglichkeit zur Defragmentierung des Freispeichers erweitert werden, was aber in parallelen Systemen zu Problemen führt, da dies exklusiven Speicherzugriff voraussetzt und somit alle auf den Speicher zugreifenden Prozesse angehalten werden müßten [Wietzke05a]. Als Abhilfe dienen hier eigene Allokatoren, die selbst einen großen Speicherblock reservieren, darauf Allokationen für kleine Objekte verwalten und abschließend den gesamten Block wieder freigeben [Alexandrescu03] [Wietzke05a]. Ein weiteres Problem stellen sogenannte Speicherfresser dar. Als Speicherfresser werden Ablaufeinheiten bezeichnet, die ständig weiteren Speicher anfordern, diesen jedoch, auch ohne Verlust der Referenz, bzw. des Zeigers, was ein Speicherleck bedeuten würde, nicht mehr freigeben. Symptomatisch gleichen Speicherfresser demnach den Speicherlecks, mit der Ausnahme, daß sie, selbst ohne die oben genannte Problematik mit getarnten Zeigern, nicht mit einem Garbage Collector in den Griff zu kriegen sind. 17

28 KAPITEL 2: SOFTWAREQUALITÄT Speicherlecks, Fragmentierung und Speicherfresser lassen sich nicht ebenso wie vagabundierende Zeiger nur zur Laufzeit erkennen. Im Falle der Lecks könnten getarnte Zeiger bestehen, den Freispeicher teilt man meist mit anderen Prozessen, weshalb der Grad an Fragmentierung stets ein anderer ist, und scheinbaren Speicherfressern könnte z.b. ein Thread zugeteilt sein, der parallel und asynchron die entsprechenden Blöcke freigibt. Eine Möglichkeit, solche Probleme automatisiert aufzudecken läge z.b. in der Überladung der globalen new/delete-operatoren (oder im Überschreiben der entsprechenden Funktionen zur Anforderung und Freigabe von Freispeicher), in denen etwa Größe und Anzahl angeforderter Speicherblöcke protokollieren lassen Copy-Konstruktor und Zuweisungsoperator In C++ ist der Compiler angehalten, einen Standardkonstruktor, Copy-Konstruktor und Zuweisungsoperator zu definieren, sollte eine Klasse keinen benutzerdefinierten haben [Std98: 12.1]. Wie in [Meyers98] ausführlich beschrieben, kann dieser Umstand insbesondere dann zu Problemen führen, wenn die Klasse dynamischen Speicher verwaltet, da Copy-Konstruktor und Zuweisungsoperator jeweils eine binäre Kopie des Objekts anfertigen, dabei also den Zeiger übernehmen, jedoch nicht den alloziierten Speicher kopieren (flache Kopie). Diese Problematik kann noch allgemeiner gefaßt werden, denn generell ermöglicht diese bitweise Kopie eines Objekts, das Zeiger/Referenzen als Member besitzt, deren womöglich ungewollte Manipulation durch ein anderes Objekt (vgl. nebenstehendes Beispiel). Um dies zu verhindern, sollten stets ein benutzerdefinierter Copy-Konstruktor und ein Zuweisungsoperator angegeben werden. Deren Implementation ist dabei zweitrangig, da im Falle des Aufrufs der Linker einen Fehler meldet, sollte die Funktion nicht definiert sein [Meyers98]. struct Class { Class(int& r) : ref(r) { } ~Class() { ref = 0; } int& }; ref; void print(class c) { cout << c.ref << endl; } int main() { int i = 5; Class c(i); } func(c); // c.ref ist hier Mängel im Design Die Wartung, das heißt die Weiterentwicklung [Stroustrup00], von Software gehört ebenso zu deren Entwicklungsprozeß wie das erste Design. Oft kommt es vor, daß die Wartung von anderen Personen als dem ursprünglichen Team durchgeführt wird, was bei unzureichender Dokumentation zu sehr lokalen Änderungen und schnell zu einer Verschlechterung des Designs 18

29 KAPITEL 2: SOFTWAREQUALITÄT führt. Um dem zu entgehen, werden Vorgehensweisen im Softwaredesign [Stroustrup00] formuliert, Entwurfsmuster entwickelt [Gamma95] oder projektbezogene Richtlinien definiert, wie beispielsweise das bei Entwicklung von Embedded Systemen übliche Verbot von Mehrfachvererbung [Wietzke05b]. Ein Versuch der Beschreibung und Identifikation von Designmängeln findet sich z.b. in der Definition der objektorientierten Metriken von [Chidamber94], die in Kapitel 2.4 geschildert werden. So herrscht zwar Einigkeit darüber, daß sich Kopplung negativ auf das Design auswirkt und kohäsive Klassen wünschenswert sind, allerdings ist mit der automatisierten Erkennung solcher Mängel das Problem der Uneinigkeit etwa über die Definition der LCOM-Metrik (Kapitel 2.4.6) verbunden. Daher ist die manuelle Codeinspektion durch den Menschen noch immer vonnöten, um letztendlich beurteilen zu können, ob z.b. die starke Kopplung einiger Klassen eines Systems vielleicht unumgänglich ist Multiprocessing / -threading In Multiprozeß-/thread-Umgebungen bilden sich kritische Abschnitte, wenn parallel auf Betriebsmittel, also Speicher, Peripherie oder Dateien, zugegriffen wird. In der Praxis bereitet die Identifikation solcher Abschnitte oft Probleme, da in nichtparallelen Umgebungen triviale Konstrukte plötzlich problematisch sein können. Beim Übersehen eines kritischen Abschnitts entfällt die notwendige Synchronisation und schwer zu lokalisierende bzw. reproduzierende Fehler sind die Folge. Da das Scheduling ein dynamischer Prozeß ist, lassen sich kritische Abschnitte nicht exakt durch Codeinspektion identifizieren, allerdings ließen sich durch Betrachtung von Thread-Funktionen oder den Anweisungsblöcken nach einem fork()-aufruf die Problemzonen eingrenzen. Eine genaue Beschreibung dieses Ansatzes findet sich im Ausblick (Kapitel 5.3) Verwendung von Zustandsvariablen Die Verwendung von Zustandsvariablen kann in zustandsbasierten Systemen ein Problem darstellen. Üblicherweise werden in besagten Systemen Zustandsautomaten implementiert. Dabei werden im Entwurf, z.b. über das Zustandsdiagramm in UML (vgl. Abbildung 2.5.2), die möglichen Zustände und deren Übergänge definiert. Zustandsübergänge werden durch Ereignisse ausgelöst und ziehen Aktionen nach sich, was in einer sogenannten Zustandsübergangstabelle festgehalten wird. 19

30 KAPITEL 2: SOFTWAREQUALITÄT Bezogen auf die Entwicklung von Embedded Systemen schildert und bewertet [Wietzke05a] dazu verschiedene Umsetzungen, die im folgenden grob beschrieben werden. Die Aufzählungsmethode basiert dabei, gemäß ihrem Namen, auf der Verwendung von Enumerationstypen als Zustandsvariablen, anhand derer bei Zustandsübergängen die auszuführende Aktion bestimmt wird. Diese Variablen sind in einer Klasse gekapselt, die den Automaten repräsentiert. transition1 StateA transition2 transition3 StateB Abbildung 2.5.2: Beispiel eines Zustandsdiagramms Das Zustandsmuster nach [Gamma95] sieht für jeden Zustand eine eigene Klasse vor, die von einem Basiszustand abgeleitet ist. Die Aktionen werden dann in überschriebenen Ereignismethoden ausgeführt, die bei Zustandübergängen aufgerufen werden. Die Unflexibilität dieser Vorgehensweise liegt in der Pflicht, für jeden neuen Zustand eine separate Klasse und für jedes neue Ereignis dem Basiszustand eine entsprechende Ereignismethode hinzuzufügen. Als verbesserte Aufzählungsmethode stellt [Wietzke05a] eine Synthese der beiden letztgenannten Ansätze vor, in der nur Oberzustände eine eigene Klasse erhalten und ihre Unterzustände und zugehörige Ereignismethoden mit der Aufzählungsmethode privat verwalten. In der Praxis wird die Notwendigkeit, einen Zustandsautomaten zu implementieren, oft nicht wahrgenommen. Das Resultat sind ungekapselte Zustandsvariablen, die an verschiedenen Programmstellen konditional ausgewertet werden und entsprechende Aktionen nach sich ziehen. Das Problem dabei ist die Möglichkeit, Zustände bzw. mögliche Übergänge zu übersehen, was bei der Umsetzung einer der oben genannten Ansätze aufgrund der Kapselung unwahrscheinlicher ist. Vergessene Zustände bei der Verwendung von Zustandsvariablen führen zu unvorhersehbarem Verhalten, wenn das System eben einen solchen Zustand einnimmt. In dieser Arbeit findet sich ein Ansatz, solche Zustandsvariablen durch statische Codeanalyse zu identifizieren (Kapitel 4.4). 20

31 KAPITEL 3: EXISTIERENDE SOFTWARE ZUR QUALITÄTSMESSUNG Kapitel 3: Existierende Software zur Qualitätsmessung In diesem Kapitel wird eine Auswahl von existierenden Tools vorgestellt, die der Qualitätsmessung von C++ Projekten dienen. Die meisten dieser Tools ermitteln zumindest einige der in Kapitel 2.3 und 2.4 geschilderten Softwaremetriken, was auf deren praktische Bedeutung hinweist. Außerdem werden teils zusätzliche Maße ermittelt, die jeweils separat genannt werden, und bieten teils umfassende Möglichkeiten der Visualisierung McCabe IQ Dieses Tool der Firma McCabe Software basiert auf der Publikation [Watson96], worin die Anwendung des McCabe-Maßes und dessen Korrelation mit dem Testaufwand von Software beschrieben wird. Unter letzterem wird die These vertreten, daß die Anzahl notwendiger Tests eines Softwaremoduls dessen Zyklomatischer Abbildung 3.1.1: Kontrollflußgraphen in McCabe IQ Komplexität gleicht (vgl. hierzu Kapitel 2.3.2). McCabe IQ ermittelt laut der Produktbeschreibung auf (Stand: ) folgende Metriken (für eine detailierte Beschreibung sei hier auf [Watson96] verwiesen): McCabe Zyklomatische Komplexität McCabe Essentielle Komplexität, welche die Strukturiertheit des Codes bewerten soll. Dabei wird der Kontrollflußgraphen iterativ vereinfacht, indem Primitive der strukturierten Programmierung, wie if und while, aus diesem entfernt werden. Übrig bleibt 21

32 KAPITEL 3: EXISTIERENDE SOFTWARE ZUR QUALITÄTSMESSUNG ein Graph, der lediglich via goto realisierbare Strukturen enthält ( Spaghetti Code ). Die Metrik selbst ist das McCabe-Maß dieses reduzierten Graphen. Modul-Design-Komplexität (MDK), welche Abhängigkeiten zu anderen Modulen bewertet. Hier wird ebenfalls der Kontrollflußgraph reduziert, wobei sämtliche Knoten und Kanten entfernt werden, die nicht mit einem Call Node verbunden sind, der einen Funktionsaufruf und somit eine Abhängigkeit darstellt. Integrationskomplexität verwendet die lokale MDK, um eine das Gesamtsystem betreffende Aussage zu treffen. Konkret errechnet sich die Integrationskomplexität als S 1 = iv G i n 1, wobei iv(g) der MDK und n der Anzahl an Knoten im Graph entspricht. Dieser Wert gleicht der Anzahl notwendiger Integrationstests, bei denen alle Aufrufe unabhängig voneinander durchlaufen werden. Halstead Lines of Code Der Emittlung dieser Metriken schließt sich deren tabellarische oder graphische Darstellung an, wozu sich in Abbildung ein Beispiel findet CMT++ CMT++ ist ein Metrikentool der Firma Verifysoft. Es führt eine statische Codeanalyse durch, wobei ausschließlich konventionelle Metriken ermittelt werden, und gibt anschließend einen textuellen Bericht in diversen Dokumentenformaten aus. Konkret werden folgende Metriken errechnet. McCabe-Maß Lines of Code, wobei hier mehrere Zählstrategien Anwendung finden, und jeweils die Anzahl an Kommentar-, Leer- oder Programmcodezeilen ermittelt wird. Halstead-Metriken, wobei lediglich Schlüsselwörter und Operatoren als Halstead-Operatoren angenommen werden und keine der Standardbibliothek zugehörigen Bezeichner, wie im Beispiel von Kapitel Des weiteren werden bei einigen Halstead-Operatoren wie z.b. for (...) die Klammern nicht separat gezählt, sondern als zum Schlüsselwort zugehörig betrachtet. Maintainability Index 10 Die Abbildung stammt von der Webseite 22

33 KAPITEL 3: EXISTIERENDE SOFTWARE ZUR QUALITÄTSMESSUNG 3.3. Lint Lint ist ein statisches Überprüfungs-Tool, das von [Johnson78] als Ergänzung zu einem C- Compiler, mit der Begründung, daß ein solcher aus historischen und praktischen Gründen schnell und effizient arbeiten müsse, entwickelt wurde. Dieses Tool ermittelt, entgegen den anderen in diesem Kapitel vorgestellten Programmen, keine Metriken, sondern versucht, semantische Problemstellen zu finden, die ein Compiler nicht zu erkennen vermochte. Dazu zählen beispielsweise die Verwendung nichtinitialisierter Variablen, unerreichbarer Code nach break, return etc. und nicht verwendete Rückgabewerte von Funktionen, was heutzutage jedoch bereits Compiler melden. Allerdings gibt es mehrere Projekte zur Erweiterung, bzw. Neuentwicklung von Lint, um etwa auch C++ Code überprüfen zu können. Dazu zählt z.b. PClint von Gimpel Software, welches ungefähr 800 verschiedene Meldungen zu bekannten Programmierfehlern oder schlechtem Stil produzieren kann. Dazu zählen beispielsweise folgende: Die Verwendung des == -Operators bei Vergleichen von C-Strings (also char*) Arithmetische Modifikation eines ungesicherten Zeigers auf dynamisch allozierten Speicher, was dessen Freigabe erschwert. Beispiel: p = new X[n] + 2; Konstante Verwendung eines Zeigers, der jedoch nicht mit const initialisiert wurde: char *p = "abc"; for( ; *p; p++ ) print(*p); Fehlende Zugriffsspezifizierer bei Klassendefinitionen werden angemahnt, da diese in einer privaten Ableitung resultieren, meist jedoch eine öffentliche gemeint war, wenn nicht explizit private oder protected verwendet wird. class A : B { int a; }; // A leitet privat von B ab 3.4. CodeCheck CodeCheck von Abraxas Software ist ein kommandozeilenbasiertes Tool, welches, anhand einer vorgegebenen Datei mit Regeln, ein C/C++ Projekt analysiert und das Ergebnis in Form von Nachrichten zurückliefert [Cobb04]. Die Regel-Datei (Rule File) wird in einer CodeCheck eigenen, an C angelehnten Skriptsprache verfaßt. Dabei besteht jede Regel aus einer if- Anweisung, in der vordefinierte Variablen abgefragt werden und die, bei erfüllter Bedingung, eine Anweisung ausführt, die üblicherweise die Ausgabe einer Nachricht beinhaltet, jedoch auch komplexerer Natur sein und Kontrollflußanweisungen wie z.b. for enthalten kann. Die in der Kondition einer Regel abgefragten Variablen werden gemäß ihrer Ebene während der Analyse aktualisiert. So werden beispielsweise Funktionsvariablen (Function Variables), wie 23

34 KAPITEL 3: EXISTIERENDE SOFTWARE ZUR QUALITÄTSMESSUNG etwa fcn_locals, die die Anzahl lokal definierter Variablen in der gerade analysierten Funktion enthält, bei Erreichen des Endes einer jeden Funktion zurückgesetzt. CodeCheck unterscheidet eine Vielzahl von Variablentypen, wovon hier nur einige exemplarisch genannt werden. Declaration Variables: Hierunter finden sich Deklarationen betreffende Variablen, die nach jedem Deklarator zurückgesetzt werden. Mit deren Hilfe könnten beispielsweise nichtinitialisierte Zeiger erkannt werden. Function Variables: Über Funktionsvariablen erhält man unter anderem die Anzahl an Codezeilen oder die der Operatoren/Operanden nach Halstead, woraus sich dessen Metriken errechnen lassen. Identifier Variables: Durch Informationen zu einem Bezeichner (z.b. seinen Typ im Falle einer Variablen, oder ob er eine Funktion repräsentiert) ließen sich etwa Verstöße gegen verwendete Notationsregeln identifizieren. Structure and Class Variables: Hier finden sich Informationen zu eigenen Typdefinitionen (class, struct, union), wie z.b. der Anzahl an Members oder ob die Klasse einen Copy-Konstruktor besitzt. CodeCheck ermittelt damit eine Vielzahl von Werten und bietet mit den individuell programmierbaren Analyseregeln einen allgemeinen Ansatz zur Ermittlung vieler verschiedener Metriken oder von Problemstellen im Quellcode. 24

35 KAPITEL 4: IMPLEMENTATION DES TOOLS Kapitel 4: Implementation des Tools In diesem Kapitel wird die im Rahmen dieser Arbeit durchgeführte Entwicklung eines Qualitäts-Tools beschrieben. Zwischen der zugrundeliegenden Idee und dem Entwurf wurde ein Kapitel zum Parsen von C++ zwischengeschoben, da sich die Wahl eines geeigneten Parsers schwieriger gestaltete als erhofft. Nach dem Subkapitel über den Entwurf werden die beiden Aufgaben des Tools und deren Umsetzung erläutert. Dies sind die Erkennung von Zustandsvariablen und die Ermittlung einiger objektorientierter Metriken, die im Grundlagenkapitel 2 bereits betrachtet wurden Idee Wie bereits in Kapitel 3 beschrieben, existieren eine Anzahl von Tools, die den ihnen übergebenen Code qualitativ bewerten und ihre Aussagen meist basierend auf etablierten Softwaremetriken treffen. Die grundlegende Idee für ein eigenes Tool war nun die Orientierung an bekannten Problemen aus der Praxis, wie sie in Kapitel 2.5 Erwähnung finden. Der Einfachheit halber wurde davon ausgegangen, daß dem Tool nur fehlerfrei kompilierbarer Code übergeben wird, so daß z.b. keine syntaktischen Parser-Fehler zu befürchten sind. Die erste Stufe der durchgeführten Analyse sollte die Erkennung von Zustandsvariablen sein, wofür bislang kein geeignetes Tool zu existieren scheint. Anschließend sollte die Darstellung eines Vererbung und Aggregationen beinhaltenden Relationsgraphen eventuelle Designschwächen erkennbar machen. Darüber hinaus sollte eine Idee zur Erkennung von kritischen Abschnitten bei Verwendung von Multiprocessing/-threading umgesetzt werden, was jedoch aus zeitlichen Gründen nicht erfolgen konnte. Diese Idee findet deshalb im Ausblick (Kapitel 5.3) Erwähnung Parser Die im letzten Abschnitt geschilderten Ideen stellen eine Form der semantischen Codeanalyse dar, derer Umsetzung der Einsatz eines Parsers bedarf. Nun sollte die Entwicklung eines solchen Parsers nicht Bestandteil dieser Arbeit sein, und deshalb findet sich in diesem 25

36 KAPITEL 4: IMPLEMENTATION DES TOOLS Unterkapitel nur ein kurzer Abriß über die Theorie des Parsens von C++, damit verbundene Schwierigkeiten und die Auswahl des Parsers, welche zugunsten von Synopsis ausfiel, der nun letztlich die Basis des Tools bildet Parser allgemein Ein Parser für C++ wird im allgemeinen im Quellprogramm Rahmen der Erzeugung eines ausführbaren Pro Lexikalische Analyse gramms verwendet, weshalb in Abbildung die Illustration aus [Aho99], die den Ablauf eines Syntaxanalyse Compilers zeigt, zur Veranschaulichung übernommen wurde. Da in dieser Arbeit jedoch nur Symboltabellenverwaltung Semantische Analyse Zwischencode-Erzeugung Fehlerbehandlung die Codeanalyse von Belang ist, können auf die Phasen ab der Zwischencode-Erzeugung verzich Code-Optimierung tet werden. Code-Erzeugung Zielprogramm Abbildung 4.2.1: Phasen eines Compilers Die lexikalische Analyse wird vom sogenannten Lexer durchgeführt und umfaßt das sequentielle Abarbeiten des Programmquelltextes und die Einteilung der einzelnen Elemente in verschiedene Gruppen, wie z.b. Bezeichner, Literale, Schlüsselwörter, Operatoren. Während der lexikalischen Analyse werden außerdem bereits Leer- und sonstige Trennzeichen (Whitespaces) entfernt, die für die Programmiersprache nicht von Belang sind. if-anweisung if ( Ausdruck ) Anweisung Bezeichner >= Bezeichner Zuweisung var1 var2 Bezeichner = Ausdruck var1 Bezeichner - Literal var2 1 Abbildung 4.2.2: Beispiel eines Parse-Baums In der syntaktischen Analyse werden die vom Lexer klassifizierten Symbole (Tokens) in einem Parse-Baum hierarchisch entsprechend der Programmstruktur angeordnet. Um dies zu erreichen, muß die Struktur der Sprache in grammatikalischen Regeln vorliegen, die, um ein 26

37 KAPITEL 4: IMPLEMENTATION DES TOOLS vereinfachtes Beispiel zu nennen, besagen, daß in C++ ein Bezeichner, gefolgt vom einem =, gefolgt von einem Ausdruck, stets eine Zuweisung darstellt. In Abbildung findet sich ein Beispiel für einen Parse-Baum zu der Anweisung: if (var1 >= var2) var1 = var2 1; Für die syntaktische Analyse existieren verschiedene algorithmische Erkennungsansätze, die nach LL und LR kategorisiert werden [Willink01]. Bei beiden Ansätzen wird von links nach rechts gelesen. LL-Parser bilden dabei die Linksableitung und erstellen damit den Parse-Baum von der Wurzel zu den Blättern [Aho99]. In manuell geschriebenen Parsern wird meist das LL- Verfahren umgesetzt. LR-Parser bilden die Rechtsableitung und erzeugen den Baum von den Blättern ausgehend. Solche Parser sind aufgrund großer Symboltabellen schwer von Hand zu schreiben und werden meist durch Generatoren erzeugt. Das LR-Verfahren ist umfassender als LL und ermöglicht mit weniger Aufwand auch das Parsen von Sprachen, deren Grammatik Mehrdeutigkeiten enthält. Des weiteren werden Parser, die dieses Verfahren anwenden, in simple (SLR), Look-Ahead- (LALR) und kanonische LR-Parser gegliedert. An dieser Stelle sei bezüglich Parsing-Algorithmen auf [Aho99] und [Willink01] verwiesen, da eine detailiertere Beschreibung den Rahmen dieser Arbeit übersteigen würde. Zuweisung = Bezeichner = Ausdruck var1 - var1 Bezeichner - Literal var2 1 var2 1 Abbildung 4.2.3: Parse-Baum und AST Nach der syntaktischen führt die semantische Analyse inhaltliche Untersuchungen durch. Dabei werden insbesondere Typinformationen ausgewertet, um z.b. bei Ausdrücken, in denen float- und int-werte verrechnet werden, eine Warnung zu produzieren. In dieser Phase wird der in der Syntaxanalyse generierte Parse-Baum auch teilweise erweitert, um bestimmte Typumwandlungen zu automatisieren. So würde im nachfolgenden Beispiel während der semantischen Analyse die implizite Typumwandlung des Bezeichners i zu einem float in der if-anweisung eingefügt. int i = 5; float f = 3.5f; if (i > f) std::cout << "bigger" << std::endl; 27

38 KAPITEL 4: IMPLEMENTATION DES TOOLS Oft werden an dieser Stelle bestimmte Knoten im Parse-Baum zur Abstrahierung zusammengefaßt, wonach man vom sogennanten abstrakten Syntaxbaum (AST) spricht. Diese Zusammenfassung bzw. der Unterschied zum Parse-Baum liegt in der Anordnung der Knoten nach ihrem Prioritätsniveau und ist in Abbildung dargestellt. Dabei fällt insbesondere auf, daß nun Operatoren mit inneren Knoten anstatt mit Blättern assoziiert sind [Aho99] Parsen von C++ In der Grammatik von C++ treten Mehrdeutigkeiten auf, was insbesondere bei der Unterscheidung von Ausdrücken und Deklarationen problematisch werden kann. [Stroustrup00] merkt an, daß dies gelöst wird, indem von einer Deklaration ausgegangen wird, wenn etwas als Deklaration interpretiert werden kann. [Willink01] gibt dazu folgendes Beispiel: int(x), y, *const z; // (1) int x; int y; int *const z; int(x), y, new int; // (2) ((int(x)), (y), (new int)); int(x), y, z = 0; // (3) int x; int y; int z = 0; // oder ((int(x)), (y), (z = 0)); Zeile (1) ist dabei als Deklaration und (2) als Ausdruck anzusehen. Zeile (3) jedoch kann gleichermaßen als Ausdruck und Deklaration interpretiert werden, und die Erkennung der wahren Bedeutung kann potentiell unendlichen Look-Ahead, also weiteres Lesen von Quellcode, erfordern Untersuchung von Parsern / Generatoren Da, wie bereits erwähnt, keine Entwicklung eines Parsers für C++, sondern die bloße Codeanalyse im Zentrum dieser Arbeit stehen sollte, wurde zunächst ein passender Parser bzw. Parser-Generator gesucht. Er sollte folgenden Ansprüchen genügen: Bereitstellung eines Parse-Baums/AST, um daran die Codeanalyse durchführen zu können kompilierbar unter Windows, vorzugsweise mit VisualC kostenlos/offen Angesichts dieser Anforderungen boten sich die in den folgenden Unterkapiteln beschriebenen Kandidaten an. Im ersten Abschnitt werden dabei die in Betracht gezogenen Parser/ Generatoren genannt, sowie die Entscheidung gegen deren Einsatz begründet. Im zweiten Abschnitt findet sich eine Beschreibung des schließlich verwendeten Parsers Synopsis sowie dessen Unzulänglichkeiten, die zum Teil erst während der Implementationsphase des Tools erkannt wurden. 28

39 KAPITEL 4: IMPLEMENTATION DES TOOLS Kandidaten ANTLR Another Tool for Language Recognition bietet ein Framework, mit dem anhand von speziellen Grammatik-Dateien Parser in verschiedenen Programmiersprachen generiert werden können. Für ANTLR existiert eine Grammatik zu C++, die laut [Birkett01] schwer verständlich ist und die Mehrdeutigkeiten der Sprache unzureichend löste. Dennoch ließ sich damit ein unter VC71 kompilierbarer Parser erzeugen, der allerdings Laufzeitfehler produzierte, sobald Code untersucht werden sollte, der Klassen enthielt. ANTLR wurde darauf zwar weiterhin als möglicher Kandidat in Betracht gezogen, jedoch sollte zunächst nach einer Alternative gesucht werden, die fehlerfrei arbeitet. SableCC SableCC bietet, ebenso wie ANTLR, ein Framework zur Parser-Generierung. [Gagnon98] diente als Grundlage zur Entwicklung von SableCC und betont dessen streng objektorientierten Ansatz, sowohl beim Entwurf des Frameworks als auch des resultierenden Parser-Codes. SableCC diente ursprünglich zur Generierung von Parsern in Java, jedoch existieren mittlerweile Tools zur Erzeugung von C++ Code. Man entschied sich dennoch gegen den Einsatz dieses Frameworks, da zum Zeitpunkt der Entstehung dieser Arbeit keine Grammatik für C++, sondern lediglich für C nach dem aktuellen Standard von 1999 existierten. OpenC++ Das Projekt OpenC++ wurde explizit zur Entwicklung von Übersetzungs- und Analyse-Tools für C++ ins Leben gerufen. Dabei schreibt der Benutzer ein sogenanntes Meta- Level -Programm, welches sich mit dem OpenC++ Compiler zu regulärem C++ übersetzen läßt. Dieser Ansatz soll Spracherweiterungen wie etwa selbstdefinierte Schlüsselwörter ermöglichen und ließe z.b. durch Definition eines Meta-Objekts zum Schlüsselwort class die Erzeugung eines Ableitungsbaums zu. Diese Architektur sieht explizit auch die Übersetzung des Ziel-Codes mit dem OpenC++ Compiler vor, um die im Meta-Level-Programm definierte Analysefunktionalität zu implementieren. Um das in dieser Arbeit entwickelte Analyse-Tool mit einer graphischen Oberfläche zu versehen, wäre damit der Aufruf des projektexternen OpenC++ Compilers notwendig, was wiederum prozeßübergreifende Kommunikation erfordert hätte. Um dieser Erhöhung an Komplexität zu entgehen, bevorzugte man die Verwendung 29

40 KAPITEL 4: IMPLEMENTATION DES TOOLS eines in das Projekt eingebetteten Parsers. Zwar wird auch OpenC++ Core, das C++ Frontend (Lexer/Parser), als Bibliothek zur Verfügung gestellt, allerdings deutete dessen Roadmap noch auf fehlende Reife hin [ooc-core] Synopsis Synopsis wurde ursprünglich zur Dokumentation von Quellcode geschrieben. Dabei sollten z.b. Klassen- und Funktionsdefinitionen gefunden, hierarchisch angeordnet und zugehörige Kommentarabschnitte in einer abschließenden HTML-Seite dargestellt werden. Der für diese Aufgabe entwickelte C++ Parser stellt eine modifizierte Version des Parsers von OpenC++ dar und ist, ähnlich OpenC++ Core, auch separat erhältlich. Der Parser ließ sich mit VC71 unter Windows übersetzen 11, las erste Quelldateien fehlerfrei und erzeugt einen Parse-Baum, der sich mit einer am Visitor-Muster von [Gamma95] orientierenden Benutzerschnittstelle durchlaufen läßt (für eine detailierte Beschreibung siehe hierzu Kapitel 4.3.1). Obwohl bereits zu Beginn aus einer des Autors bekannt war, daß der Parser Probleme mit template-lastigem Code hat, entschied man sich für dessen Einsatz, da Templates in der Praxis noch immer selten weitergehend als für typunabhängige Containerklassen eingesetzt 12 und in vielen Projekten auch aufgrund schwieriger Fehlersuche verboten werden [Wietzke05b]. Ein weiteres Problem, das die Entwicklung des Analyse-Tools noch maßgeblich beeinflussen sollte, stellte sich jedoch erst im weiteren Projektverlauf heraus. So war der Symbol-Lookup der in dieser Arbeit verwendeten Version von Synopsis fehlerhaft, was sich darin äußerte, daß lokale Variablen außerhalb ihres Sichtbarkeitsbereichs angezeigt wurden, sobald eine Funktion einen nicht C++ eigenen Datentyp (z.b. int, void, float etc.) als Rückgabewert besitzt, was die Verwendung des Lookups und damit die Auflösung von Symbolen unmöglich machte. In den Kapiteln 4.4 und 4.5 werden die sich daraus ergebenen Konsequenzen ausführlich behandelt. Noch ein weiteres Problem, das erst während der Testphase des Tools zu Tage trat, kann bei der Verwendung von Makros auftreten. Synopsis ignoriert Präprozessordirektiven und löst demnach auch keine Makros auf. Der Versuch, z.b. MFC-Code zu parsen, worin mit deren Hilfe die Message-Map für Objekte auf der graphischen Oberfläche aufgebaut wird, endet im 11 In dieser Arbeit wurde Synopsis in der Version 0.8, bzw. ein Snapshot von Dezember 2005 verwendet. 12 Als Ausnahme seien hier die stark template-basierte STL, sowie die Entwurfsbeispiele von [Alexandrescu03] genannt. 30

41 KAPITEL 4: IMPLEMENTATION DES TOOLS Abbruch von Synopsis ohne jegliche Fehlermeldung. Keine Probleme ergeben sich aus Makros, die vom Aufbau her einer gewöhnlichen Anweisung gleichen im Falle von MFC scheitert Synopsis am fehlenden abschließenden Semikolon Entwurf Der Entwurf des Tools orientiert sich an der von Synopsis zur Verfügung gestellten Visitor- Klasse und dem damit realisierten Konzept zum Durchlaufen des generierten Parse-Baums. Im folgenden wird zunächst die Benutzerschnittstelle von Synopsis und anschließend das darauf aufbauende Konzept, sowie der Ablauf der Analyse geschildert Die Benutzerschnittstelle von Synopsis Der Parser von Synopsis erstellt zu jedem vom Lexer gefundenen Token ein Node-Objekt und ordnet diese in einem Parse-Baum an. Die Klasse Node spannt einen Vererbungsbaum auf, und ihre direkten Kinder sind List und Atom, die normale Knoten und Blätter respräsentieren (ein Beispiel dazu findet sich in Abbildung ). Zur Konkretisierung eines Knotens existiert eine Hierarchie von ungefähr 120 C++ spezifischen Typen, die von List abgeleitet sind, wie z.b. Declaration, IfStatement, FunctionDefinition und ClassSpec. Diese Hierarchie stellt prinzipiell die ObjectStructure des Visitor-Musters nach [Gamma95] dar. Dem entsprechend bietet Synopsis eine Visitor- Klasse, mit virtuellen visit()-methoden für jeden Knotentyp, zur Traversierung des Parse-Baums. Die Analyse geschieht in konkreten Visitor-Klassen, die von der genannten abgeleitet sind, nur die benötigten visit()-methoden implementieren und in den folgenden Abschnitten dieses Kapitels erläutert werden. Abbildung 4.3.1: Beispiel eines Parse-Baums in Synopsis Ablauf der Analyse Zur Durchführung der Analyse anhand des Parse-Baums ist eine vollständige Traversierung notwendig. Dazu wurde die sich rekursiv aufrufende Funktion walktree() geschrieben, die den gesamten Parse-Baum in einer Tiefensuche durchläuft. walktree() erhält den aktuellen 13 Die Abbildung stammt von der Webseite 31

42 KAPITEL 4: IMPLEMENTATION DES TOOLS Knoten im Baum und einen konkreten Visitor als Parameter, dessen visit()-methoden über node->accept() aufgerufen werden. Abbruchbedingungen bilden dabei das Besuchen eines Blattes und eines Knotens, der bereits besucht wurde. Um letzteres zu ermöglichen, wird die Hierarchie der konkreten Visitor-Klassen, welche die Analyse durchführen, von der Klasse VisitorBase aufgespannt. Diese bietet die boolsche protected-membervariable processed_node_, welche in walktree() abgefragt wird. Da Subbäume meist, wie in den nachfolgenden Abschnitten beschrieben, von anderen Visitor-Klassen durchlaufen werden, wird z.b. beim Besuch eines FunctionDefinition-Knotens processed_node_ = true gesetzt, um das mehrfache Durchlaufen der Funktionsdefinition zu vermeiden. Analyzer Synopsis::PTree::Visitor VisitorBase Message RelationGraph RawData ClassNode RelationInfo ClassFuncScanner DeclarationScanner FunctionScanner DeclaratorScanner AssignedCondExprScanner FuncallCondExprScanner IdentifierGatherer ManipulationScanner Abbildung 4.3.2: Klassendiagramm des Tools (ohne GUI) Die Traversierung des Baums beginnt in der Klasse Analyzer, welche einen ClassFuncScanner zum Besuchen von Klassen- und Funktionsdefinitionen erzeugt, dessen Nachrichtenliste der GUI zur Verfügung stellt und einen Relationsgraphen aufbaut. Die Klassenhierarchie ist in Abbildung abgebildet, wobei jedoch aus Gründen der Übersichtlichkeit die Benutzt -Beziehungen zu den Hilfsklassen IdentifierGatherer und ManipulationScanner, die von nahezu allen anderen Visitor-Klassen verwendet werden, nicht eingezeichnet wurden. Eine detailierte Beschreibung dieses Ablaufs sowie Aufbau und Verwendung jener Hilfsklassen findet sich im Rest dieses Kapitels. 32

43 KAPITEL 4: IMPLEMENTATION DES TOOLS Hilfsklasse IdentifierGatherer Den beiden Abschnitten zur eigentlichen Analyse vorweg soll an dieser Stelle das Klassentemplate IdentifierGatherer beschrieben werden, da es im folgenden öfters eingesetzt wird. Manche Abschnitte, wie z.b. im Falle des Bedingungsteils einer if-anweisung, verlangen das Sammeln von Bezeichnern, unabhängig vom Kontext, in dem sie stehen. IdentifierGatherer besucht Identifier- und Name-Knoten, wovon letzterer einen qualifizierten Namen repräsentiert und selbst aus einem Wechsel von Identifier und dem Atom :: besteht. Die Bezeichner werden im als Template-Parameter übergebenen Container gesammelt. IdentifierGatherer Container -identifiers_: Container& +visit(spt::identifier*) +visit(spt::name*) Abbildung 4.3.3: IdentifierGatherer 4.4. Erkennung von Zustandsvariablen Wie bereits in Kapitel beschrieben, ist die Verwendung von Zustandsvariablen anstelle eines vollwertigen Automaten gängige Praxis, allerdings auch eine Fehlerquelle. Die Erkennung von Zustandsvariablen war daher die erste zu implementierende Funktionalität des Tools. Im folgenden wird der Ansatz, nach dem das Tool arbeitet, sowie implementationsnah der Ablauf beschrieben. Anschließend findet sich noch eine Erläuterung der Probleme bzw. Grenzen des Erkennens von Zustandsvariablen Vorgehensweise Vom aktuellen Zustand eines Systems hängt sein Verhalten ab. Bezogen auf Zustandsvariablen bedeutet dies, daß der Programmfluß von ihnen abhängt, sie also Bestandteil von Konditionen sind. Nun läßt sich die Abfrage einer Zustandsvariablen nicht von der einer normalen Variablen unterscheiden, weshalb man sich hier auf die Zustandsübergänge des Systems fokussiert. Bei einem solchen muß der aktuelle Zustand des Systems abgefragt und abhängig davon sein neuer Zustand festgelegt werden. Um solche Übergänge bestmöglich eingrenzen zu können, sollte eine prozedurale Untersuchung erfolgen. Dabei wird weiterhin davon ausgegangen, daß die Zustandsvariable mehrere Komponenten des Systems beeinflußt, 33

44 KAPITEL 4: IMPLEMENTATION DES TOOLS somit also nicht lokal definiert ist. 14 Folgend die Zusammenfassung der drei grundlegenden Punkte dieses mit [Wietzke05b] erarbeiteten Ansatzes zur Identifikation von Zustandsvariablen. 1. Die Variable ist nicht lokal definiert. 2. Die Variable wird im Bedingungsteil einer Kondition ausgewertet. 3. Die Variable wird im Anweisungsteil der Kondition manipuliert. Sind diese drei Bedingungen erfüllt, wird von einer Zustandsvariablen ausgegangen. Die in Punkt 1 ausgeschlossene lokale Definition bezieht sich auf den Gültigkeitsbereich einer Funktion, d.h. daß Zustandsvariablen auf prozeduraler Ebene gesucht und identifiziert werden. Gemäß dem in Kapitel 4.3 beschriebenen Visitor-Konzept werden hier also Funktionsdefinitionen im Parse-Baum besucht und analysiert. VisitorBase -processed_node_: bool +visit(spt::node*) +visit(spt::atom*) +visit(spt::list*) SPT = Namespace Synopsis::PTree Message ClassFuncScanner -class_name_: string -database_: map<string, RawData>& -msgs_: list<message>& +visit(spt::namespacespec*) +visit(spt::classspec*) +visit(spt::functiondefintion*) -readderivations() -readaggregations() RawData FunctionScanner -locals_: set<string> -candidates_: set<string> +visit(spt::declaration*) +visit(spt::usingdirective*) +visit(spt::ifstatement*) +visit(spt::switchstatement*) +visit(spt::condexpr*) +visit(spt::assignexpr*) +visit(spt::funcallexpr*) +filterstatevariables() DeclaratorScanner -identifiers_: set<string>& +visit(spt::declarator*) AssignedCondExprScanner -candidates_: set<string>& -assigned_: string +visit(spt::condexpr*) FuncallCondExprScanner -candidates_: set<string>& +visit(spt::condexpr*) +line_: int +text_: string +baseclasses_: set<string> +aggregates_: set<string> IdentifierGatherer Container -identifiers_: Container& +visit(spt::identifier*) +visit(spt::name*) ManipulationScanner -identifiers_: set<string>& +visit(spt::assignexpr*) +visit(spt::funcallexpr*) Abbildung 4.4.1: Klassen zur Ermittlung von Zustandsvariablen Analyzer verwendet ClassFuncScanner als primäre Visitor-Klasse zum Durchlaufen des Parse-Baums. Stößt diese auf eine Funktionsdefinition, erzeugt sie einen FunctionScanner, der die weitere Analyse vornimmt und über die Methode filterstatevariables() die erkannten Zustandsvariablen zurückgibt. Der ClassFuncScanner generiert zu diesen dann je 14 Lokale Zustandsvariablen würden nur die Funktion selbst beeinflussen, womit die auf die Komplexität eines Systems zurückzuführende Problematik, daß nicht alle Zustände bzw. Zustandsübergänge erkannt werden (vgl. Kapitel 2.5.6), nur lokal begrenzt ist und keine weitreichenden Folgen hat. 34

45 KAPITEL 4: IMPLEMENTATION DES TOOLS ein Message-Objekt, in welchem sich Codezeile 15 und ein Hinweis, daß in der jeweiligen Funktionsdefinition eine Zustandsvariable gefunden wurde, befinden, das dann an die GUI weitergereicht wird. Der FunctionScanner durchsucht die Funktionsdefinition nach lokalen Variablendeklarationen und Konditionen, gemäß den oben genannten drei Punkten. Bezeichner lokaler Deklarationen werden in locals_ und solche, die zum einen in Konditionen ausgewertet als auch innerhalb deren Anweisungsblock manipuliert werden, in candidates_ gesammelt. Um diese weitergehende Analyse durchzuführen, werden, wie im Klassendiagramm von Abbildung ersichtlich, Hilfsklassen verwendet, deren Funktionsweise und Einsatz im folgenden geschildert werden Lokale Deklarationen Die Grammatik von C++ unterteilt Deklarationen in einzelne Deklaratoren, was die gängige Praxis kommaseparierter einzeiliger Deklarationen ermöglicht: int a, *b = 0, c[10]; Hier die zugehörige Darstellung als Parse-Baum von Synopsis: Deklaration int Liste ; Deklarator, Deklarator, Deklarator Bezeichner * Bezeichner = Literal Bezeichner [ Literal ] a b 0 c 10 Abbildung 4.4.2: Parse-Baum einer Deklaration Lokale Variablen stellen, selbst wenn die übrigen Anforderungen erfüllt sind, keine Zustandsvariablen eines Objekts dar. Daher müssen sämtliche Bezeichner einer Deklaration erkannt und später mit den konditionalen bzw. manipulierten Bezeichnern verglichen werden. Dazu erzeugt FunctionScanner im Falle einer Deklaration einen DeclaratorScanner, der sämtliche Bezeichner der Deklaration ermittelt und in einer Menge von Strings zurückgibt. Synopsis vereinfacht diesen Vorgang, da Declarator-Objekte bereits eine Methode besitzen, die diesen Bezeichner liefert. 15 Die Zeilennummer liefert Synopsis::Buffer::origin() unter Angabe der zum aktuellen Knoten gehörigen Position im Dateipuffer. 35

46 KAPITEL 4: IMPLEMENTATION DES TOOLS Using-Direktiven, also die lokale Auflösung eines Namensraums, stellen laut Grammatik Deklarationen dar [Std98: 7.3.4]. Allerdings sollen an dieser Stellen nur lokale Variablen ermittelt werden, weshalb FunctionScanner auch eine leere visit()-methode für Using- Direktiven implementiert, um für diese den Aufruf von visit(pst::declaration*) zu vermeiden Manipulationen Die Bestimmung von manipulierten Bezeichnern geschieht im Rahmen der Erkennung von Zustandsvariablen nur innerhalb der Anweisungsblöcke von Konditionen. Während der Analyse besucht der FunctionScanner zunächst Konditionen, prüft deren Bedingungsteil und erst anschließend den Anweisungsteil auf Manipulationen 16. Hier werden Abläufe in vertauschter Reihenfolge geschildert, da zur Erkennung von manipulierten Bezeichnern eigens eine Visitor-Klasse ManipulationScanner implementiert wurde, die stets auf den Anweisungsteil einer Kondition angewendet wird, was die Erläuterung im nächsten Kapitel erleichtert. Der ManipulationScanner sammelt alle von ihm erkannten manipulierten Bezeichner in einem std::set, welches der aufrufende FunctionScanner dann mit den Bezeichnern des Bedingungsteils vergleichen kann. Der Vorteil des sets liegt hier in der Eindeutigkeit seiner Einträge, so daß nicht explizit geprüft werden muß, ob der Bezeichner wegen einer anderen Manipulation bereits aufgenommen wurde. Als mögliche Formen der Manipulation wurden die Zuweisung, die Übergabe an eine manipulierende Funktion, die indirekte Manipulation via Referenz/Zeiger und die In-, bzw. Dekrementierung der Variable in Betracht gezogen. ManipulationScanner -identifiers_: set<string>& +visit(spt::assignexpr*) +visit(spt::funcallexpr*) Abbildung 4.4.3: ManipulationScanner Zuweisung Die Zuweisung ist die einfachste Form der Manipulation einer Zustandsvariable. Da hier links vom Zuweisungsoperator stets ein Bezeichner (auch lvalue genannt [Stroustrup00]) stehen muß, kann dieser einfach übernommen werden. Synopsis faßt sämtliche Zuweisungsoperatoren als AssignExpr zusammen, was an dieser Stelle auch die Erkennung verkürzter Operatoren wie +=, -= u.s.w. zuläßt. Zuweisungsausdruck Bezeichner = Ausdruck Abbildung 4.4.4: Aufbau eines Zuweisungsausdrucks 16 Eine Ausnahme bildet hier der Konditionalausdruck, wie in Kapitel beschrieben. 36

47 KAPITEL 4: IMPLEMENTATION DES TOOLS Übergabe an eine Funktion Die Übergabe an eine manipulierende Funktion ist eine weitere Möglichkeit. Um die Variable manipulieren zu können, muß die Funktion einen Zeiger oder eine Referenz darauf erhalten. Wegen des in Kapitel beschriebenen fehlerhaften Symbol-Lookups ergibt sich an dieser Stelle ein Problem. Zwar läßt sich die Übergabe als Zeiger anhand des Adreßoperators & auch beim Funktionsaufruf erkennen, allerdings ist die Übergabe als Referenz nicht von der als Wert zu unterscheiden, was eine Überprüfung der Definition der aufgerufenen Funktion erfordern würde. Dazu wären folgende Schritte erforderlich: Lookup des Bezeichners der aufgerufenen Funktion ausgehend vom aktuellen Gültigkeitsbereich. Der Bezeichner könnte einen Funktionszeiger repräsentieren, dessen eigene Manipulationen wiederum untersucht werden müßten, um die konkrete Funktion zu ermitteln. Dazu hier ein Beispiel: int (*funcptr)(int&);... int funca(int& varref) {... }... int funcb(int& varref) {... }... funcptr = funca; // Diese Zeile müßte ermittelt werden... // In der auf Zustandsvariablen zu untersuchenden Kondition: funcptr(statevar); Die Zuweisung des Funktionszeigers selbst könnte allerdings wiederum von dynamischen, womöglich nicht aus dem Code ersichtlichen Werten, wie beispielsweise der Uhrzeit oder eingelesenen Dateiinhalten, abhängen, was die Identifikation der tatsächlich verwendeten Funktion unter Umständen, selbst mit Symbol-Lookup, der statischen Codeanalyse unmöglich macht. Der Bezeichner könnte einen Funktor repräsentieren, dessen Typ durch Lookup ermittelt werden müßte. Die manipulierende Funktion wäre dann der überladene ()- Operator der Klasse. Da der Zugriff auf den Funktor auch über einen Zeiger / eine Referenz geschehen könnte, ergeben sich hier die gleichen Probleme wie bei Funktionszeigern. Im Falle von mehreren gleichnamigen Funktionen im Gültigkeitsbereich wäre die Ermittlung des Typs der übergebenen Variable, also jener, die als manipuliert identifiziert werden soll, erforderlich. Auch dies setzt einen Lookup voraus, da die Variable ja per Definition (Kapitel 4.4.1) nicht lokal definiert wurde. 37

48 KAPITEL 4: IMPLEMENTATION DES TOOLS Ohne Symbol-Lookup sind diese Schritte also nicht zu bewerkstelligen, weshalb man sich dazu entschloß, alle an Funktionen übergebene Bezeichner als manipuliert anzusehen. Dies trägt zur Verunschärfung des Endergebnisses bei, da somit auch Übergaben als Wert als Manipulation gewertet werden. Allerdings werden auch alle anderen Formen der Parameterübergabe berücksichtigt, Zustandsvariablen nach in Kapitel genannter Definition also ebenfalls erkannt. Funktionsaufrufe, bzw. die syntaktisch gleiche Verwendung des ()-Operators eines Funktors, finden sich als FuncallExpr in Synopsis Parse-Baum und besitzen die in Abbildung gezeigte Struktur. Zur Ermittlung der übergebenen Funktionsparameter erzeugt der ManipulationScanner beim Besuch eines solchen Knotens einen IdentifierGatherer (Kapitel 4.3.3), der die optionale Parameterliste des Funktionsaufrufs durchläuft und die Bezeichner als Menge von Strings zurückliefert. Funktionsaufruf Bezeichner ( Liste (optional) ) Ausdruck, Ausdruck..., Ausdruck Abbildung 4.4.5: Aufbau eines Funktionsaufrufs Manipulation via Zeiger/Referenz Analog zur im letzten Abschnitt erwähnten Problematik mit Funktionszeigern ist auch die indirekte Manipulation des Kandidaten einer Zustandsvariable nicht ohne Symbol-Lookup lösbar. Selbst mit einem solchen könnte, wie oben bereits erwähnt, die Abhängigkeit von externen dynamischen Daten die Auflösung der echten Zustandsvariable unmöglich sein. Die aktuelle Implementation würde, sofern nicht lokal definiert, den Bezeichner des Zeigers / der Referenz als manipulierte Variable erkennen, dessen Referenzobjekt kann das Tool daraus jedoch nicht ableiten Inkrement/Dekrement Die In- bzw. Dekrementierung der Variable stellt ebenfalls eine Möglichkeit der Manipulation dar (im folgenden wird nur noch das Inkrement genannt, gemeint sind jedoch stets beide Operationen). Würde dies berücksichtigt, müßte bereits im Bedingungsteil der Kondition auf ein eventuelles Inkrement geprüft werden. Zur Veranschaulichung hier ein kurzes Beispiel: if (state && state++ > STATE_A)... 38

49 KAPITEL 4: IMPLEMENTATION DES TOOLS Da laut Standard der rechte Teil des logischen UND nicht ausgeführt werden muß, wenn der linke Teil fehlschlug [Std98: 5.14], hängt die Manipulation durch das Postinkrement von state selbst ab, womit eine Zustandsvariable vorläge. Man entschied sich gegen die Behandlung der Inkrementierung, da nach gängiger Praxis Zustände nicht gezählt werden [Wietzke05b], sondern sich gezielt, entsprechend einer Zustandsübergangstabelle, ändern (siehe Kapitel 2.5.6) Konditionen Das Tool untersucht drei verschiedene Konditionen: if, switch und den seltener verwendeten Konditionalausdruck, der durch den ternären Operator?: realisiert wird und prinzipiell eine verkürzte if-anweisung darstellt, jedoch zusätzlich als Ausdruck ausgewertet wird. Alle diese Anweisungen haben eine andere syntaktische Struktur, weshalb ihre Auswertung hier einzeln behandelt werden soll if-anweisung FunctionScanner -locals_: set<string> -candidates_: set<string> +visit(spt::declaration*) +visit(spt::usingdirective*) +visit(spt::ifstatement*) +visit(spt::switchstatement*) +visit(spt::condexpr*) +visit(spt::assignexpr*) +visit(spt::funcallexpr*) +filterstatevariables() Abbildung 4.4.6: FunctionScanner Abbildung 4.4.7: Aufbau einer if-anweisung Für if-anweisungen ermittelt der FunctionScanner in der entsprechenden visit()-methode zunächst alle Bezeichner des Bedingungsteils via IdentifierGatherer (Kapitel 4.3.3) und anschließend, unter Verwendung des im letzten Kapitel beschriebenen ManipulationScanners, die manipulierten Bezeichner und fügt zum Schluß die in beiden Mengen vorkommenden Bezeichner dem Container candidates_ hinzu. Da der boolsche Ausdruck im Bedingungsteil der if-anweisung beliebig komplex sein und auch mehrere Variablen gegeneinander prüfen kann, werden pauschal sämtliche Bezeichner daraus übernommen. Ob sich daraus Probleme ergeben, da ja auch Funktionen und Konstanten repräsentierende Bezeichner verwendet werden können, soll das folgende Beispiel zeigen: if-anweisung (optional) if ( Ausdruck ) Anweisung else Anweisung if (var == CONST) CONST = anothervar; // nicht umöglich if (func() == var) func = anothervar; 39

50 KAPITEL 4: IMPLEMENTATION DES TOOLS Wie das linke Beispiel veranschaulicht, stellt die Übernahme von Konstantenbezeichnern kein Problem dar, denn diese können nicht nachträglich manipuliert werden 17. Das rechte Beispiel ist nur möglich, wenn func einen Funktionszeiger/Funktor darstellt. Damit würde func gemäß den Anforderungen als Zustandsvariable identifiziert switch-anweisung switch-anweisung switch ( Ausdruck ) Block case-anweisung case-anweisung... (optional) { default-anweisung } case Konst. Ausdruck : Anweisung default : Anweisung Abbildung 4.4.8: Aufbau einer switch-anweisung switch-anweisungen werden genauso wie if-anweisungen behandelt. Der ManipulationScanner durchläuft den Anweisungsblock und erreicht damit sämtliche Manipulationen innerhalb der case-anweisungen, wie das obige Schaubild zeigt 18. Als Ausnahme zur if-anweisung wird davon ausgegangen, daß der Ausdruck im Bedingungsteil aus einem Variablenbezeichner besteht. Zwar legt der Standard lediglich fest, daß der Ausdruck ein Integral- oder Enumerationstyp sein muß [Std98: 6.4], allerdings ist an dieser Stelle ein simpler Bezeichner als Ausdruck üblich. 17 Eine Ausnahme bildet die Parameterübergabe an eine Funktion, aber dieses Problem liegt auf Seite des ManipulationScanners, wie bereits in Kapitel geschildert. 18 Gemäß der Grammatik von C++ müßte die switch-anweisung in Abbildung anstelle des Blocks nur eine Anweisung haben [Std98: 6.4]. Zur Veranschaulichung wurde hier jedoch deren gängige Verwendung mit Anweisungsblock und mehreren (optionalen) case-anweisungen abgebildet. 40

51 KAPITEL 4: IMPLEMENTATION DES TOOLS Konditionalausdruck Der Konditionalausdruck erfordert wegen seiner flexiblen Einsatzfähigkeit einen größeren Aufwand als if- und switch-anweisungen. Prinzipiell entspricht der Konditionalausdruck der verkürzten Schreibweise einer if-anweisung und wird auch üblicherweise als solche verwendet: Ausdruck Abbildung 4.4.9: Aufbau eines Konditionalausdrucks Konditionalausdruck? Ausdruck : Ausdruck var = cond? value1 : value2; statt if (cond) var = value1; else var = value2; Aus diesem Beispiel wird bereits das Problem ersichtlich, das eine andere Behandlung des Konditionalausdrucks gebietet. Zwar haben beide Anweisungen oben dieselbe Wirkung, allerdings ergeben sich unterschiedliche Parse-Bäume. Zuweisung if-anweisung Bezeichner = Konditionalausdruck if ( Bezeichner ) Zuweisung else Zuweisung var Bezeichner? Bezeichner : Bezeichner cond Bezeichner = Bezeichner Bezeichner = Bezeichner cond value1 value2 var value1 var value2 Abbildung : Parse-Bäume von Konditionalausdruck und if-anweisung Die Abbildung zeigt, daß durch die Berücksichtigung von Konditionalausdrücken bereits auf Ebene des FunctionScanners die Ermittlung von Manipulationen erforderlich ist, da sich der Bedingungsteil tiefer im Parse-Baum befindet. Zu diesem Zweck implementiert schon FunctionScanner die vom ManipulationScanner bekannten visit()-methoden für AssignExpr- und FuncallExpr-Knoten, die den jeweiligen Subbaum mit einer eigens dafür geschriebenen Visitor-Klasse durchlaufen (FunctionScanner besucht auch direkt CondExpr- Knoten, falls diese nicht als Bestandteil einer Zuweisung oder eines Funktionsaufrufs verwendet werden). Für Zuweisungen wird der Ausdruck rechts vom Operator mit einem AssignedCondExprScanner-Objekt nach Konditionalausdrücken durchsucht, bei Funktionsaufrufen erledigt dies ein FuncallCondExprScanner für die Parameterliste. Im folgenden nun eine Beschreibung der Unterschiede der beiden genannten Klassen. 41

52 KAPITEL 4: IMPLEMENTATION DES TOOLS AssignedCondExprScanner Diese Visitor-Klasse führt die gleichen Schritte durch, wie die Behandlung von if- Anweisungen, also die Sammlung der Bezeichner im Bedingungsteil via IdentifierGatherer und die Anwendung eines ManipulationScanners auf die beiden resultierenden Ausdrücke, um auch solche Konstrukte richtig zu erkennen: has_changed = (state == STATE_A)? setstate(state) : false; Der AssignedCondExprScanner ermöglicht die Übergabe eines manipulierten Bezeichners im Vorfeld, was im Falle einer Zuweisung das lvalue wäre. Dies ist optional, und deshalb wird die Visitor-Klasse auch von FunctionScanner:: visit(spt::condexpr*) verwendet. FuncallCondExprScanner Hier werden die gleichen Schritte wie vom AssignedCondExprScanner unternommen, mit dem Zusatz, daß die beiden Ausdrücke des if-/else-zweigs noch einmal komplett als manipuliert übernommen werden. Da bei einem Funktionsaufruf der übergebene Bezeichner als manipuliert angesehen wird, ist dieser Schritt z.b. hierfür notwendig: state_manipulating_func(state1 == STATE_A? state1 : state2); Stünde in den resultierenden Zweigen ein komplexerer Ausdruck als nur ein Bezeichner, würde dieser zwar ebenfalls als manipulierter Bezeichner gespeichert, allerdings nicht als Zustandsvariable übernommen, da er dann so nicht im Bedingungsteil stehen wird. Diese Umsetzung ermöglicht weitestgehend die Behandlung aller Anwendungen des vielfältig einsetzbaren Konditionalausdrucks und Erkennung darin verwendeter Zustandsvariablen gemäß obiger Defintion. Eine Ausnahme bilden verschachtelte Konditionalausdrücke: // Die Klammern sind nicht notwendig, sollen lediglich das Lesen erleichtern. x = (a > 1)? (var > 2? c : d) : change(&var); Solche Konstrukte ließen sich zwar rekursiv auflösen, erforderten aber auf jeder Rekursionsebene eine Überprüfung, ob Manipulation und Bedingung zueinander in Beziehung stehen. Im Beispiel hängt von der Variablen var eine Bedingung ab, und sie wird manipuliert. Da ihre Manipulation aber nur von a abhängt, wäre dies keine Zustandsvariable. Man entschied sich gegen eine derartige Behandlung, da bereits die beschriebenen Konditionalausdrücke teils sehr speziell sind und verschachtelte Konstrukte dieser Art womöglich nie Anwendung finden, obwohl die Grammatik von C++ dies erlaubt. 42

53 KAPITEL 4: IMPLEMENTATION DES TOOLS Zustandsobjekte Üblicherweise werden simple Datentypen wie enums für Zustandsvariablen verwendet. Bei Benutzung komplexerer Typen, stellt sich erneut die Fragen, was eine Manipulation ist, da sich Objektzustände nicht bloß durch (in)direkte Zuweisung, sondern auch durch Aufruf von Methoden manipulieren lassen. Sinnvoll wäre daher, bei Zustandsobjekten Const-Correctness vorauszusetzen, also daß jene Methoden als const definiert sind, die nicht manipulierend auf das Objekt wirken. Das Tool ist nicht in der Lage, den Aufruf einer nichtkonstanten Methode als Manipulation zu werten. Dazu wäre die Auflösung des Objekttyps notwendig (Lookup), woraus sich dieselbe Problematik ergibt wie bei der Manipulation durch Funktionszeiger/Funktoren (Kapitel ). Mit der implementierten auf lokalen Bezeichnern basierenden Überprüfung ließe sich im folgenden Beispiel obj also in der linken, jedoch nicht in der rechten Anweisung als Zustandsvariable, bzw. -objekt identifizieren. if (obj.member) manipulate(obj); if (obj.member) obj.manipulate(); 4.5. Objektorientierte Metriken Nach der Erkennung von Zustandsvariablen sollte als nächste Aufgabe an das Tool die Erkennung bestimmter Designmängel, wie sie in Kapitel beschrieben sind, stehen. Die meisten der beschriebenen Mängel orientieren sich am Ableitungsbaum, weshalb dessen Generierung an erster Stelle stand. Mit Hinblick auf die Objektkopplung (Kapitel 2.4.4) wurde entschieden, daß nicht nur Vererbungsbeziehungen interessant sind, so daß das Tool einen Relationsgraph erstellen soll, der die Klassenhierarchie einschließlich Mehrfachvererbung sowie Aggregationen umfaßt 19. Im folgenden soll nun auf dessen Generierung sowie die Analyse anhand des Relationsgraphen eingegangen werden. 19 Daraus ergibt sich die Möglichkeit von Zyklen und Kreisen, weshalb graphentheoretisch nicht mehr von einem Baum gesprochen werden kann. 43

54 KAPITEL 4: IMPLEMENTATION DES TOOLS Relationsgraph Der Relationsgraph stellt den Kern der Qualitätsanalyse durch objektorientierte Metriken dar. Im folgenden soll die Ermittlung einer string-orientierten Datenbasis aus dem Parse- Baum, deren Weiterverarbeitung zum Relationsgraphen und eine abschließende Zusammenfassung von dessen Struktur geschildert werden. Im den darauffolgenden Subkapiteln wird dann die Ermittlung objektorientierter Metriken beschrieben Ermittlung der Datenbasis Die Datenbasis für die Generierung des Relationsgraphen besteht aus einer std::map mit dem qualifizierten Klassennamen als Schlüssel 20 und einem RawData-Objekt als Wert. Dieses Objekt beinhaltet je ein std::set der Basisklassen und Aggregate der Schlüsselklasse. Diese Bezeichner werden direkt aus der Klassendefinition im Quellcode übernommen und sind somit nicht unbedingt qualifiziert. VisitorBase -processed_node_: bool SPT = Namespace Synopsis::PTree +visit(spt::node*) +visit(spt::atom*) +visit(spt::list*) IdentifierGatherer Container -identifiers_: Container& +visit(spt::identifier*) +visit(spt::name*) ClassFuncScanner -class_name_: string -database_: map<string, RawData>& -msgs_: list<message>& +visit(spt::namespacespec*) +visit(spt::classspec*) +visit(spt::functiondefintion*) -readderivations() -readaggregations() DeclarationScanner -aggregates_: set<string>& +visit(spt::declaration*) Message +line_: int +text_: string RawData +baseclasses_: set<string> +aggregates_: set<string> Abbildung 4.5.1: Klassen zur Ermittlung von Klassendefinitionen Kern der Ermittlung der Datenbasis ist, wie auch bei der Identifikation von Zustandsvariablen, die Visitor-Klasse ClassFuncScanner. Hier werden jedoch die anderen beiden visit()-methoden betrachtet, die bei Namensraum- und Klassendefinitionen aufgerufen 20 Die Map ist als Suchbaum realisiert, was das Hinzufügen neuer Klassen erleichtert. 44

55 KAPITEL 4: IMPLEMENTATION DES TOOLS werden (NamespaceSpec und ClassSpec siehe dazu Abbildung 4.5.1). Das Besuchen von Namensraumdefinitionen dient der Emittlung des qualifizierten Klassennamens. Dabei wird der Bezeichner des Namensraums dem Member class_name_ hinzugefügt 21 und über einen lokalen ClassFuncScanner, welcher den aktuellen Klassennamen, bzw. dessen bislang ermittelten Qualifizierer übergeben bekommt, der Parse-Subbaum des Namensraums durchlaufen. Im Falle von Klassendefinitionen wird zunächst die Liste der Basisklassen in der Hilfsmethode readderivations() ermittelt. Anschließend wird ein Message-Objekt generiert, das die Zeilennummer der Klassendefinition erhält sowie ggf. die Warnung, daß es sich um eine Mehrfachableitung handelt, und der Liste msgs_ zwecks Weitergabe an die Benutzeroberfläche hinzugefügt. Schließlich wird der Rumpf der Klasse mit Hilfe der Methode readaggregations() auf die Verwendung anderer Klassen hin untersucht. Folgend findet sich eine Ablaufbeschreibung der beiden genannten Hilfsmethoden. readderivations() Die Methode erhält den Knoten im Parse-Baum, ab dem die Liste der Basisklassen beginnt und iteriert anschließend durch diese. Jeder Eintrag wird von einem IdentifierGatherer durchlaufen, um eventuell führende, die Art der Ableitung bestimmende Schlüsselwörter zu filtern (public, protected, private, virtual). Die Klassennamen werden in einem std::set gespeichert und mit diesem anschließend ein Eintrag in database_ erzeugt. readaggregations() Zur Ermittlung von Aggregationen wird das Durchlaufen des Klassenrumpfs einem DeclarationScanner überlassen. Dieser besucht, gemäß seinem Namen, Deklarationen und prüft in der entsprechenden visit()-methode deren Typ. Funktionsdeklarationen, sowie solche, deren Typ einen C++ Datentyp darstellt, werden ignoriert. Ebenso Deklarationen von eingebetteten Klassen, da dies zunächst nur eine Assoziation darstellt, Aggregationen jedoch eine Member-Variable von diesem Typ erfordern. Wurde eine Aggregation gefunden, wird der Bezeichner analog zu readderivations() via IdentifierGatherer extrahiert und einer std::set hinzugefügt, die ebenfalls abschließend in einem database_-eintrag endet. 21 Unbenannte Namensräume werden dabei ignoriert. Zwar dienen auch diese zur Einschränkung der Sichtbarkeit von Variablen, Funktionen etc., allerdings arbeitet das Tool auf schlicht aneinandergehängten Dateien, wodurch die Information über die Lokalität von Übersetzungseinheiten verloren geht (siehe Kapitel 4.3.2). Dadurch sind globale Variablen nicht mehr von solchen in unbenannten Namensräumen unterscheidbar. 45

56 KAPITEL 4: IMPLEMENTATION DES TOOLS An dieser Stelle kann es wieder aufgrund des fehlenden Symbol-Lookups zu Problemen kommen. Da readaggregations() lediglich auf Built-in-Typen prüft und ansonsten den Bezeichner einfach übernimmt, werden z.b. eigene enum- oder via typedef umbenannte Typen als Klassen angesehen und somit auch zugehörige Knoten im Relationsgraphen erzeugt (und zwar als unbekannte Basisklasse siehe folgender Abschnitt). Um dies zu vermeiden, müßte zu jedem Bezeichner ein Lookup erfolgen, welcher Typ sich tatsächlich dahinter verbirgt Generierung des Graphen Anhand der ermittelten Datenbasis wird nun der Graph erzeugt. In Abbildung sind die daran beteiligten Klassen dargestellt. Die Klasse RelationGraph bekommt die Datenbasis übergeben und enthält die Liste graph_, die später den Graph aufspannt. Die Methode create() generiert den Relationsgraph anhand der zuvor ermittelten database_. Dabei werden linear alle erkannten Klassen durchlaufen und zunächst ClassNodes für alle Basisklassen, also jene, RelationGraph -database_: map<string, RawData>& -graph_: list<ptrclassnode> -temp_: list<ptrclassnode> +create() +print() -connectbaseclasses() -connectaggregates() -findcandidatesfrompartialname() -createclassnode() -createconnection() RawData +baseclasses_: set<string> +aggregates_: set<string> Abbildung 4.5.2: An der Erzeugung des Relationsgraphen beteiligte Klassen ClassNode -name_: string -relations_: set<relationinfo> +getname() +getrefrelations() +getdepthofinhtree() +getnumofchildren() +addrelation() RelationInfo +related_class_: WPtrClassNode +relation_type_: enum +uncertain_: bool bei denen RawData::baseclasses_ leer ist, der Liste graph_ hinzugefügt 22. Anschließend erzeugen die beiden internen Methoden connectbaseclasses() und connectaggregates() die zur aktuellen Klasse benötigten Knoten im Graph. Beide Methoden laufen nach demselben Schema ab, das im folgenden Quelltextauszug dargestellt ist. void RelationGraph::connectBaseClasses(DBType::const_iterator& itclass, WPtrClassNode& actual_class) { for (RawData::SetType::const_iterator it2 = itclass->second.baseclasses_.begin(); it2!= itclass->second.baseclasses_.end(); ++it2) { ListType candidates = findcandidatesfrompartialname(*it2, itclass->first); if (candidates.size() == 1) { PtrClassNode parent = createclassnode(*candidates.begin()); createconnection(parent, actual_class, DERIVATION); } 22 Die ClassNode-Objekte werden im Freispeicher angelegt. Wenn im folgenden von Zeigern darauf die Rede ist, so handelt es sich um PtrClassNode-, bzw. WPtrClassNode-Objekte, die dem Typ boost:: shared_ptr<classnode>, respektive boost::weak_ptr<classnode> entsprechen. Eine Erläuterung dieser Smart-Pointer-Klassen und ihrer Anwendungsregeln findet sich im Anhang. 46

57 KAPITEL 4: IMPLEMENTATION DES TOOLS } } else if (candidates.size() > 1) { // unsichere Verbindungen, da mehrere Kandidaten existieren for (ListType::iterator cand = candidates.begin(); cand!= candidates.end(); ++cand) { PtrClassNode parent = createclassnode(*cand); createconnection(parent, actual_class, DERIVATION, true); } } else // candidates ist leer { } PtrClassNode parent = createclassnode(*it2, true); createconnection(parent, actual_class, DERIVATION, true); Hier eine Erläuterung der wichtigsten dabei verwendeten Methoden: findcandidatesfrompartialname() In Kapitel wurde bereits der fehlerhafte Symbol-Lookup des diesem Tool zugrundeliegenden Parsers von Synopsis erwähnt, der sich erst im Projektverlauf herausstellte. Diese Methode soll dem dadurch auftretenden Problem einer eventuellen Mehrdeutigkeit bei der Verwendung von unqualifizierten Bezeichnern in Klassendefinitionen begegnen, welches im folgenden kurzen Codeabschnitten veranschaulicht wird. namespace ns1 { class A {}; } namespace ns2 { class A {}; } using namespace ns1; class B : public A {}; namespace ns1 { class A {}; } namespace ns2 { class A {}; class B : public A {}; } Um im linken Codeabschnitt eindeutig ermitteln zu können, daß B von ns1::a abgeleitet ist, bedarf es des Symbol-Lookups, da geprüft werden muß, ob sich der Gültigkeitsbereich der using-direktive mit dem der Klassendefinition von B deckt. Da der Symbol- Lookup nicht verwendet werden kann, werden using-direktiven ignoriert. Wie bereits in Kapitel geschildert, wird für jede Klassendefinition ihr qualifizierter Name gespeichert. Anhand dieser Liste versucht nun die Methode findcandidatesfrompartialname() eindeutig einen Kandidaten als Basisklasse zu finden. Dabei wird zunächst überprüft, ob ein Kandidat im selben Namespace wie die definierte Klasse, also eine Klasse mit demselben Qualifizierer, existiert. In diesem Fall (wie z.b. im rechten Codebeispiel) wäre die Klasse eindeutig definiert, da die zusätzliche Auflösung eines anderen Namensraums, der eine gleichnamige Klasse 47

58 KAPITEL 4: IMPLEMENTATION DES TOOLS enthält, in einem Compiler-Fehler resultieren würde, das Tool aber von kompilierbarem Code ausgeht. Existiert kein solcher eindeutiger Kandidat, werden sämtliche Klassen zurückgegeben, die den unqualifizierten Namen der Klassendefinition rechtsbündig enthalten. Im linken Codeabschnitt ist der unqualifizierte Name A und die Kandidaten für die Basisklasse von B wären ns1::a und ns2::a. Die in den oben genannten Beispielen gezeigte Problematik ist analog bei Aggregationen vorhanden, weshalb findcandidatesfrompartialname() genauso bei connectaggregates() aufgerufen wird. createclassnode() Hier wird zunächst überprüft, ob bereits ein Knoten zum übergebenen Klassennamen erzeugt wurde. Dazu wird intern neben graph_ noch eine Liste temp_ geführt, die Zeiger auf alle bislang erstellten ClassNodes enthält, um die Suche danach zu erleichtern. Zurückgegeben wird ein Zeiger auf den unter Umständen neu erstellten Knoten. createconnection() Erstellt ein neues RelationInfo-Objekt und fügt dieses dem Elternknoten via addrelation() hinzu. Dieses Objekt enthält einen Zeiger auf den übergebenen Kindsknoten, den Typ der Relation (Ableitung, Aggregation) und einen boolschen Wert, der die Relation ggf. als unsicher markiert, sollte findcandidatesfrompartialname() keinen eindeutigen Kandidaten ermittelt haben. 48

59 KAPITEL 4: IMPLEMENTATION DES TOOLS Struktur Aggregation Liste der A B C Basisklassen... Ableitung Ableitung (unsicher) Aggregation Ableitung Ableitung ClassNode RelationInfo A1 AC C1 Aggregation Abbildung 4.5.3: Beispiel eines Relationsgraphen Nach der im letzten Abschnitt beschriebenen Generierung des Relationsgraphen befindet sich in RelationGraph::graph_ nun eine Liste mit Zeigern auf ClassNodes, die die Basisklassen repräsentieren. Jeder ClassNode enthält eine Liste von RelationInfo-Objekten, die wiederum auf Kinds-, bzw. aggregierte Knoten zeigen. Es handelt sich dabei nicht um eine Sammlung von Bäumen (auch, wenn davon ausgehend die OO-Metrik Depth of Inheritance Tree ermittelt wird und deren Name auf einen Baum hindeutet; eine Frage, die sich daraus ergibt, wird in Kapitel behandelt), da auch Aggregationen berücksichtigt werden, was in baumübergreifenden Kanten resultiert und damit Zyklen und Kreise entstehen können. Ein Beispiel für einen solchen Relationsgraphen findet sich in Abbildung Der Zweck des Relationsgraphen besteht zum einen in der Ermittlung einiger Metriken, wie sie in den folgenden Abschnitten beschrieben sind, und zum anderen darin, eventuelle Design-Mängel durch schlichte Visualisierung zu offenbaren (vgl. Kapitel 4.6.4). 49

60 KAPITEL 4: IMPLEMENTATION DES TOOLS Depth of Inheritance Tree Zur Berechnung der Tiefe des Ableitungsbaums stellt ClassNode die entsprechende Methode getdepthofinhtree(). Gemäß der Lazy Evaluation wird dieser Wert bei erstmaligem Aufruf in der privaten Methode calcdepthofinhtree() ermittelt und anschließend als Member übernommen. Für flache Hierarchien, also kinderlose Klassen, wird der Mindestwert DIT=0 als Minimum verwendet. Entsprechend der Definition von [Chidamber94] (vgl. Kapitel 2.4.2) wird für diamantförmige Vererbungsgraphen [Meyers98] der längste Pfad als Wert für die DIT- Metrik gewählt. Konkret durchläuft calcdepthofinhtree() dabei die Liste mit RelationInfo-Objekten und ruft für jedes Kind 23 wiederum rekursiv calcdepthofinhtree() mit DIT+1 auf, um abschließend den größten Wert als Resultat zu übernehmen. Im Beispiel rechtsstehender Abbildung wäre die Tiefe des Ableitungsbaums also DIT=3. Base Derived1 Derived2 Derived3 MultiplyDerived Abbildung 4.5.4: Ableitungsbaum mit Mehrfachvererbung Number of Children Ebenso wie die Tiefe des Ableitungsbaums wird die Anzahl an Kindsknoten bei erstmaligem Aufruf verzögert ermittelt. Dabei werden in der aktuellen Implementierung nur direkte Ableitungen für jeden separaten ClassNode gezählt. Dies könnte in einer sich rekursiv aufrufenden Hilfsmethode analog zu calcdepthofinhtree() zur Ermittlung sämtlicher Nachfahren für jede Basisklasse erweitert werden. 23 Dabei wird zunächst RelationInfo::relation_type_ geprüft, ob es sich um eine Ableitung handelt. 50

61 KAPITEL 4: IMPLEMENTATION DES TOOLS 4.6. Benutzeroberfläche Während der Entwicklungsphase des Tools wurde zunächst der die Analyse durchführende Kern implementiert und anschließend mit einer Benutzeroberfläche versehen. Ziel war die bequeme Verwendbarkeit des Tools, bei gleichzeitiger strikter Trennung des analysierenden Kerns und der graphischen Benutzeroberfläche Vorgehensweise Da zu Beginn der Entwicklung des Tools die Art der Oberfläche noch unklar war und man sich eine spätere eventuelle Einbettung in Entwicklungsumgebungen wie Eclipse offenhalten wollte, wurde zunächst nur der in den letzten Kapiteln beschriebene analysierende Kern in Standard C++ implementiert. Anschließend wurde die Oberfläche dazu unter Verwendung des.net-frameworks hinzugefügt. Um die Analyse weitgehend unabhängig zu halten, wurde zunächst die Verwendung des Model-View-Controller-Musters [Gamma95] erwogen. Da der Analyzer allerdings nur einmalig auf dem ihm übergebenen Code ausgeführt werden muß und anschließend sein Ergebnis in Form einer Nachrichtenliste und einem textuellen Relationsgraphen zurückliefert, genügte dessen Aggregation. Die Anwendung des Tools basiert auf folgendem Ablauf: Auswahl der zu analysierenden Dateien Ausführung der Analyse: Verbinden der Quelldateien (beschrieben im nächsten Abschnitt) Erzeugung eines Analyzer-Objekts mit Übergabe eines std::stringstreams für die Ausgabe des textuellen Relationsgraphen (Kapitel 4.6.4) Darstellung von Nachrichten und Relationsgraph Überprüfung der Nachrichten mittels des Code-Browsers (Kapitel 4.6.3) 51

62 KAPITEL 4: IMPLEMENTATION DES TOOLS Verbinden der Quellcode-Dateien Synopsis arbeitet auf Dateiebene, das heißt Dateien werden einzeln dem Parser übergeben. Da aber z.b. die Abbildung der Klassenhierarchie im Relationsgraphen alle Klassendefinitionen benötigt, müßten anschließend mehrere vorliegende Parse-Bäume miteinander verknüpft werden. Zur Vereinfachung dieses Umstands, entschloß man sich, die Dateien aneinanderzufügen und als Ganzes zu parsen. Dabei gehen Informationen über lokale Gültigkeitsbereiche in den einzelnen Übersetzungseinheiten verloren, was bei der Initialisierung von statischen Variablen zu Problemen führen kann. Synopsis kann diesen dann keinen Sichtbarkeitsbereich zuweisen und bricht mit einer Exception den Parsevorgang ab. Module, die Initialisierungen statischer Variablen enthalten, müssen demnach einzeln dem Tool übergeben werden. Diese Einschränkung mindert die Anwenderfreundlichkeit des Tools, und deshalb sollte die Verbindung der Quellcode-Dateien in einer zukünftigen Erweiterung dem komplizierteren aber korrekten separaten Parsen und anschließendem Verbinden der Parse- Bäume weichen. FileInfo +startpos_: int +name_: String* Abbildung 4.6.1: FileInfo und Verbunddatei Wie im nachfolgenden Kapitel geschildert wird, werden auf der Oberfläche Nachrichten dargestellt, die während der Analyse generiert wurden (Kapitel und ). Die dabei ermittelte Zeilennummer bezieht sich allerdings auf die Verbunddatei, da der Parser nur diese übergeben bekommt. Um nun die tatsächliche Datei und die lokale Zeilennummer zu erhalten, müssen Namen der tatsächlichen Dateien sowie deren Position im Verbund erhalten bleiben. Dazu wird ein Array von FileInfo-Objekten geführt, welche diese Informationen enthalten, was die Ermittlung der lokalen Zeilennummer trivialisiert (vgl. Abbildung 4.6.1) file1.cpp file2.cpp file3.cpp 52

63 KAPITEL 4: IMPLEMENTATION DES TOOLS Nachrichten und Code-Browser Abbildung 4.6.2: Oberfläche des Tools mit Code-Browser Während der Analyse werden beim Erkennen von Funktions- und Klassendefinitionen Nachrichten generiert. Das Tool soll mit der Erwähnung gefundener Zustandsvariablen oder Mehrfachableitungen eventuelle Problemstellen angeben und ermöglicht, im Code-Browser direkt zur entsprechenden Definition zu springen (siehe Abbildung 4.6.2). Vom Analyzer wird nur der rechte Teil einer solchen Nachricht erzeugt, Dateiname und lokale Zeilennummer fügt die Oberfläche hinzu wie im vorherigen Abschnitt beschrieben. Abbildung 4.6.3: Nachrichtenliste 53

64 KAPITEL 4: IMPLEMENTATION DES TOOLS Darstellung des Relationsgraphen Die Methode RelationGraph::print() schreibt den Relationsgraph als Text (siehe Abbildung 4.6.4) in einen übergebenen std::ostream. Die Ausgabe gruppiert die Hierarchie nach Basisklassen, für die jeweils die Tiefe des Subgraphen (DIT) gemäß Kapitel angegeben wird. Unter der Basisklasse befinden sich abgeleitete und aggregierte Klassen, wobei für erstere die Anzahl Kindsknoten (NOC) angegeben wird und letztere mit einem vorangestellten AGG gekennzeichnet wurden. Die Einrückung repräsentiert die Klassenhierarchie. Alle Klassennamen sind qualifizierte Bezeichner und, sollte eine Beziehung nicht eindeutig aufgelöst worden sein (siehe dazu Kapitel ), gegebenenfalls mit einem uncertain versehen. Zur Veranschaulichung wurde das obige Beispiel in Abbildung noch einmal graphisch dargestellt (Abbildung 4.6.5). Ursprünglich war eine graphische Darstellung des Graphen vorgesehen, was auch eventuelle Designmängel besser veranschaulichen würde. Diese Idee mußte leider aus Zeitgründen der schneller zu implementierenden textuellen Realisierung weichen. Abbildung 4.6.4: textueller Relationsgraph im Tool A ns1::b ns1::b2 ns1::ns2::c ns1::ns2::b ns1::ns2::c2 ns3::e ns3::d uncertain uncertain ns3::x Abbildung 4.6.5: graphischer Relationsgraph 54

Verwendung von OO-Metriken zur Vorhersage

Verwendung von OO-Metriken zur Vorhersage Verwendung von OO-Metriken zur Vorhersage Tobias Angermayr Übersicht 1. Definitionen 2. Gründe, Anforderungen, Ziele 3. Die CK-Metriken 4. Beobachtungen 5. Studie 6. Zusammenfassung Folie 2 Definitionen

Mehr

Software-Metriken. Wolfgang Globke. Seminar Moderne Softwareentwicklung SS 2005. Software-Metriken. Wolfgang Globke. Metriken und Qualitätsmodelle

Software-Metriken. Wolfgang Globke. Seminar Moderne Softwareentwicklung SS 2005. Software-Metriken. Wolfgang Globke. Metriken und Qualitätsmodelle Software- und smodelle Software- Klassische Objektorientierte Seminar Moderne Softwareentwicklung SS 2005 Gliederung Software- und smodelle 1 und smodelle Klassische Objektorientierte 2 Klassische Objektorientierte

Mehr

Klassenentwurf. Wie schreiben wir Klassen, die leicht zu verstehen, wartbar und wiederverwendbar sind? Objektorientierte Programmierung mit Java

Klassenentwurf. Wie schreiben wir Klassen, die leicht zu verstehen, wartbar und wiederverwendbar sind? Objektorientierte Programmierung mit Java Objektorientierte Programmierung mit Java Eine praxisnahe Einführung mit BlueJ Klassenentwurf Wie schreiben wir Klassen, die leicht zu verstehen, wartbar und wiederverwendbar sind? 1.0 Zentrale Konzepte

Mehr

Software-Metriken. Dipl.-Ing.(BA) Henning Sievert <email@henningsievert.de> Seminar Software-Entwurf WS 2004/05

Software-Metriken. Dipl.-Ing.(BA) Henning Sievert <email@henningsievert.de> Seminar Software-Entwurf WS 2004/05 Software-Metriken Dipl.-Ing.(BA) Henning Sievert Seminar Software-Entwurf WS 2004/05 Gliederung Einordnung in den Seminar-Kontext Grundlegende Definitionen Klassifikation von

Mehr

Objektorientierte Programmierung

Objektorientierte Programmierung Objektorientierte Programmierung 1 Geschichte Dahl, Nygaard: Simula 67 (Algol 60 + Objektorientierung) Kay et al.: Smalltalk (erste rein-objektorientierte Sprache) Object Pascal, Objective C, C++ (wiederum

Mehr

geben. Die Wahrscheinlichkeit von 100% ist hier demnach nur der Gehen wir einmal davon aus, dass die von uns angenommenen

geben. Die Wahrscheinlichkeit von 100% ist hier demnach nur der Gehen wir einmal davon aus, dass die von uns angenommenen geben. Die Wahrscheinlichkeit von 100% ist hier demnach nur der Vollständigkeit halber aufgeführt. Gehen wir einmal davon aus, dass die von uns angenommenen 70% im Beispiel exakt berechnet sind. Was würde

Mehr

Zeichen bei Zahlen entschlüsseln

Zeichen bei Zahlen entschlüsseln Zeichen bei Zahlen entschlüsseln In diesem Kapitel... Verwendung des Zahlenstrahls Absolut richtige Bestimmung von absoluten Werten Operationen bei Zahlen mit Vorzeichen: Addieren, Subtrahieren, Multiplizieren

Mehr

Programmieren in Java

Programmieren in Java Programmieren in Java objektorientierte Programmierung 2 2 Zusammenhang Klasse-Datei In jeder *.java Datei kann es genau eine public-klasse geben wobei Klassen- und Dateiname übereinstimmen. Es können

Mehr

4. Jeder Knoten hat höchstens zwei Kinder, ein linkes und ein rechtes.

4. Jeder Knoten hat höchstens zwei Kinder, ein linkes und ein rechtes. Binäre Bäume Definition: Ein binärer Baum T besteht aus einer Menge von Knoten, die durch eine Vater-Kind-Beziehung wie folgt strukturiert ist: 1. Es gibt genau einen hervorgehobenen Knoten r T, die Wurzel

Mehr

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen Binäre Bäume 1. Allgemeines Binäre Bäume werden grundsätzlich verwendet, um Zahlen der Größe nach, oder Wörter dem Alphabet nach zu sortieren. Dem einfacheren Verständnis zu Liebe werde ich mich hier besonders

Mehr

Professionelle Seminare im Bereich MS-Office

Professionelle Seminare im Bereich MS-Office Der Name BEREICH.VERSCHIEBEN() ist etwas unglücklich gewählt. Man kann mit der Funktion Bereiche zwar verschieben, man kann Bereiche aber auch verkleinern oder vergrößern. Besser wäre es, die Funktion

Mehr

Klausurteilnehmer. Wichtige Hinweise. Note: Klausur Informatik Programmierung, 17.09.2012 Seite 1 von 8 HS OWL, FB 7, Malte Wattenberg.

Klausurteilnehmer. Wichtige Hinweise. Note: Klausur Informatik Programmierung, 17.09.2012 Seite 1 von 8 HS OWL, FB 7, Malte Wattenberg. Klausur Informatik Programmierung, 17.09.2012 Seite 1 von 8 Klausurteilnehmer Name: Matrikelnummer: Wichtige Hinweise Es sind keinerlei Hilfsmittel zugelassen auch keine Taschenrechner! Die Klausur dauert

Mehr

.NET Code schützen. Projekt.NET. Version 1.0

.NET Code schützen. Projekt.NET. Version 1.0 .NET Code schützen Projekt.NET Informationsmaterial zum Schützen des.net Codes Version 1.0 Autor: Status: Ablage: Empfänger: Seiten: D. Hoyer 1 / 6 Verteiler : Dokument1 Seite 1 von 1 Änderungsprotokoll

Mehr

Verhindert, dass eine Methode überschrieben wird. public final int holekontostand() {...} public final class Girokonto extends Konto {...

Verhindert, dass eine Methode überschrieben wird. public final int holekontostand() {...} public final class Girokonto extends Konto {... PIWIN I Kap. 8 Objektorientierte Programmierung - Vererbung 31 Schlüsselwort: final Verhindert, dass eine Methode überschrieben wird public final int holekontostand() {... Erben von einer Klasse verbieten:

Mehr

Das große ElterngeldPlus 1x1. Alles über das ElterngeldPlus. Wer kann ElterngeldPlus beantragen? ElterngeldPlus verstehen ein paar einleitende Fakten

Das große ElterngeldPlus 1x1. Alles über das ElterngeldPlus. Wer kann ElterngeldPlus beantragen? ElterngeldPlus verstehen ein paar einleitende Fakten Das große x -4 Alles über das Wer kann beantragen? Generell kann jeder beantragen! Eltern (Mütter UND Väter), die schon während ihrer Elternzeit wieder in Teilzeit arbeiten möchten. Eltern, die während

Mehr

Erweiterung der Aufgabe. Die Notenberechnung soll nicht nur für einen Schüler, sondern für bis zu 35 Schüler gehen:

Erweiterung der Aufgabe. Die Notenberechnung soll nicht nur für einen Schüler, sondern für bis zu 35 Schüler gehen: VBA Programmierung mit Excel Schleifen 1/6 Erweiterung der Aufgabe Die Notenberechnung soll nicht nur für einen Schüler, sondern für bis zu 35 Schüler gehen: Es müssen also 11 (B L) x 35 = 385 Zellen berücksichtigt

Mehr

Wintersemester Maschinenbau und Kunststofftechnik. Informatik. Tobias Wolf http://informatik.swoke.de. Seite 1 von 22

Wintersemester Maschinenbau und Kunststofftechnik. Informatik. Tobias Wolf http://informatik.swoke.de. Seite 1 von 22 Kapitel 19 Vererbung, UML Seite 1 von 22 Vererbung - Neben der Datenabstraktion und der Datenkapselung ist die Vererbung ein weiteres Merkmal der OOP. - Durch Vererbung werden die Methoden und die Eigenschaften

Mehr

Fachdidaktik der Informatik 18.12.08 Jörg Depner, Kathrin Gaißer

Fachdidaktik der Informatik 18.12.08 Jörg Depner, Kathrin Gaißer Fachdidaktik der Informatik 18.12.08 Jörg Depner, Kathrin Gaißer Klassendiagramme Ein Klassendiagramm dient in der objektorientierten Softwareentwicklung zur Darstellung von Klassen und den Beziehungen,

Mehr

Prozessbewertung und -verbesserung nach ITIL im Kontext des betrieblichen Informationsmanagements. von Stephanie Wilke am 14.08.08

Prozessbewertung und -verbesserung nach ITIL im Kontext des betrieblichen Informationsmanagements. von Stephanie Wilke am 14.08.08 Prozessbewertung und -verbesserung nach ITIL im Kontext des betrieblichen Informationsmanagements von Stephanie Wilke am 14.08.08 Überblick Einleitung Was ist ITIL? Gegenüberstellung der Prozesse Neuer

Mehr

Es sollte die MS-DOS Eingabeaufforderung starten. Geben Sie nun den Befehl javac ein.

Es sollte die MS-DOS Eingabeaufforderung starten. Geben Sie nun den Befehl javac ein. Schritt 1: Installation des Javacompilers JDK. Der erste Start mit Eclipse Bevor Sie den Java-Compiler installieren sollten Sie sich vergewissern, ob er eventuell schon installiert ist. Gehen sie wie folgt

Mehr

Datensicherung. Beschreibung der Datensicherung

Datensicherung. Beschreibung der Datensicherung Datensicherung Mit dem Datensicherungsprogramm können Sie Ihre persönlichen Daten problemlos Sichern. Es ist möglich eine komplette Datensicherung durchzuführen, aber auch nur die neuen und geänderten

Mehr

Vgl. Kapitel 4 aus Systematisches Requirements Engineering, Christoph Ebert https://www.sws.bfh.ch/studium/cas/swe-fs13/protected/re/re_buch.

Vgl. Kapitel 4 aus Systematisches Requirements Engineering, Christoph Ebert https://www.sws.bfh.ch/studium/cas/swe-fs13/protected/re/re_buch. Vgl. Kapitel 4 aus Systematisches Requirements Engineering, Christoph Ebert https://www.sws.bfh.ch/studium/cas/swe-fs13/protected/re/re_buch.pdf Nachdem die Projekt-Vision und die Stakeholder bekannt sind,

Mehr

Folge 18 - Vererbung

Folge 18 - Vererbung Workshop Folge 18 - Vererbung 18.1 Ein einfacher Fall der Vererbung Schritt 1 - Vorbereitungen Besorgen Sie sich - vielleicht aus einer der Übungen der Folge 17 - ein fertiges und lauffähiges Listenprojekt,

Mehr

Lineargleichungssysteme: Additions-/ Subtraktionsverfahren

Lineargleichungssysteme: Additions-/ Subtraktionsverfahren Lineargleichungssysteme: Additions-/ Subtraktionsverfahren W. Kippels 22. Februar 2014 Inhaltsverzeichnis 1 Einleitung 2 2 Lineargleichungssysteme zweiten Grades 2 3 Lineargleichungssysteme höheren als

Mehr

1 Mathematische Grundlagen

1 Mathematische Grundlagen Mathematische Grundlagen - 1-1 Mathematische Grundlagen Der Begriff der Menge ist einer der grundlegenden Begriffe in der Mathematik. Mengen dienen dazu, Dinge oder Objekte zu einer Einheit zusammenzufassen.

Mehr

Fachbericht zum Thema: Anforderungen an ein Datenbanksystem

Fachbericht zum Thema: Anforderungen an ein Datenbanksystem Fachbericht zum Thema: Anforderungen an ein Datenbanksystem von André Franken 1 Inhaltsverzeichnis 1 Inhaltsverzeichnis 1 2 Einführung 2 2.1 Gründe für den Einsatz von DB-Systemen 2 2.2 Definition: Datenbank

Mehr

Der Aufruf von DM_in_Euro 1.40 sollte die Ausgabe 1.40 DM = 0.51129 Euro ergeben.

Der Aufruf von DM_in_Euro 1.40 sollte die Ausgabe 1.40 DM = 0.51129 Euro ergeben. Aufgabe 1.30 : Schreibe ein Programm DM_in_Euro.java zur Umrechnung eines DM-Betrags in Euro unter Verwendung einer Konstanten für den Umrechnungsfaktor. Das Programm soll den DM-Betrag als Parameter verarbeiten.

Mehr

Software Engineering in der Praxis

Software Engineering in der Praxis Software Engineering in der Praxis Praktische Übungen Adersberger, Spisländer FAU Erlangen-Nürnberg Software-Metriken 1 / 26 Software-Metriken Josef Adersberger Marc Spisländer Lehrstuhl für Software Engineering

Mehr

Berechnung der Erhöhung der Durchschnittsprämien

Berechnung der Erhöhung der Durchschnittsprämien Wolfram Fischer Berechnung der Erhöhung der Durchschnittsprämien Oktober 2004 1 Zusammenfassung Zur Berechnung der Durchschnittsprämien wird das gesamte gemeldete Prämienvolumen Zusammenfassung durch die

Mehr

Projektmanagement in der Spieleentwicklung

Projektmanagement in der Spieleentwicklung Projektmanagement in der Spieleentwicklung Inhalt 1. Warum brauche ich ein Projekt-Management? 2. Die Charaktere des Projektmanagement - Mastermind - Producer - Projektleiter 3. Schnittstellen definieren

Mehr

Ist Excel das richtige Tool für FMEA? Steve Murphy, Marc Schaeffers

Ist Excel das richtige Tool für FMEA? Steve Murphy, Marc Schaeffers Ist Excel das richtige Tool für FMEA? Steve Murphy, Marc Schaeffers Ist Excel das richtige Tool für FMEA? Einleitung Wenn in einem Unternehmen FMEA eingeführt wird, fangen die meisten sofort damit an,

Mehr

ERGÄNZUNGEN ZUR ANALYSIS II MITTELWERTSATZ UND ANWENDUNGEN

ERGÄNZUNGEN ZUR ANALYSIS II MITTELWERTSATZ UND ANWENDUNGEN ERGÄNZUNGEN ZUR ANALYSIS II MITTELWERTSATZ UND ANWENDUNGEN CHRISTIAN HARTFELDT. Zweiter Mittelwertsatz Der Mittelwertsatz Satz VI.3.4) lässt sich verallgemeinern zu Satz.. Seien f, g : [a, b] R auf [a,

Mehr

Einführung in die Programmierung

Einführung in die Programmierung Technische Universität München WS 2003/2004 Institut für Informatik Prof. Dr. Christoph Zenger Testklausur Einführung in die Programmierung Probeklausur Java (Lösungsvorschlag) 1 Die Klasse ArrayList In

Mehr

Diplomarbeit. Konzeption und Implementierung einer automatisierten Testumgebung. Thomas Wehrspann. 10. Dezember 2008

Diplomarbeit. Konzeption und Implementierung einer automatisierten Testumgebung. Thomas Wehrspann. 10. Dezember 2008 Konzeption und Implementierung einer automatisierten Testumgebung, 10. Dezember 2008 1 Gliederung Einleitung Softwaretests Beispiel Konzeption Zusammenfassung 2 Einleitung Komplexität von Softwaresystemen

Mehr

Technische Dokumentation: wenn Englisch zur Herausforderung wird

Technische Dokumentation: wenn Englisch zur Herausforderung wird Praxis Technische Dokumentation: wenn Englisch zur Herausforderung wird Anforderungsspezifikation, Requirements-Engineering, Requirements-Management, Terminologieverwaltung www.sophist.de Über Englischkenntnisse

Mehr

Übungen zur Softwaretechnik

Übungen zur Softwaretechnik Technische Universität München Fakultät für Informatik Lehrstuhl IV: Software & Systems Engineering Markus Pister, Dr. Bernhard Rumpe WS 2002/2003 Lösungsblatt 9 17. Dezember 2002 www4.in.tum.de/~rumpe/se

Mehr

Stellen Sie bitte den Cursor in die Spalte B2 und rufen die Funktion Sverweis auf. Es öffnet sich folgendes Dialogfenster

Stellen Sie bitte den Cursor in die Spalte B2 und rufen die Funktion Sverweis auf. Es öffnet sich folgendes Dialogfenster Es gibt in Excel unter anderem die so genannten Suchfunktionen / Matrixfunktionen Damit können Sie Werte innerhalb eines bestimmten Bereichs suchen. Als Beispiel möchte ich die Funktion Sverweis zeigen.

Mehr

Softwaretests in Visual Studio 2010 Ultimate Vergleich mit Java-Testwerkzeugen. Alexander Schunk Marcel Teuber Henry Trobisch

Softwaretests in Visual Studio 2010 Ultimate Vergleich mit Java-Testwerkzeugen. Alexander Schunk Marcel Teuber Henry Trobisch Softwaretests in Visual Studio 2010 Ultimate Vergleich mit Java-Testwerkzeugen Alexander Schunk Henry Trobisch Inhalt 1. Vergleich der Unit-Tests... 2 2. Vergleich der Codeabdeckungs-Tests... 2 3. Vergleich

Mehr

Daniel Warneke warneke@upb.de 08.05.2006. Ein Vortrag im Rahmen des Proseminars Software Pioneers

Daniel Warneke warneke@upb.de 08.05.2006. Ein Vortrag im Rahmen des Proseminars Software Pioneers Design Patterns Daniel Warneke warneke@upb.de 08.05.2006 Ein Vortrag im Rahmen des Proseminars Software Pioneers Design Patterns 1/23 Übersicht Einleitung / Motivation Design Patterns Beispiele Rolle des

Mehr

Austausch- bzw. Übergangsprozesse und Gleichgewichtsverteilungen

Austausch- bzw. Übergangsprozesse und Gleichgewichtsverteilungen Austausch- bzw. Übergangsrozesse und Gleichgewichtsverteilungen Wir betrachten ein System mit verschiedenen Zuständen, zwischen denen ein Austausch stattfinden kann. Etwa soziale Schichten in einer Gesellschaft:

Mehr

Bauteilattribute als Sachdaten anzeigen

Bauteilattribute als Sachdaten anzeigen Mit den speedikon Attributfiltern können Sie die speedikon Attribute eines Bauteils als MicroStation Sachdaten an die Elemente anhängen Inhalte Was ist ein speedikon Attribut?... 3 Eigene Attribute vergeben...

Mehr

Folge 19 - Bäume. 19.1 Binärbäume - Allgemeines. Grundlagen: Ulrich Helmich: Informatik 2 mit BlueJ - Ein Kurs für die Stufe 12

Folge 19 - Bäume. 19.1 Binärbäume - Allgemeines. Grundlagen: Ulrich Helmich: Informatik 2 mit BlueJ - Ein Kurs für die Stufe 12 Grundlagen: Folge 19 - Bäume 19.1 Binärbäume - Allgemeines Unter Bäumen versteht man in der Informatik Datenstrukturen, bei denen jedes Element mindestens zwei Nachfolger hat. Bereits in der Folge 17 haben

Mehr

Software Survivability

Software Survivability Software Survivability Ansatz zur Quantifizierung der Überlebensfähigkeit von Softwaresystem Seminar: Web-Qualitätsmanagement Sommersemester 2004 Gliederung 1. Einleitung 2. Survivability 3. Software Survivability

Mehr

Softwaretechnik (Allgemeine Informatik) Überblick

Softwaretechnik (Allgemeine Informatik) Überblick Softwaretechnik (Allgemeine Informatik) Überblick 1 Einführung und Überblick 2 Abstraktion 3 Objektorientiertes Vorgehensmodell 4 Methoden der Anforderungs- und Problembereichsanalyse 5 UML-Diagramme 6

Mehr

Einführung in die Programmierung

Einführung in die Programmierung : Inhalt Einführung in die Programmierung Wintersemester 2008/09 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund - mit / ohne Parameter - mit / ohne Rückgabewerte

Mehr

Kapitel 3 Frames Seite 1

Kapitel 3 Frames Seite 1 Kapitel 3 Frames Seite 1 3 Frames 3.1 Allgemeines Mit Frames teilt man eine HTML-Seite in mehrere Bereiche ein. Eine Seite, die mit Frames aufgeteilt ist, besteht aus mehreren Einzelseiten, die sich den

Mehr

Robot Karol für Delphi

Robot Karol für Delphi Robot Karol für Delphi Reinhard Nitzsche, OSZ Handel I Version 0.1 vom 24. Januar 2003 Zusammenfassung Nach der Einführung in die (variablenfreie) Programmierung mit Robot Karol von Freiberger und Krško

Mehr

Speicher in der Cloud

Speicher in der Cloud Speicher in der Cloud Kostenbremse, Sicherheitsrisiko oder Basis für die unternehmensweite Kollaboration? von Cornelius Höchel-Winter 2013 ComConsult Research GmbH, Aachen 3 SYNCHRONISATION TEUFELSZEUG

Mehr

OECD Programme for International Student Assessment PISA 2000. Lösungen der Beispielaufgaben aus dem Mathematiktest. Deutschland

OECD Programme for International Student Assessment PISA 2000. Lösungen der Beispielaufgaben aus dem Mathematiktest. Deutschland OECD Programme for International Student Assessment Deutschland PISA 2000 Lösungen der Beispielaufgaben aus dem Mathematiktest Beispielaufgaben PISA-Hauptstudie 2000 Seite 3 UNIT ÄPFEL Beispielaufgaben

Mehr

Fassade. Objektbasiertes Strukturmuster. C. Restorff & M. Rohlfing

Fassade. Objektbasiertes Strukturmuster. C. Restorff & M. Rohlfing Fassade Objektbasiertes Strukturmuster C. Restorff & M. Rohlfing Übersicht Motivation Anwendbarkeit Struktur Teilnehmer Interaktion Konsequenz Implementierung Beispiel Bekannte Verwendung Verwandte Muster

Mehr

Use Cases. Use Cases

Use Cases. Use Cases Use Cases Eigenschaften: Ein Use Case beschreibt einen Teil des Verhaltens eines Systems aus externer Sicht (Formuliert in der der Fachsprache der Anwendung) Dies geschieht, indem ein Systemdialog beschrieben

Mehr

Dokumentation von Ük Modul 302

Dokumentation von Ük Modul 302 Dokumentation von Ük Modul 302 Von Nicolas Kull Seite 1/ Inhaltsverzeichnis Dokumentation von Ük Modul 302... 1 Inhaltsverzeichnis... 2 Abbildungsverzeichnis... 3 Typographie (Layout)... 4 Schrift... 4

Mehr

Protokoll des Versuches 7: Umwandlung von elektrischer Energie in Wärmeenergie

Protokoll des Versuches 7: Umwandlung von elektrischer Energie in Wärmeenergie Name: Matrikelnummer: Bachelor Biowissenschaften E-Mail: Physikalisches Anfängerpraktikum II Dozenten: Assistenten: Protokoll des Versuches 7: Umwandlung von elektrischer Energie in ärmeenergie Verantwortlicher

Mehr

Einführung in die Java- Programmierung

Einführung in die Java- Programmierung Einführung in die Java- Programmierung Dr. Volker Riediger Tassilo Horn riediger horn@uni-koblenz.de WiSe 2012/13 1 Wichtig... Mittags keine Pommes... Praktikum A 230 C 207 (Madeleine + Esma) F 112 F 113

Mehr

Drei-Schichten-Architektur. Informatik B - Objektorientierte Programmierung in Java. Vorlesung 16: 3-Schichten-Architektur 1 Fachkonzept - GUI

Drei-Schichten-Architektur. Informatik B - Objektorientierte Programmierung in Java. Vorlesung 16: 3-Schichten-Architektur 1 Fachkonzept - GUI Universität Osnabrück Drei-Schichten-Architektur 3 - Objektorientierte Programmierung in Java Vorlesung 6: 3-Schichten-Architektur Fachkonzept - GUI SS 2005 Prof. Dr. F.M. Thiesing, FH Dortmund Ein großer

Mehr

Theoretische Grundlagen der Informatik

Theoretische Grundlagen der Informatik Theoretische Grundlagen der Informatik Vorlesung am 12.01.2012 INSTITUT FÜR THEORETISCHE 0 KIT 12.01.2012 Universität des Dorothea Landes Baden-Württemberg Wagner - Theoretische und Grundlagen der Informatik

Mehr

Welche Unterschiede gibt es zwischen einem CAPAund einem Audiometrie- Test?

Welche Unterschiede gibt es zwischen einem CAPAund einem Audiometrie- Test? Welche Unterschiede gibt es zwischen einem CAPAund einem Audiometrie- Test? Auch wenn die Messungsmethoden ähnlich sind, ist das Ziel beider Systeme jedoch ein anderes. Gwenolé NEXER g.nexer@hearin gp

Mehr

Unterrichtsmaterialien in digitaler und in gedruckter Form. Auszug aus: Übungsbuch für den Grundkurs mit Tipps und Lösungen: Analysis

Unterrichtsmaterialien in digitaler und in gedruckter Form. Auszug aus: Übungsbuch für den Grundkurs mit Tipps und Lösungen: Analysis Unterrichtsmaterialien in digitaler und in gedruckter Form Auszug aus: Übungsbuch für den Grundkurs mit Tipps und Lösungen: Analysis Das komplette Material finden Sie hier: Download bei School-Scout.de

Mehr

Vorkurs C++ Programmierung

Vorkurs C++ Programmierung Vorkurs C++ Programmierung Klassen Letzte Stunde Speicherverwaltung automatische Speicherverwaltung auf dem Stack dynamische Speicherverwaltung auf dem Heap new/new[] und delete/delete[] Speicherklassen:

Mehr

Primzahlen und RSA-Verschlüsselung

Primzahlen und RSA-Verschlüsselung Primzahlen und RSA-Verschlüsselung Michael Fütterer und Jonathan Zachhuber 1 Einiges zu Primzahlen Ein paar Definitionen: Wir bezeichnen mit Z die Menge der positiven und negativen ganzen Zahlen, also

Mehr

Ein neues System für die Allokation von Spenderlungen. LAS Information für Patienten in Deutschland

Ein neues System für die Allokation von Spenderlungen. LAS Information für Patienten in Deutschland Ein neues System für die Allokation von Spenderlungen LAS Information für Patienten in Deutschland Ein neues System für die Allokation von Spenderlungen Aufgrund des immensen Mangels an Spenderorganen

Mehr

D i e n s t e D r i t t e r a u f We b s i t e s

D i e n s t e D r i t t e r a u f We b s i t e s M erkblatt D i e n s t e D r i t t e r a u f We b s i t e s 1 Einleitung Öffentliche Organe integrieren oftmals im Internet angebotene Dienste und Anwendungen in ihre eigenen Websites. Beispiele: Eine

Mehr

EasyWk DAS Schwimmwettkampfprogramm

EasyWk DAS Schwimmwettkampfprogramm EasyWk DAS Schwimmwettkampfprogramm Arbeiten mit OMEGA ARES 21 EasyWk - DAS Schwimmwettkampfprogramm 1 Einleitung Diese Präsentation dient zur Darstellung der Zusammenarbeit zwischen EasyWk und der Zeitmessanlage

Mehr

Agile Vorgehensmodelle in der Softwareentwicklung: Scrum

Agile Vorgehensmodelle in der Softwareentwicklung: Scrum C A R L V O N O S S I E T Z K Y Agile Vorgehensmodelle in der Softwareentwicklung: Scrum Johannes Diemke Vortrag im Rahmen der Projektgruppe Oldenburger Robot Soccer Team im Wintersemester 2009/2010 Was

Mehr

Zum Abschluss wird gezeigt, wie aus einem C++ Quell-Programm ein ausführbares Programm erzeugt wird. 1. Installation von NetBeans...

Zum Abschluss wird gezeigt, wie aus einem C++ Quell-Programm ein ausführbares Programm erzeugt wird. 1. Installation von NetBeans... Erste Schritte Dieser Teil der Veranstaltung gibt einen ersten Eindruck der Programmierung mit C++. Es wird ein erstes Gefühl von Programmiersprachen vermittelt, ohne auf die gezeigten Bestandteile genau

Mehr

Abamsoft Finos im Zusammenspiel mit shop to date von DATA BECKER

Abamsoft Finos im Zusammenspiel mit shop to date von DATA BECKER Abamsoft Finos im Zusammenspiel mit shop to date von DATA BECKER Abamsoft Finos in Verbindung mit der Webshopanbindung wurde speziell auf die Shop-Software shop to date von DATA BECKER abgestimmt. Mit

Mehr

Objektorientierte Programmierung für Anfänger am Beispiel PHP

Objektorientierte Programmierung für Anfänger am Beispiel PHP Objektorientierte Programmierung für Anfänger am Beispiel PHP Johannes Mittendorfer http://jmittendorfer.hostingsociety.com 19. August 2012 Abstract Dieses Dokument soll die Vorteile der objektorientierten

Mehr

Grundlagen von Python

Grundlagen von Python Einführung in Python Grundlagen von Python Felix Döring, Felix Wittwer November 17, 2015 Scriptcharakter Programmierparadigmen Imperatives Programmieren Das Scoping Problem Objektorientiertes Programmieren

Mehr

Qualitätsmanagement im Projekt

Qualitätsmanagement im Projekt Software-Engineering Qualitätsmanagement im Projekt Vorlesung im Wintersemester 2008/2009 Fakultät Wirtschaftsinformatik Klaus Mairon, M.Sc. Inhalte Messen und Bewerten: Metriken in der Qualitätssicherung

Mehr

Zwischenablage (Bilder, Texte,...)

Zwischenablage (Bilder, Texte,...) Zwischenablage was ist das? Informationen über. die Bedeutung der Windows-Zwischenablage Kopieren und Einfügen mit der Zwischenablage Vermeiden von Fehlern beim Arbeiten mit der Zwischenablage Bei diesen

Mehr

Systemen im Wandel. Autor: Dr. Gerd Frenzen Coromell GmbH Seite 1 von 5

Systemen im Wandel. Autor: Dr. Gerd Frenzen Coromell GmbH Seite 1 von 5 Das Management von Informations- Systemen im Wandel Die Informations-Technologie (IT) war lange Zeit ausschließlich ein Hilfsmittel, um Arbeitsabläufe zu vereinfachen und Personal einzusparen. Sie hat

Mehr

Formale Sprachen und Grammatiken

Formale Sprachen und Grammatiken Formale Sprachen und Grammatiken Jede Sprache besitzt die Aspekte Semantik (Bedeutung) und Syntax (formaler Aufbau). Die zulässige und korrekte Form der Wörter und Sätze einer Sprache wird durch die Syntax

Mehr

Physik & Musik. Stimmgabeln. 1 Auftrag

Physik & Musik. Stimmgabeln. 1 Auftrag Physik & Musik 5 Stimmgabeln 1 Auftrag Physik & Musik Stimmgabeln Seite 1 Stimmgabeln Bearbeitungszeit: 30 Minuten Sozialform: Einzel- oder Partnerarbeit Voraussetzung: Posten 1: "Wie funktioniert ein

Mehr

LEITFADEN ZUR SCHÄTZUNG DER BEITRAGSNACHWEISE

LEITFADEN ZUR SCHÄTZUNG DER BEITRAGSNACHWEISE STOTAX GEHALT UND LOHN Stollfuß Medien LEITFADEN ZUR SCHÄTZUNG DER BEITRAGSNACHWEISE Stand 09.12.2009 Seit dem Januar 2006 hat der Gesetzgeber die Fälligkeit der SV-Beiträge vorgezogen. So kann es vorkommen,

Mehr

Fragebogen ISONORM 9241/110-S

Fragebogen ISONORM 9241/110-S Fragebogen ISONORM 9241/110-S Beurteilung von Software auf Grundlage der Internationalen Ergonomie-Norm DIN EN ISO 9241-110 von Prof. Dr. Jochen Prümper www.seikumu.de Fragebogen ISONORM 9241/110-S Seite

Mehr

Übung 9 - Lösungsvorschlag

Übung 9 - Lösungsvorschlag Universität Innsbruck - Institut für Informatik Datenbanken und Informationssysteme Prof. Günther Specht, Eva Zangerle Besprechung: 15.12.2008 Einführung in die Informatik Übung 9 - Lösungsvorschlag Aufgabe

Mehr

Programmiersprachen und Übersetzer

Programmiersprachen und Übersetzer Programmiersprachen und Übersetzer Sommersemester 2010 19. April 2010 Theoretische Grundlagen Problem Wie kann man eine unendliche Menge von (syntaktisch) korrekten Programmen definieren? Lösung Wie auch

Mehr

Anleitung zur Daten zur Datensicherung und Datenrücksicherung. Datensicherung

Anleitung zur Daten zur Datensicherung und Datenrücksicherung. Datensicherung Anleitung zur Daten zur Datensicherung und Datenrücksicherung Datensicherung Es gibt drei Möglichkeiten der Datensicherung. Zwei davon sind in Ges eingebaut, die dritte ist eine manuelle Möglichkeit. In

Mehr

MCRServlet Table of contents

MCRServlet Table of contents Table of contents 1 Das Zusammenspiel der Servlets mit dem MCRServlet... 2 1 Das Zusammenspiel der Servlets mit dem MCRServlet Als übergeordnetes Servlet mit einigen grundlegenden Funktionalitäten dient

Mehr

Um zusammenfassende Berichte zu erstellen, gehen Sie folgendermaßen vor:

Um zusammenfassende Berichte zu erstellen, gehen Sie folgendermaßen vor: Ergebnisreport: mehrere Lehrveranstaltungen zusammenfassen 1 1. Ordner anlegen In der Rolle des Berichterstellers (siehe EvaSys-Editor links oben) können zusammenfassende Ergebnisberichte über mehrere

Mehr

Informationssystemanalyse Problemstellung 2 1. Trotz aller Methoden, Techniken usw. zeigen Untersuchungen sehr negative Ergebnisse:

Informationssystemanalyse Problemstellung 2 1. Trotz aller Methoden, Techniken usw. zeigen Untersuchungen sehr negative Ergebnisse: Informationssystemanalyse Problemstellung 2 1 Problemstellung Trotz aller Methoden, Techniken usw. zeigen Untersuchungen sehr negative Ergebnisse: große Software-Systeme werden im Schnitt ein Jahr zu spät

Mehr

Theoretische Informatik SS 04 Übung 1

Theoretische Informatik SS 04 Übung 1 Theoretische Informatik SS 04 Übung 1 Aufgabe 1 Es gibt verschiedene Möglichkeiten, eine natürliche Zahl n zu codieren. In der unären Codierung hat man nur ein Alphabet mit einem Zeichen - sagen wir die

Mehr

Software Engineering Interaktionsdiagramme

Software Engineering Interaktionsdiagramme Software Engineering Interaktionsdiagramme Prof. Adrian A. Müller, PMP, PSM 1, CSM Fachbereich Informatik und Mikrosystemtechnik 1 Nachrichtenaustausch Welche Nachrichten werden ausgetauscht? (Methodenaufrufe)

Mehr

Ziel- und Qualitätsorientierung. Fortbildung für die Begutachtung in Verbindung mit dem Gesamtplanverfahren nach 58 SGB XII

Ziel- und Qualitätsorientierung. Fortbildung für die Begutachtung in Verbindung mit dem Gesamtplanverfahren nach 58 SGB XII Ziel- und Qualitätsorientierung Fortbildung für die Begutachtung in Verbindung mit dem Gesamtplanverfahren nach 58 SGB XII Qualität? In der Alltagssprache ist Qualität oft ein Ausdruck für die Güte einer

Mehr

Anmerkungen zur Übergangsprüfung

Anmerkungen zur Übergangsprüfung DM11 Slide 1 Anmerkungen zur Übergangsprüfung Aufgabeneingrenzung Aufgaben des folgenden Typs werden wegen ihres Schwierigkeitsgrads oder wegen eines ungeeigneten fachlichen Schwerpunkts in der Übergangsprüfung

Mehr

Die Gleichung A x = a hat für A 0 die eindeutig bestimmte Lösung. Für A=0 und a 0 existiert keine Lösung.

Die Gleichung A x = a hat für A 0 die eindeutig bestimmte Lösung. Für A=0 und a 0 existiert keine Lösung. Lineare Gleichungen mit einer Unbekannten Die Grundform der linearen Gleichung mit einer Unbekannten x lautet A x = a Dabei sind A, a reelle Zahlen. Die Gleichung lösen heißt, alle reellen Zahlen anzugeben,

Mehr

Mean Time Between Failures (MTBF)

Mean Time Between Failures (MTBF) Mean Time Between Failures (MTBF) Hintergrundinformation zur MTBF Was steht hier? Die Mean Time Between Failure (MTBF) ist ein statistischer Mittelwert für den störungsfreien Betrieb eines elektronischen

Mehr

Was sind Jahres- und Zielvereinbarungsgespräche?

Was sind Jahres- und Zielvereinbarungsgespräche? 6 Was sind Jahres- und Zielvereinbarungsgespräche? Mit dem Jahresgespräch und der Zielvereinbarung stehen Ihnen zwei sehr wirkungsvolle Instrumente zur Verfügung, um Ihre Mitarbeiter zu führen und zu motivieren

Mehr

Einführung in die objektorientierte Programmierung mit Java. Klausur am 19. Oktober 2005

Einführung in die objektorientierte Programmierung mit Java. Klausur am 19. Oktober 2005 Einführung in die objektorientierte Programmierung mit Java Klausur am 19. Oktober 2005 Matrikelnummer: Nachname: Vorname: Semesteranzahl: Die Klausur besteht aus drei Frageblöcken zu den Inhalten der

Mehr

Handbuch Fischertechnik-Einzelteiltabelle V3.7.3

Handbuch Fischertechnik-Einzelteiltabelle V3.7.3 Handbuch Fischertechnik-Einzelteiltabelle V3.7.3 von Markus Mack Stand: Samstag, 17. April 2004 Inhaltsverzeichnis 1. Systemvorraussetzungen...3 2. Installation und Start...3 3. Anpassen der Tabelle...3

Mehr

Objektorientierter Software-Entwurf Grundlagen 1 1. Analyse Design Implementierung. Frühe Phasen durch Informationssystemanalyse abgedeckt

Objektorientierter Software-Entwurf Grundlagen 1 1. Analyse Design Implementierung. Frühe Phasen durch Informationssystemanalyse abgedeckt Objektorientierter Software-Entwurf Grundlagen 1 1 Einordnung der Veranstaltung Analyse Design Implementierung Slide 1 Informationssystemanalyse Objektorientierter Software-Entwurf Frühe Phasen durch Informationssystemanalyse

Mehr

5. Abstrakte Klassen. Beispiel (3) Abstrakte Klasse. Beispiel (2) Angenommen, wir wollen die folgende Klassenhierarchie implementieren:

5. Abstrakte Klassen. Beispiel (3) Abstrakte Klasse. Beispiel (2) Angenommen, wir wollen die folgende Klassenhierarchie implementieren: 5. Abstrakte Klassen Beispiel 5. Abstrakte Klassen 5. Abstrakte Klassen Beispiel Beispiel (3) Angenommen, wir wollen die folgende Klassenhierarchie implementieren: Probleme des Implementierungsvorschlags:

Mehr

Objektorientierte Programmierung

Objektorientierte Programmierung Universität der Bundeswehr Fakultät für Informatik Institut 2 Priv.-Doz. Dr. Lothar Schmitz FT 2006 Zusatzaufgaben Lösungsvorschlag Objektorientierte Programmierung Lösung 22 (Java und UML-Klassendiagramm)

Mehr

Erfolg und Vermögensrückgänge angefertigt im Rahmen der Lehrveranstaltung Nachrichtentechnik von: Eric Hansen, eric-hansen@gmx.de am: 07.09.

Erfolg und Vermögensrückgänge angefertigt im Rahmen der Lehrveranstaltung Nachrichtentechnik von: Eric Hansen, eric-hansen@gmx.de am: 07.09. Abstract zum Thema Handelssysteme Erfolg und Vermögensrückgänge angefertigt im Rahmen der Lehrveranstaltung Nachrichtentechnik von: Eric Hansen, eric-hansen@gmx.de am: 07.09.01 Einleitung: Handelssysteme

Mehr

PRÜFUNG FÜR ELEKTROINGENIEURE. Softwaretechnik I. Musterlösung SS 12. - Ohne Gewähr -

PRÜFUNG FÜR ELEKTROINGENIEURE. Softwaretechnik I. Musterlösung SS 12. - Ohne Gewähr - PRÜFUNG FÜR ELEKTROINGENIEURE Softwaretechnik I Musterlösung SS 12 - Ohne Gewähr - LfdNr. Thema Punkte Zeitbedarf in min 1 Analyse und Entwurf 15 30 2 Basistechniken und Test 15 30 3 Projektmanagement

Mehr

Übung: Verwendung von Java-Threads

Übung: Verwendung von Java-Threads Übung: Verwendung von Java-Threads Ziel der Übung: Diese Übung dient dazu, den Umgang mit Threads in der Programmiersprache Java kennenzulernen. Ein einfaches Java-Programm, das Threads nutzt, soll zum

Mehr

Rekursionen. Georg Anegg 25. November 2009. Methoden und Techniken an Beispielen erklärt

Rekursionen. Georg Anegg 25. November 2009. Methoden und Techniken an Beispielen erklärt Methoden und Techniken an Beispielen erklärt Georg Anegg 5. November 009 Beispiel. Die Folge {a n } sei wie folgt definiert (a, d, q R, q ): a 0 a, a n+ a n q + d (n 0) Man bestimme eine explizite Darstellung

Mehr

QM: Prüfen -1- KN16.08.2010

QM: Prüfen -1- KN16.08.2010 QM: Prüfen -1- KN16.08.2010 2.4 Prüfen 2.4.1 Begriffe, Definitionen Ein wesentlicher Bestandteil der Qualitätssicherung ist das Prüfen. Sie wird aber nicht wie früher nach der Fertigung durch einen Prüfer,

Mehr

Druckvorlagen Als Druckvorlagen sind dafür vorhanden:!liste1.ken (Kennzahlen)!Liste2.KEN (Kontennachweis)

Druckvorlagen Als Druckvorlagen sind dafür vorhanden:!liste1.ken (Kennzahlen)!Liste2.KEN (Kontennachweis) Kennzahlen und Kennzeichen Dieses Dokument zeigt Ihnen in wenigen kurzen Schritten die Logik und Vorgehensweise der Definition der Kennzahlen und Kennzeichen und deren Auswertung in eigens dafür vorhandenen

Mehr

Erwin Grüner 09.02.2006

Erwin Grüner 09.02.2006 FB Psychologie Uni Marburg 09.02.2006 Themenübersicht Folgende Befehle stehen in R zur Verfügung: {}: Anweisungsblock if: Bedingte Anweisung switch: Fallunterscheidung repeat-schleife while-schleife for-schleife

Mehr