Sprachübergreifendes Refaktorisieren zwischen Ruby und Java

Werbung
FernUniversität in Hagen
Fakultät für Mathematik und Informatik
Lehrgebiet Programmiersysteme
Prof. Dr. F. Steimann
Sprachübergreifendes Refaktorisieren
zwischen Ruby und Java
Bachelorarbeit
Antje Osten
Matrikelnummer q6632386
2. April 2012
Erklärung
Hiermit erkläre ich, dass ich diese Abschlussarbeit selbstständig verfasst, noch nicht
anderweitig für Prüfungszwecke vorgelegt, keine anderen als die angegebenen Quellen
und Hilfsmittel benutzt sowie wörtliche und sinngemäße Zitate als solche gekennzeichnet
habe.
Berlin, 2. April 2012
Antje Osten
Zusammenfassung
Refactoring (Refaktorisierung) beschreibt eine Strukturänderung von Programm-Quelltexten unter Beibehaltung des beobachtbaren Verhaltens. Refaktorisierungen sind Teil
der Implementierungsphase. Werkzeuge sollen helfen, die Entwickler und Entwicklerinnen in der Komplexität des Refaktorisierungsvorgangs zu unterstützen. Bisher existieren entwicklungsumgebungs- und programmiersprachenabhängige Refaktorisierungswerkzeuge. Auf Basis des Frameworks Refacola wird ein sprachübergreifender Ansatz
diskutiert, in dem nicht nur mehrere Sprachen in einem Projekt erlaubt sind, sondern
darüber hinaus Ideen für eine Refaktorisierung zwischen statisch getypten und dynamischen Programmiersprachen aufgezeigt und programmiertechnisch konkretisiert werden.
Inhaltsverzeichnis
Erklärung
2
Zusammenfassung
3
1 Einleitung
1.1 Refaktorisierung – ein in der Softwareentwicklung integrierter Prozess .
1.2 Werkzeuge für den Refaktorisierungsprozess . . . . . . . . . . . . . . . .
1.2.1 Refaktorisierungswerkzeuge aus historischem Blickwinkel . . . .
1.2.2 Refaktorisierungswerkzeuge als eigenes Fachgebiet . . . . . . . .
1.3 Flexibilität durch eine programmiersprachen-übergreifende Entwicklung
1.3.1 Vorteile der Interaktion mit dynamischen Programmiersprachen .
1.3.2 Java Virtual Machine als Basis mehrerer Programmiersprachen .
1.4 Sprachübergreifende Refaktorisierungswerkzeuge . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
6
7
9
10
12
13
14
15
17
2 Entwicklungsumgebung und mehrsprachige Projekte
19
2.1 Eclipse Dynamic Language Toolkit . . . . . . . . . . . . . . . . . . . . . . 19
2.2 Verbindung eines Java- und Ruby-Projekts in der Eclipse . . . . . . . . . 19
3 Interoperabilität der Programmiersprachen Ruby und Java
3.1 Einordnung der Programmiersprache Ruby . . . . . . . . .
3.1.1 Die Ideale des Ruby-Erfinders . . . . . . . . . . . . .
3.1.2 Merkmale der Programmiersprache Ruby . . . . . .
3.1.3 Dynamische Aspekte der Programmiersprache Ruby
3.2 Methodenaufrufe zwischen Ruby und Java . . . . . . . . . .
3.2.1 Methodenaufruf von Java nach Ruby . . . . . . . . .
3.2.2 Methodenaufruf von Ruby nach Java . . . . . . . . .
3.3 Konvertierungsregeln . . . . . . . . . . . . . . . . . . . . . .
3.3.1 Konvertierungsregeln für Sichtbarkeiten . . . . . . .
3.3.2 Regeln für die Abbildung von Datentypen . . . . . .
3.4 Bedingungen für eine Refaktorisierung . . . . . . . . . . . .
3.4.1 Sichtbarkeiten sind nicht relevant . . . . . . . . . . .
3.4.2 Getter und Setter . . . . . . . . . . . . . . . . . . .
3.4.3 to_s . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
21
21
21
22
25
27
27
28
28
28
29
30
31
31
32
4 Refacola – Constraint-Sprache und Framework
34
4.1 Die Sprache Refacola . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.1.1 Sprachdefinition . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.1.2 Definition der Constraintregeln . . . . . . . . . . . . . . . . . . . . 36
4
4.2
4.1.3 Definition von Refaktorisierungen . . . . . . . . . . . . . . . . . . . 37
Definition eines Verbots für das Refaktorisieren sprachübergreifender Methodenaufrufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
5 Codeanalyse als Voraussetzung für Refaktorisierung
5.1 Statische Codeanalyse . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.1.1 Exkurs – Mehrdeutigkeit in Ruby-Quelltexten . . . . . . . . . . .
5.1.2 Mehrdeutigkeit bei Sprachübergriffen zwischen Ruby und Java .
5.1.3 Endgültiges Versagen der Typanalyse bei dynamischen Klassennamen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.1.4 Vorläufiger Ablauf einer statischen Codeanalyse . . . . . . . . . .
5.2 Test-Suite zum Sammeln von Informationen für eine Refaktorisierung .
5.2.1 Test-Suite mit modifiziertem JRuby-Interpreter . . . . . . . . . .
5.2.2 Test-Suite mit Instrumentierung . . . . . . . . . . . . . . . . . .
5.3 Ausblick auf eine sinnvollen Verbindung der unterschiedlichen Ansätze .
6 Beschreibung der Software
6.1 Das Plugin AST4CongenJRubyView . . .
6.2 XRefact . . . . . . . . . . . . . . . . . . .
6.2.1 Modifizierter JRuby-Interpreter . .
6.2.2 Modifiziertes Refacola-Framework
6.2.3 Instrumentation . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
A Auflistung, Installations- und Bedienungsanleitung der
A.1 Beigelegte Software . . . . . . . . . . . . . . . . . .
A.2 Installation und Bedienung . . . . . . . . . . . . .
A.2.1 XRefact . . . . . . . . . . . . . . . . . . . .
A.2.2 Plugin AST4CongenJRubyView . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
beigelegten
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
40
. 42
. 42
. 45
.
.
.
.
.
.
48
48
49
51
52
52
.
.
.
.
.
54
54
55
57
58
58
Software
. . . . . .
. . . . . .
. . . . . .
. . . . . .
60
60
60
60
61
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
B Abkürzungen
62
C Liste bekannter Refaktorisierungswerkzeuge
63
Abbildungsverzeichnis
64
Listingverzeichnis
65
Literaturverzeichnis
66
5
1 Einleitung
Refactoring (Refaktorisierung) gilt heute als eine grundlegende Disziplin zur Realisierung
eines guten Designs. Der Begriff steht für eine manuell oder automatisiert durchgeführte
Strukturänderung von Programm-Quelltexten unter Beibehaltung des beobachtbaren
Verhaltens der jeweiligen Software (vgl. u. a. [Ste10], [Fow05]). Andere variable Faktoren,
beispielsweise eine veränderte Laufzeit, müssen mit vorher spezifizierten Anforderungen
an die Software abgeglichen werden.
Als Ergebnis des überarbeiteten Designs sollen, neben einer verbesserten Lesbarkeit
und Verständlichkeit der Quelltexte, Module leichter isolierbar und damit besser testbar
werden, wodurch der Aufwand für Fehleranalysen gesenkt wird und Softwaresysteme
leichter wartbar werden. Zusätzlich wird für die Zukunft erhofft, durch Refaktorisierung
existierende Programme problemlos erweitern zu können.
Hinsichtlich der erwarteten Ergebnisse ist eine umfassende Auseinandersetzung mit
dem Begriff Refaktorisierung im Hinblick auf heutige Softwareprojekte sinnvoll. Ziel muss
sein, die Integration eines Refaktorisierungsvorgangs in die konkrete Softwareentwicklung
möglichst einfach zu gestalten. Dafür bietet sich eine Automatisierung in Form von
Refaktorisierungswerkzeugen an.
In der vorliegenden Arbeit soll ausgelotet werden, wie automatisierte Refaktorisierungen für programmiersprachenübergreifende Softwareprojekte erfolgen können. Der Fokus
wird auf solche Stellen gelegt, an denen die unterschiedlichen Programmiersprachen miteinander interagieren.
Jedoch muss deutlich unterschieden werden zwischen der Zusammenarbeit von ausschließlich solchen Programmiersprachen, die ein statisches Typsystem besitzen, und der
Zusammenarbeit von statisch typgeprüften Sprachen mit Programmiersprachen, die hier
als zweckmäßige Einordnung mit dem Begriff „dynamisch“ belegt werden.
Statisch typgeprüfte Programmiersprachen, in denen die Typregeln schon während
des Übersetzungsvorgangs kontrolliert werden, harmonieren besser mit dem Prozess des
Sammelns von notwendigen Informationen für einen Refaktorisierungsvorgang als dynamische Sprachen.
Beim Refaktorisieren werden Informationen genutzt, die in statisch typgeprüften Sprachen nahezu vollständig (mit wenigen Ausnahmen) zur Verfügung stehen. Beispielsweise
kann die Bindung einer Methode an ihre Definition statisch abgeleitet werden.
Dies ist in einer nicht statisch typgeprüften Sprache ungleich schwerer. Hier kann zwar
die Reihenfolge der Anweisungen abgebildet werden, aber spätestens bei Aufruf einer
Methode, die zur Laufzeit an jeweils zwei unterschiedliche Objekte gebunden werden
kann, sind die gefundenen Informationen über das Programm nicht mehr eindeutig.
Damit stehen die notwendigen Informationen für eine automatisierte Refaktorisierung
nicht in der Klarheit zur Verfügung, wie es in statisch typgeprüften Sprachen der Fall
ist.
6
Das Thema dieser Ausarbeitung wird eingegrenzt auf solche sprachübergreifende Projekte, in denen statisch typisierte und dynamische Programmiersprachen in einer Software genutzt werden.
In der Einleitung wird zuerst skizziert, zu welchem Zeitpunkt Refaktorisierungen einsetzen sollen. Hier wird der Refaktorisierungsprozess auf startbare Programme mit beobachtbarem Verhalten eingegrenzt. Weiter werden exemplarisch Refaktorisierungswerkzeuge historisch verfolgt, um Ideen und Ansätze, aber auch existierende Schwierigkeiten
solcher Werkzeuge in die weitere Diskussion mit einbeziehen zu können. Zur Konkretisierung des Themas der vorliegenden Arbeit wird dann eine aktuelle Forschung1 vorgestellt,
in der Refaktorisierungswerkzeuge als eigenes Forschungsgebiet diskutiert werden. Dieser
Ansatz wird als Grundlage der Ausarbeitung und der beigelegten Software genutzt. Als
Motivation für die Notwendigkeit, Refaktorisierungswerkzeuge für sprachübergreifende
Projekte zu entwickeln, werden Vorteile solcher mehrsprachigen Projekte beschrieben.
Am Ende der Einleitung werden die herangezogene Entwicklungsumgebung und die verwendeten Programmiersprachen vorgestellt und der Aufbau der Ausarbeitung skizziert.
1.1 Refaktorisierung – ein in der Softwareentwicklung integrierter Prozess
Bei klassischen Vorgehensmodellen der Softwareentwicklung kann Flexibilität oft schwer
realisiert werden. Es lässt sich kaum verhindern, dass in Projekten mit einem anfänglich ausgefeilten Entwurf eine nachträgliche Änderung der Spezifikation ein notwendiges
Redesign mit sich bringt, das im schlechtesten Fall dazu führt, dass große Teile neu
programmiert werden müssen oder das gesamte Produkt neu entworfen werden muss.
Workarounds, also Programmcodes, durch die ein Projekt nachträglich an neue Ziele
angepasst werden soll, machen den Quellcode oft unübersichtlich und lassen ihn mit der
Zeit strukturlos und unwartbar werden. Martin Fowler, der unterstützt von Kent Beck,
William Opdyke, Don Roberts, John Brant und Erich Gamma im Jahr 1999 einen Satz
von Refaktorisierungsregeln aus dem Erfahrungsfundus von Entwicklern und Entwicklerinnen katalogisiert hat, folgert hieraus: „Ohne Refaktorisieren zerfällt das Design eines
Programms mit der Zeit.“ [Fow05, S. 43]
Eine stufenweise Refaktorisierung im Nachhinein könnte in dieser Phase helfen. Jedoch
existiert in diesem Zusammenhang die Angst vor hohen Kosten und großem Zeitaufwand.
Andere Softwareentwicklungsprozesse, die unter dem Begriff Agile Softwareentwicklung zusammengefasst werden, integrieren von vornherein den Prozess der Refaktorisierung in den Ablauf der Softwareerstellung. Im agilen Ansatz wird es sogar als Ziel formuliert, Software flexibel zu halten (vgl. [Bec00]). In der Phase der Entwicklung sollen
fortwährend neue Ideen für ein Produkt einfließen können. Refaktorisierung des Quellcodes setzt dem agilen Ansatz folgend nicht erst nach Fertigstellen eines Programms
ein. Genauso wenig wird die Strukturänderung von Codes als paralleles Vorhaben neben
(verstanden als abgelöst von) der konkreten Softwareentwicklung betrachtet. Refaktorisierung gilt im agilen Ansatz als ein elementarer Prozess, der (eng verknüpft) mit zur
1
Vgl. http://www.fernuni-hagen.de/ps/prjs
7
Entwicklung gehört. In der Praxis ist der Refaktorisierungsprozess mit der Implementierung kombiniert.
Um den Begriff Refaktorisierung inhaltlich zu konkretisieren, soll noch einmal Fowler herangezogen werden. Er beschreibt Refaktorisierung zum einen mit Fokus auf den
Gebrauch als Substantiv (Refaktorisierung) und zum anderen auf den Gebrauch als
Verb (refaktorisieren): Refaktorisierung in Substantivform wurde zuvor schon aufgegriffen als eine Änderung an der internen Struktur zur besseren Verständlichkeit (vgl.
[Fow05, S. 41]). Erweiternd fügt Fowler dem Begriff eine Aktion hinzu: „Refaktorisieren(Verb): Eine Software umstrukturieren, ohne ihr beobachtbares Verhalten zu ändern,
indem man eine Reihe von Refaktorisierungen anwendet.“ [Fow05, ebd.])
Fowler zeigt damit eine Technik. Die Änderungen sollen in kleinen, abgeschlossenen
Einheiten vorgenommen werden. Zwingend ist, nach jedem einzelnen Schritt zu testen,
ob das Verhalten gleich geblieben ist. Mit dieser Technik weist Fowler den Weg, den Refaktorisierungsprozess manuell – parallel zur Softwareentwicklung – vorzunehmen. Mit
Hilfe eines Katalogs von Regeln kann das Design nun verbessert werden, nachdem größere
Teile der Software bereits implementiert sind. Fowler stellt sich damit dem angesprochenen Problem, dass ein Softwaredesign vorher zwar geplant wird, im Zuge der Implementierung jedoch oft verloren geht. Spätestens dann, wenn sogenannte „Code Smells“
(vgl. [Fow05, S. 67-82] oder als erweiterter Katalog [Mar09, S. 337-372]) in einer Software entdeckt würden, könne durch ein schrittweises Refaktorisieren ermöglicht werden,
die Struktur im Nachhinein zu verbessern, und die Wahrscheinlichkeit gering gehalten
werden, dabei neue Fehler einzuführen. Fowler geht sogar soweit, dass er behauptet, ein
mit einem chaotischen Design begonnenes Projekt könne durch das schrittweise Refaktorisieren radikal verbessert werden, und spitzt diese Aussage noch einmal darauf zu,
dass ein von vornherein vollständig durchdachtes Design gar nicht notwendig sei: „Sie
werden feststellen, dass das Design, anstatt vollständig vorher zu erfolgen, kontinuierlich
während der Entwicklung stattfindet.“ [Fow05, S. xix]
Fowler formuliert dann für den aktiven Refaktorisierungsvorgang (refaktorisieren) die
Aussage: „Refaktorisieren ist ein Prozess, ein Softwaresystem so zu verändern, dass das
externe Verhalten nicht geändert wird, der Code aber eine bessere interne Struktur
erhält.“ [Fow05, S. xviii]
Stefan Roock und Martin Lippert fassen die Refaktorisierung ebenso als kontinuierliches Entwerfen auf und sehen im Jahr 2004 darüber hinaus Refaktorisierung mittlerweile
als festen Bestandteil, vor allem in groß angelegten, agilen Entwicklungsprojekten: „Mit
dem ständigen Refactoring der Software gehen wir in die entgegengesetzte Richtung: Refactoring und Entwerfen werden zum Bestandteil der täglichen Entwicklungsarbeit. Es
wird damit nicht weniger entworfen als in klassischen Projekten. Die Entwurfsaufwände
werden lediglich gleichmäßiger über den Entwicklungsprozess verteilt.“ [RL04, S. 13]
Roock und Lippert möchten ein – eventuell durch Schwachstellen notwendig gewordenes – Redesign des Gesamtsystems verhindern. Sie fordern, dass eine Refaktorisierung
der Software in den Entwicklungsprozess mit eingebaut wird. Bei auftretenden Schwachstellen soll, ebenso wie bei Fowler, in möglichst kleinen Schritten restrukturiert werden
(vgl. [RL04, S. 18-25]).
Als Prämisse der vorliegenden Ausarbeitung soll die Implementierungsphase als zen-
8
trales „Paket“ verstanden werden, in dem das Refaktorisieren, das Programmieren und
darüber hinaus das heute vor allem in agilen Projekten eingesetzte Testen von Modulen2 und Verhalten (Behavior Driven Development3 ) gebündelt wird. Damit entfällt
jegliche Idee einer eigenständigen „Phase der Refaktorisierung“, die zusätzlich Zeit fordern würde. Aus dem agilen Ansatz wird also übernommen, dass das Refaktorisieren
einhergehend mit der Implementierung stattfindet. Aus der Perspektive des oder der
Programmierenden wird der Refaktorisierungsprozess in dieser Phase in einen Handlungsablauf eingefasst und der Vorgang systematisiert.
Abgegrenzt wird der Begriff Refaktorisierung von seiner Verwendung in Phasen der
Software-Erstellung, in denen noch kein sichtbares Verhalten existiert, beispielsweise,
wenn ein Programm noch nicht lauffähig und damit der Programmablauf noch nicht
beobachtbar ist. Damit fallen Designänderungen vor der Implementierung genauso wenig unter den hier verwendeten Begriff von Refaktorisierung wie Architekturänderungen. Grenzfälle, beispielsweise eine Änderung von Quellcode, ohne dass ein Programm
lauffähig ist, werden durch die klare Definition herausgefiltert, dass das Verhalten beobachtbar sein muss. Strukturänderungen am Ende der Implementierung werden durch
die vorherige Aussage eingeschlossen.
Refaktorisierung impliziert nicht zwingend das formulierte Ziel einer Strukturverbesserung. Nur die Programmierenden selbst können entscheiden, welche Umstrukturierungen sinnvoll sind im Hinblick auf die von ihnen erstellte Software. Anhaltspunkte hierfür
können sich aus den festgehaltenen Erfahrungen ergeben, die in den letzten Jahren von
unterschiedlichen Autoren und Autorinnen veröffentlicht wurden. Hier existieren automatisierte Ansätze, beispielsweise Smell Detection (vgl. u. a. [Sli05]), auf die an dieser
Stelle jedoch nicht weiter eingegangen wird.
1.2 Werkzeuge für den Refaktorisierungsprozess
Selbst wenn zu jeder Zeit eine wünschenswerte Strukturänderung von Quellcode erfolgt,
die dann im positiven Fall zur Übersichtlichkeit beiträgt, zeigt sich in einer manuellen
Refaktorisierung das Problem, dass Abhängigkeiten im Code nicht unmittelbar und damit mühelos von den Programmierenden nachverfolgbar sind. Denn den Überblick über
Quellcode zu behalten, der auf mehrere Dateien in unterschiedlichen Ordnern bzw. Paketen verteilt und dazu mit programmiersprachenspezifischen Sichtbarkeitsregeln belegt
wurde, ist spätestens in einem Team von mehreren Entwickelnden schwer zu leisten.
Mit anderen Worten: Manuelles Refaktorisieren ist fehleranfällig. Durch den skizzierten Ablauf, die Struktur in kleinen Schritten zu ändern, und durch die jeweils sofort
notwendigen Tests auf semantische Gleichheit ist eine manuelle Refaktorisierung zudem
sehr zeitaufwändig. Wie schon durch den Begriff „automatisiert“ angedeutet, wurden
und werden daher im Zuge der Diskussion um Refaktorisierung Werkzeuge entwickelt,
die den Prozess der Strukturänderung von Quellcode unterstützen. Programmierenden
werden für Änderungswünsche automatisierte Angebote gemacht oder die gewünschte
2
3
Exemplarisch http://www.junit.org, http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/
Exemplarisch http://cukes.info
9
Änderung vollständig übernommen.
Die im Folgenden exemplarisch herausgegriffenen frühen Werkzeuge für eine Änderung der Struktur eines Quellcodes sollen den Blick auf die Entstehung heutiger und
kommender Automatisierungen von Refaktorisierungsvorgängen lenken, mit dem Ziel,
Refaktorisierungswerkzeuge im Kontext sprachübergreifender Projekte im weiteren Verlauf einordnen und damit Möglichkeiten und Grenzen eines solchen Werkzeugs auch
historisch diskutieren zu können.
Die historische Sicht führt darüber hinaus zu der Frage nach dem Stand der Entwicklung heutiger Werkzeuge. Denn die zuvor skizzierten Probleme der Fehleranfälligkeit
und natürlich auch des immensen Zeitaufwandes durch manuelles Refaktorisieren machen es notwendig, existierende Werkzeuge, wenn möglich, weiter zu entwickeln und zu
verbessern. In einem Folgeabschnitt wird dazu der für diese Ausarbeitung herangezogene
Ansatz vorgestellt, in dem nach einem einheitlichen Standard für Refaktorisierungswerkzeuge gesucht wird.
1.2.1 Refaktorisierungswerkzeuge aus historischem Blickwinkel
Nach einer Abhandlung über Strukturierungswerkzeuge für Softwaresysteme von Thomas Dudziak und Jan Wloka aus dem Jahre 2002 ist der Anfang der Auseinandersetzung
mit dem skizzierten Begriff Refaktorisierung zeitlich zumindest ab Anfang der 70er Jahre
einzuordnen, als die Diskussion über das GOTO-Statement in imperativen Programmen
ihren Höhepunkt hatte, wenn nicht sogar davor: „Refactoring is not new - in fact it has
been done unknowingly at least since the introduction of structured programming, if not
earlier.“ [DW02, S. 10]
Sie begründen diese Aussage damit, dass die Sprunganweisung GOTO ersetzt wurde,
ohne das Verhalten der jeweiligen Software zu verändern.
Diese Regel, Restrukturieren ohne Verhaltensänderung, entspricht zwar dem heute eingegrenzten Begriff der Refaktorisierung. Ob damalige Restrukturierungen im Zuge der
Diskussion um GOTO jedoch schon als ein Anfang von Refaktorisierung verstanden werden kann, ist m. E. zu diskutieren. Denn Dudziak und Wloka ignorieren an dieser Stelle,
dass es damals im Grunde nur um die Beweisführung ging, dass die Sprunganweisung
GOTO durch die Kontrollstruktur Schleife, ggf. durch die Einführung von booleschen
Variablen, ersetzt und dann mit dem endgültig veränderten Code weiter gearbeitet werden kann. Den Quellcode selbst leserlicher zu gestalten, war für diesen Beweis nicht
relevant: „In [2] Guiseppe Jacopini seems to have proved the (logical) superfluousness
of the go to statement. The exercise to translate an arbitrary flow diagram more or less
mechanically into a jump-less one, however, is not to be recommended. Then the resulting flow diagram cannot be expected to be more transparent than the original one.“
[Dij68]
Jedoch soll im Hinblick auf eine automatisierte Refaktorisierung die Aufmerksamkeit darauf gelenkt werden, dass zu jener Zeit im Zuge der Auseinandersetzung mit
Restrukturierungsprozessen erste Werkzeuge entwickelt wurden, die eine automatisierte Strukturänderung boten. Eines der ersten entstandenen Werkzeuge war Mitte der
70er Jahre die „Structuring Engine“ für die Sprache Fortran, die jedoch den Objekt-
10
code und die Laufzeit der Software stark anwachsen ließ. Stefan Eickner und Thomas
Schnieder vermuten dazu in einem Arbeitsbericht aus dem Jahr 1992, dass sich das
Werkzeug deswegen wahrscheinlich nicht durchsetzen konnte: „Das Tool zerlegt das Programm in Prozedurblöcke und ersetzt vorwärtsgerichtete Goto-Anweisungen durch Ifthen-else-Konstrukte, rückwärtsgerichtete Goto-Anweisungen durch Do-until-Schleifen
sowie Goto-Anweisungen mit Sprungzielen außerhalb der Blöcke durch Prozeduraufrufe.
Der Restrukturierungsprozess lässt den Objektcode um 10% - 40% und auch die Laufzeit des Programmes um bis zu 8% anwachsen. Dies ist als ein wesentlicher Grund dafür
anzusehen, daß sich das Tool in der Praxis nicht durchgesetzt hat.“ [ES92, S. 17-18]
Ab Mitte der 80er Jahre wurde die Restrukturierung von prozeduralen und objektorientierten Softwaresystemen weiterverfolgt.
Für imperative Programmiersprachen untersuchte William G. Griswold das Restrukturieren ohne Verhaltensänderung. Er verwendete die semi-funktionale Programmiersprache Scheme4 . Die Programmiersprache bot sich an, da sie imperative Merkmale und
darüber hinaus eine relativ einfache Syntax besitzt (vgl. [Gri91, S. 20]). Mit Hilfe eines für Scheme als Paket angebotenen Abhängigkeitsgraphen entwickelte Griswold einen
Ansatz für ein Werkzeug zur Restrukturierung von Quellcode.
Die Grundlage für automatisierte Refaktorisierungen objektorientierter Systeme schufen u. a. Ralph E. Johnson und William Opdyke Anfang des Jahres 1990. Sie gingen
davon aus, dass objektorientierte Frameworks nicht von vornherein vollständig geplant
werden könnten und es daher einer Umstrukturierung bedürfte. Sie untersuchten die
Umstrukturierungsmöglichkeiten bezüglich Aggregation und Vererbung5 für die Sprache C++. Als Vorteil gegenüber prozedural aufgebauter Software nahmen Johnson und
Opdyke an, dass bei einer notwendigen Erweiterung oder Modifizierung bestehender
Software objektorientiert aufgebaute Systeme geeigneter wären, da hier oft durch ein
Hinzufügen von Klassen die Abhängigkeiten aufgelöst werden könnten. Jedoch würden
trotzdem ebenso Veränderungen objektorientierter Software nötig (vgl. [JO93, S. 1ff.]).
Opdyke ermittelte aus der Beobachtung von Refaktorisierungen von Programmen Eigenschaften, die bei einer automatisierten Refaktorisierung nicht verletzt werden dürfen.
Er befürchtete, dass ein erneutes Übersetzen des Quellcodes nach der Refaktorisierung
zwar die meisten Fehler, jedoch nicht eine Veränderung der Semantik von Referenzen
und Operationen vor und nach der Refaktorisierung entdecke (vgl. [Opd92, S. 39-42]).
Eines der bekanntesten frühen Werkzeuge, das auf der Grundlage der Arbeiten von
Johnson und Opdyke entwickelt wurde, ist der von John Brant und Donald Bradley
Roberts veröffentlichte Refactoring Browser6 für Smalltalk-Programme. Roberts lieferte
die theoretischen Grundlagen zu dem Browser in einer Dissertation (vgl. [Rob99]). Der
Refactoring Browser nimmt Methodenumbenennungen zur Laufzeit vor und basiert damit auf einem anderen Konzept als die eingangs skizzierte Idee von Refaktorisierungen,
in denen die Quelltexte verändert werden, ohne dass das jeweilige Programm ausge4
Eine eigentlich funktionale Sprache, in der auch imperativ programmiert werden kann,
vgl. http://www.cs.hut.fi/Studies/T-93.210/schemetutorial/node9.html, Abruf am 27.01.2012.
5
Mit der Einschränkung, dass Mehrfachvererbung und Methodenüberladung ausgeschlossen wurden
(vgl. [Opd92, S. 35]).
6
http://st-www.cs.illinois.edu/users/brant/Refactory/RefactoringBrowser.html
11
führt wird. Da Smalltalk eine dynamische Programmiersprache ist, werden Ideen des
Refactoring Browsers im Folgenden weiter untersucht (s. Abschnitt 5.2).
Die heute etablierten Werkzeuge zur Unterstützung von Refaktorisierungen sind oft
in Entwicklungsumgebungen integriert oder als Plugin einladbar. Darüber hinaus stehen
für einige Programmiersprachen eigene Programme in Anlehnung an den Refactoring
Browser für Smalltalk zur Verfügung.7
Bisher sind die Refaktorisierungswerkzeuge sehr eng verknüpft mit der jeweiligen Entwicklungsumgebung (IDE) und Programmiersprache. Für sprachübergreifende Werkzeuge muss jedoch nach einer allgemeinen Basis gesucht werden. Interessant werden hier
exemplarisch Entwicklungsumgebungen wie Eclipse, Netbeans oder RubyMine,8 in denen von Haus aus mehrere Sprachen integriert sind und die deswegen dafür prädestiniert
sind, Refaktorisierungsumgebungen zu vereinheitlichen oder sogar sprachübergreifende
Refaktorisierungswerkzeuge anzubieten.
1.2.2 Refaktorisierungswerkzeuge als eigenes Fachgebiet
Friedrich Steimann wirft im Jahr 2010 in einem Artikel zu korrekten Refaktorisierungen
ein Problem auf: Existierende Refaktorisierungswerkzeuge würden nicht immer korrekt
arbeiten (vgl. [Ste10, S. 24-25]). Er spricht sich, als Lösungsansatz, für den Bau von Refaktorisierungswerkzeugen als eigenständige Disziplin aus. Denn verglichen mit dem Bau
von Compilern fehlen ihm anerkannte Ansätze, die identifizierten Refaktorisierungsprobleme zu lösen. Er bezieht sich auf in Entwicklungsumgebungen integrierte Werkzeuge,
die trotz hohem Qualitätsstandard dem Praxistest nicht standhalten würden: „Vollständige und korrekte Spezifikationen von Refaktorisierungen sind also nur im Rahmen der
Entwicklung von Refaktorisierungswerkzeugen sinnvoll. Leider sind auch die heute verfügbaren Refaktorisierungswerkzeuge alles andere als korrekt [...].“ [Ste10, S. 24]
Zusammengefasst schätzt Steimann existierende Werkzeuge als bisher nicht ausreichend ein und fordert eine Standardisierung, damit auf Dauer die notwendige Qualität
erzielt werden könne (vgl. [Ste10, S. 29]). Daraus folgt die Frage, welche Lösungsansätze
an dieser Stelle als weiterführend betrachtet werden können.
Steimann bevorzugt einen constraint-basierten Ansatz. Denn der Versuch, die existierenden Leitfäden und Muster direkt in Refaktorisierungsprogrammen umzusetzen,
beinhaltet für Steimann die Schwierigkeit, sich in zu vielen Fallunterscheidungen zu verlieren. Ebenso distanziert er sich von dem Ansatz, Programme als Graphen darzustellen
und damit Refaktorisierungen als Graphentransformationen durchzuführen (vgl. [Ste10,
S. 27]). Constraints seien dagegen lesbar, da in den Regeln beschrieben wird, was erfüllt
werden muss.
Steimann sieht für die Zukunft ein Baukastenprinzip: „Anstatt – wie bisher – die
Vorbedingungen und die zur Durchführung notwendigen Schritte für jede Refaktorisierung getrennt zu formulieren und anschließend mit einigem Aufwand in den Kontext
der jeweiligen Entwicklungsumgebung einzubetten, wäre es hilfreich, wenn Refaktorisierungswerkzeuge nach dem Baukastenprinzip aus erprobten Komponenten zusammenge7
8
Eine Liste gängiger Refaktorisierungswerkzeuge im Anhang.
http://www.eclipse.org, http://www.netbeans.org, http://www.jetbrains.com/ruby/
12
stellt werden könnten und wenn sie von den Entwicklungsumgebungen stets die gleiche
Infrastruktur benötigen.“ [Ste10, S. 29]
Dazu will Steimann ein Framework nutzen, in dem Constraintregeln einheitlich für alle
Sprachen formuliert werden können. Des Weiteren müssen die veränderten Programme
getestet werden können.
Infolgedessen und als Basis der vorliegenden Arbeit über Refaktorisierungswerkzeuge
für sprachübergreifende Projekte wird die deklarative Sprache Refacola9 (für constraintbasierte Refaktorisierungen) herangezogen. Refacola ist ein Schritt in die von Steimann
vorgeschlagene Richtung. Refacola soll für verschiedene Programmiersprachen genutzt
werden können; exemplarisch werden momentan Eiffel und Java unterstützt. Sprachspezifisch müssen die Programmelemente mit ihren Eigenschaften und Wertebereichen
bestimmt werden, einschließlich der Beziehungen, die sich hieraus ergeben, und den Constraintregeln, die aufgestellt werden müssen (vgl. [SKP11]).
Ebenso auf Basis von Constraints arbeiten Daniel Speicher, Tobias Rho, Günter
Kniesel an einem Werkzeug für eine logikbasierte Infrastruktur zur Codeanalyse (vgl.
[SRK07]). Das entwickelte Werkzeug JTransformer basiert auf Ausarbeitungen von Tip,
Kiezun und Bäume (vgl. [TKB03]). Tip et al. bearbeiteten das Konzept für Refaktorisierungen weiter und integrierten Ideen u. a. in die Eclipse Entwicklungsumgebung (vgl.
[TFKEBS09] und [SDSTT10]).
Im Hinblick auf die vorliegende Ausarbeitung muss gefragt werden, ob die von Steimann, Speicher et al. und Tip et al. vorgeschlagenen Ansätze auch dann greifen, wenn
mit Hilfe eines Refaktorisierungswerkzeugs sprachübergreifende Projekte refaktorisiert
werden sollen, vor allem mit Blick darauf, dass die Sprachen sich in ihren Konzepten ggf. grundlegend unterscheiden. Dass solche Werkzeuge heute notwendig sind, weil
sprachübergreifende Projekte genutzt werden, soll im Folgenden dargestellt werden.
1.3 Flexibilität durch eine programmiersprachen-übergreifende Entwicklung
Für die vorliegende Ausarbeitung wird die Java Virtual Machine (JVM) als Plattform
für eine programmiersprachenübergreifende Entwicklung herangezogen. Die Java Virtual
Machine und die ursprünglich dafür entwickelte Programmiersprache Java bieten sich im
Hinblick auf sprachübergreifende Refaktorisierungswerkzeuge zum einen deswegen an, da
es für Java bereits Werkzeuge für eine automatisierte Umstrukturierung gibt, auch wenn
diese oft als nicht ausreichend kritisiert werden und zum anderen, da heute viele weitere
Programmiersprachen existieren, die auf dieser Plattform lauffähig sind.
Ebenso könnte an dieser Stelle jedoch auch das .NET Framework als Basis herangezogen werden. Auf der .NET Plattform laufen genauso unterschiedliche Programmiersprachen, beispielsweise die zu den objektorientierten Programmiersprachen zählende
Sprache C# oder die funktionale Programmiersprache F# (an OCaml angelehnt). In einem MSDN-Artikel wird vermerkt: „Auch wenn Programmierer, die über beide Ohren in
der objektorientierten Entwicklung stecken, möglicherweise eine andere Ansicht haben,
sind funktionale Programme häufig für bestimmte Arten von Anwendungen einfacher zu
9
http://www.feu.de/ps/prjs/refacola
13
schreiben und zu verwalten.“ [New08]
Es lohnt sich also, innerhalb eines Projektes zielgerichtet unterschiedliche Programmiersprachen zu nutzen, nämlich solche, durch die sich die in der Softwareerstellung
jeweils auftretenden Probleme einfacher lösen lassen.
Das Verflechten mehrerer Programmiersprachen in einem Projekt soll im Weiteren verfolgt werden. Im Hinblick auf den eingangs festgelegten Schwerpunkt der Arbeit, dem Zusammenspiel sogenannter dynamischer und statisch typgeprüfter Programmiersprachen,
folgt eine Diskussion des Begriffs dynamisch, eine Skizze der Vorteile der Interaktion
unterschiedlicher Programmiersprachen und ein Überblick über die große Anzahl schon
existierender Sprachen, die auf der JVM laufen. Damit soll die Notwendigkeit der zukünftigen Entwicklung programmiersprachenübergreifender Refaktorisierungswerkzeuge
aufgezeigt werden.
1.3.1 Vorteile der Interaktion mit dynamischen Programmiersprachen
Um den Nutzen einer Interaktion statisch typgeprüfter und dynamischer Programmiersprachen zu erkunden, muss m. E. der Begriff dynamische Programmiersprache genauer
bestimmt werden, denn es existiert bis heute keine einheitliche Definition dieses Begriffs.
Kontrovers diskutiert,10 aber dennoch ein eventuell für diese Arbeit aufschlussreicher
Aufsatz wurde im Jahr 2005 von Oscar Nierstrasz, Alexandre Bergel, Marcus Denker,
Stephane Ducasse, Markus Gälli und Roel Wuyts veröffentlicht. Sie behaupten, dass
statische Sprachen von Natur aus nicht geeignet sind für die Realisierung von Anwendungen mit dynamischen Anforderungen: „Inherently static languages will always pose
an obstacle to the effective realization of real applications with essentially dynamic requirements.“ [NBDDGW05, S. 1]
Sie schließen daraus, dass dringend eine Forschung nach Programmiersprachen notwendig ist, die Veränderungen zur Laufzeit eines Programms unterstützen (vgl. ebd.,
S. 2). Denn die Forschung sei eingefahren auf statische Sprachen (vgl. ebd., S. 10).
Es stellt sich daraus folgend die Frage, wo der Unterschied zwischen dynamischen und
statisch typgeprüften Sprachen liegt.
Allgemein gilt als grundlegendes Kennzeichen dynamischer Programmiersprachen, dass
solche Sprachen zur Laufzeit Tätigkeiten ausführen, die andere Programmiersprachen
nicht oder zur Übersetzungszeit umsetzen.
Einerseits werden Programmiersprachen grob eingeordnet nach den Konzepten, die
die jeweilige Sprache implizit anbietet. Wird eine Reihe elementarer (folgend umrissener) dynamischer Konzepte11 unkompliziert unterstützt, gilt die Sprache als dynamische
Programmiersprache. Unkompliziert deswegen, weil sich eine dynamische Programmiersprache nicht unbedingt dadurch abgrenzt, dass eine dagegen stehende, nicht-dynamische
Sprache eine der im Folgenden skizzierten Techniken nicht beherrscht, sondern die Abgrenzung erfolgt durch die Natürlichkeit des Einsatzes dieser Technik in der jeweiligen
Programmiersprache. Es ist daher schwer, den Begriff zu fassen.
10
Vgl. dazu die Diskussion: http://lambda-the-ultimate.org/node/852#comment-7783, Abruf am
18.01.2012.
11
Exemplarisch: http://www.lesscode.de/initiative/dynamische-programmiersprachen
14
Als dynamische Konzepte gelten beispielsweise solche, in denen es möglich ist, eine
Variable zur Laufzeit an Werte mit unterschiedlichen Typen zu binden, in denen es
möglich ist, über eine Eingabe direkt Codes zur Ausführung zu bringen („eval“) oder in
denen Reflexion existiert. Reflexion bietet dann weiter die Möglichkeit, Strukturen von
u. a. Klassen oder Funktionen zur Laufzeit zu untersuchen und dynamisch zu nutzen
oder entsprechende Informationen über Objekte zu erhalten.
Jedoch ist das Aufzählen solcher Konzepte nicht ausreichend, um den Begriff dynamische Programmiersprache klar zu umreißen, da nicht alle als dynamisch geltenden
Sprachen alle Konzepte unterstützen.
Als einfache Möglichkeit soll deshalb von der konkreten Seite des Nutzens einer Programmiersprache ausgegangen werden, indem gefragt wird, wo solche Programmiersprachen heute typischerweise eingesetzt werden. Damit lassen sich eine Reihe von Programmiersprachen einordnen nach ihrer Möglichkeit, elegante Lösungen für Probleme zu bieten, die bei statisch getypten Sprachen nur umständlich gelöst werden können. Beispiele
wären Verbindungen zwischen Schnittstellen (Glue Code), Entwicklung von Benutzeroberflächen, Einsatz von Webservices, schnelles Erstellen von Prototypen, Verwalten von
Projekt-Infrastrukturen mittels Build-Skripten, Entwickeln von Testskripten u. v. m.
Wenn also in einem einzigen Projekt mehrere Programmiersprachen genutzt werden,
können die Vorteile und Stärken der jeweiligen Sprachen genutzt werden.
Für die Java Virtual Machine wurden vorwiegend dynamische Programmiersprachen
portiert oder entwickelt, um mit der statisch typgeprüften Sprache Java in Kommunikation zu treten.12
1.3.2 Java Virtual Machine als Basis mehrerer Programmiersprachen
Die weite Verbreitung der Java Virtual Machine und die Abstraktion der darunter liegenden Maschinen-Architektur ermöglicht die Entwicklung und den Einsatz von Komponenten, Frameworks und Services auf unterschiedlichen Betriebssystemen. Jedoch wird
in vielen Gruppen im Umfeld von Softwareentwicklung diskutiert, dass die ursprünglich
für die Plattform entwickelte Programmiersprache Java selbst für bestimmte Einsatzbereiche nicht flexibel genug sei.13
Darüber hinaus gilt Java verglichen mit Sprachen wie Ruby, Python, Lisp oder Smalltalk als weniger elegant und ausdrucksstark.
Als eine der ersten neu entwickelten Programmiersprachen versuchte Groovy14 im Jahr
2003, diesen Nachteil zu beheben. Groovy ist dynamisch typisiert und bietet Closures,
Operatorüberladung, eine native Darstellung für BigDecimal und BigInteger u. v. m.
Groovy wird in Bytecode übersetzt und ausgeführt. Java-Klassen können daher GroovyKlassen nutzen und umgekehrt
12
Es existieren jedoch auch Ausnahmen wie die Programmiersprache Scala, die durch ein statisches
Typsystem nicht in diese Kategorie passt.
13
Vgl. u. a. http://www.oio.de/public/java/groovy/groovy-einfuehrung.htm,
http://openbook.galileocomputing.de/javainsel/javainsel_01_002.html,
http://it-republik.de/jaxenter/artikel/Groovy-fuer-Java-Entwickler-DynamischeProgrammierung-auf-der-Java-Plattform-1065.html, alle Abruf am 18.01.2012.
14
http://groovy.codehaus.org
15
Etwas früher, im Jahr 2000, entstand Jython. Diese Programmiersprache ist nicht
neu entwickelt wie Groovy, sondern eine reine Java-Implementierung der seit Anfang
1990 existierenden Programmiersprache Python15 . Jython ermöglicht die Ausführung
von Python-Programmen auf der Java-Plattform und sämtliche Java-Bibliotheken können in Python-Programme importiert und genutzt werden.
Im Kontext aktueller, auf der Java Virtual Machine lauffähiger Sprachen werden an
dieser Stelle noch die im Jahr 2003 als Forschungssprache entwickelte und als Multiparadigmen-Sprache bezeichnete Sprache Scala16 und die im Jahr 2007 erschienene Sprache
Clojure17 hervorgehoben. Scala ist im Kern eine objektorientierte Sprache mit statischem
Typsystem, besitzt jedoch darüber hinaus funktionale Möglichkeiten. Clojure ist ein
speziell für die Java Virtual Machine entwickelter Lisp-Dialekt, der insbesondere durch
seine Unterstützung für die Entwicklung von nebenläufigen Anwendungen heraussticht.
Aus dem existierenden Pool der JVM-fähigen Programmiersprachen wird für diese
Arbeit exemplarisch JRuby18 (eine Implementierung des Ruby-Interpreters in Java) herangezogen. JRuby schafft eine Brücke zwischen Ruby und Java und vereint damit die
Vorzüge dieser beiden Sprachen.
Die Sprache Ruby19 selbst erschien im Jahr 1995. Sie gilt als dynamische Sprache.
Ruby ist rein objektorientiert und wurde darüber hinaus als Multiparadigmen-Sprache
entworfen. Damit steht es dem/der Entwickelnden offen, weitere Programmierparadigmen zur Erstellung seiner/ihrer Programme zu nutzen (s. Kapitel 3).
„The Ruby programming language was released to the public in 1995 and gained
widespread adoption in 2006. A multipurpose language that focuses on simplicity and
productivity, it combines the best features of many compiled and interpreted languages, such as easy development of large programs, rapid prototyping, almost-real-time
development, and compact code. Ruby is a reflective, dynamic, and interpreted objectoriented scripting language, and JRuby is a Java programming language implementation
of the Ruby language syntax, core libraries, and standard libraries.“ [Paw07]
Mit JRuby wird, vergleichbar mit den oben genannten Sprachen, die Nutzung der Java
Virtual Machine für die Programmiersprache Ruby ermöglicht. Der Sprachumfang von
Ruby wird von JRuby nahezu vollständig implementiert. JRuby ermöglicht darüber hinaus die Interaktion von Java nach Ruby und von Ruby nach Java. JRuby wird momentan
ständig weiterentwickelt und an gegenwärtige Anforderungen angepasst. Im Jahr 2009
wurde eine Version von JRuby vorgestellt, die auf der Software-Plattform für mobile
Geräte, Android, ausführbar ist.20
Wenn Entwickler und Entwicklerinnen heute für konkrete Problemstellungen nach
einfachen und effizienten Lösungen suchen und sich dafür auf unterschiedliche Sprachen mit jeweils unterschiedlichen Sprachkonzepten stützen, stellt sich die Frage, ob
für sprachübergreifende Projekte Refaktorisierungswerkzeuge im Einsatz sind, die den
15
http://www.python.org
http://www.scala-lang.org
17
http://clojure.org
18
http://www.jruby.org
19
http://www.ruby-lang.org
20
http://ruboto.org
16
16
unterschiedlichen Sprachkonzepten gerecht werden.
Dass sprachübergreifende Refaktorisierungswerkzeuge eine besondere Herausforderung
darstellen, soll durch das Beispiel motiviert werden, dass eine Variable in Ruby keinen
Typ besitzt. „Der Typ von Objekten wird zur Laufzeit überprüft, allerdings sind Variablen typenlos (sozusagen void). Dementsprechend gibt es keine Type-Casts und keine
typedefs.“21 Das macht Ruby als dynamische Sprache flexibel, eröffnet jedoch ein komplexes Problemfeld für sprachübergreifende Aufrufe zu einer statisch typgeprüften Sprache
wie Java. Denn es kann durch eine Analyse des Quellcodes nicht entschieden werden,
auf welche Objekte die Ruby-Variable zur Laufzeit referenzieren wird.
1.4 Sprachübergreifende Refaktorisierungswerkzeuge
JRuby als Brücke zwischen Java und Ruby birgt, wie zuvor skizziert, Vorzüge für Projekte, in denen für bestimmte Problemstellungen nach einem ausdrucksstarken Code gesucht
wird und trotzdem im vollen Umfang auf existierende Java-Bibliotheken zurückgegriffen
werden soll. Für mehrsprachige Projekte werden dafür nun geeignete sprachübergreifende
Refaktorisierungswerkzeuge benötigt.
Wenn jedoch schon Refaktorisierungswerkzeuge für einzelne Programmiersprachen
nicht ausreichend untersucht sind und bisher nur unzureichend Unterstützung anbieten,
ist abzusehen, dass solche Werkzeuge für sprachübergreifende Projekte noch am Anfang
stehen. Andreas Thies et al. beschreiben in einem Projekt „Cross-Language Refactoring:
The CLaRe Research Project“22 die Situation folgendermaßen: „In fact, the different sets
of constraint rules required for the different languages mirror commonalities and differences of the language specifications in a concise way. It is an open research question
whether cross-language refactorings will require additional constraint rules reflecting the
conditions of accessing program elements across specific language boundaries, or whether
the sharing of constraint variables (including a mapping of their values to the different
domains required by the different languages) suffices. We expect that the number of
constraint rules required for cross-language refactoring will be a linear function of the
numbers of constraint rules required for each individual language.“23
Für die vorliegende Ausarbeitung soll als Prämisse gelten, dass es eindeutig der falsche
Weg ist, eine der beteiligten Sprachen auf bestimmte, von der/den anderen Sprachen verlangte Konstrukte, mit dem Ziel, dass eine Refaktorisierung möglich wird, einzugrenzen.
Dieser Ansatz, eine Sprache wie Ruby auf ausschließlich die Möglichkeiten, die auch Java
bietet, einzuengen, beispielsweise sie mit (wie auch immer gearbeiteten) Typen zu versehen, wird hier als absolut falsch begriffen. Denn nur die skizzierte Unterschiedlichkeit
der Sprachen ermöglicht einen produktiven, effizienten und ausdrucksvollen Einsatz. Im
Folgenden wird daher versucht, die Unterschiede explizit auszuloten, um den Blick auf
sprachübergreifende Refaktorisierungen zu lenken, die ohne diese Einschränkung ablaufen können.
21
http://www.ruby-lang.org/de/documentation/ruby-from-other-languages/to-ruby-from-cand-c-, Abruf am 10.06.2011.
22
http://www.fernuni-hagen.de/ps/prjs/CLaRe/
23
Ebd.
17
Beschränkt wird sich an dieser Stelle auf die Umgebung Eclipse und das im Weiteren vorgestellte Dynamic Languages Toolkit (DLTK)24 . Das Eclipse Framework wird
als Open-Source-Umgebung für viele Projekte genutzt. Es unterstützt gängige Programmiersprachen.25
Im Folgenden wird als Grundlage eine Möglichkeit der Zusammenarbeit von unterschiedlichen Sprachen im Eclipse-Framework beschrieben. Danach wird die Sprache Ruby und die Regeln der Interaktion von Ruby und Java vorgestellt. Mit Blick auf einen
constraint-basierten Ansatz wird dann das Framework und die Sprache Refacola vorgestellt und exemplarisch eine Regel für einen sprachübergreifenden Methodenaufruf entwickelt. Anhand des Methodenaufrufs wird folgend untersucht, welche Voraussetzungen
für ein sprachübergreifendes Refaktorisieren mit dem constraint-basierten Ansatz gelten
müssen. Die Ausarbeitung schließt mit der Beschreibung der entwickelten Software, in
der die gefundenen Ideen konkret umgesetzt wurden.
24
25
http://www.eclipse.org/dltk/
Gewählt wurde die Umgebung nicht zuletzt, weil u. a. das aktuelle Projekt: Ruboto (JRuby für Android) existiert und Android von dem Eclipse Framework unterstützt wird.
18
2 Entwicklungsumgebung und mehrsprachige Projekte
Für sprachübergreifende Softwareentwicklung ist es sinnvoll, die unterschiedlichen Programmiersprachen in einem einzigen Projekt vereinen zu können. Dies ist in der für
diese Arbeit genutzten Entwicklungsumgebung Eclipse aktuell noch nicht möglich, jedoch in Planung. Im Folgenden wird die Unterstützung von Ruby (hier in der JavaImplementierung JRuby) in Eclipse skizziert und der Umgang mit den existierenden
Schwierigkeiten der Nutzung zweier unterschiedlicher Projekte (Java und Ruby) aufgezeigt.
2.1 Eclipse Dynamic Language Toolkit
Das Eclipse-Projekt bietet die Möglichkeit, über das Dynamic Language Toolkit (DLTK)
komplette Umgebungen für Programmiersprachen zu erstellen. Das Core-Framework von
DLTK stellt dafür sprachunabhängige Bausteine bereit. Entwicklungsumgebungen existieren bereits für verschiedene dynamische Sprachen, so auch für Ruby.
Es fehlt jedoch bis heute eine Möglichkeit, mehrere Sprachen in einem einzigen Projekt
gemeinsam zu integrieren. Daher existiert beispielsweise kein übergreifender Debugger.
Im Jahr 2009 sagte Andrey Platov (Projektleitung) in einem Interview: „Mithilfe von
DLTK ist es zwar bereits möglich, Entwicklungstools für neue Sprachen sehr schnell umzusetzen. Es fehlt allerdings noch an einer Unterstützung der Interoperabilität zwischen
verschiedenen Programmiersprachen, was insbesondere sehr wichtig für Sprachen ist, die
auf der Java Virtual Machine laufen. Hier gibt es ein großes Bedürfnis an Werkzeugen,
die mit diesen unterschiedlichen Sprachen umgehen können.“1
2.2 Verbindung eines Java- und Ruby-Projekts in der Eclipse
Die Entwicklungsumgebung Eclipse bietet für Java wie für Ruby (über das DLTK) die
Möglichkeit, sprachenspezifische Projekte anzulegen. Ein einziges Projekt für beide Sprachen anzulegen, ist bis zu diesem Zeitpunkt jedoch nicht möglich. Eine provisorische Lösung für die Interaktion zwischen den Sprachen ist, zwei Projekte anzulegen und damit
vorerst die existierenden Features (Syntax Highlighting, Debugger, Pretty Printer) für
die jeweilige Programmiersprache zu nutzen (s. Abbildung 2.1). Ein sprachübergreifendes
Verwenden von Werkzeugen ist dann jedoch nicht möglich.
1
http://it-republik.de/jaxenter/news/Eclipse-DLTK-Komplette-IDEs-fuer-dynamischeSprachen-bauen-050094.html, Abruf 9.01.2012.
19
Abbildung 2.1: Projekte im Package Explorer
Bekannt gemacht werden können die Projekte dann über den Klassenpfad, der zum
Ruby-Skript hinzugefügt wird.
1
2
3
require " java "
$CLASSPATH<<" / j a v a p r o j / b i n " ;
import " a . J a v a C l a s s "
Listing 2.1: Bekanntmachen des Javaprojektes
Zumindest sind nun die grundlegenden Voraussetzungen für eine sprachübergreifende
Software hergestellt, auch wenn viele der üblich genutzten Features noch fehlen.
20
3 Interoperabilität der Programmiersprachen Ruby und Java
Um eine Auseinandersetzung über Refaktorisierungswerkzeuge für eine Software, in der
beide Programmiersprachen, Ruby und Java, genutzt werden, führen zu können, muss
die Fähigkeit der Zusammenarbeit zwischen diesen Sprachen untersucht werden.
Im Folgenden werden Ruby vorgestellt und elementare für die Zusammenarbeit der
beiden Sprachen wichtige Konvertierungsregeln beschrieben.
3.1 Einordnung der Programmiersprache Ruby
3.1.1 Die Ideale des Ruby-Erfinders
Ruby1 ist eine objektorientierte Programmiersprache, die interpretiert wird.2
Der Erfinder der Sprache, Yukihiro Matsumoto, begann im Jahr 1993 aus Unzufriedenheit über verfügbare Skriptsprachen an einer eigenen Sprache zu arbeiten und gab 1995
die erste Ruby-Version frei. Er nutzte Ideen verschiedener Sprachen (Perl, Smalltalk,
Eiffel, Ada und Lisp)3 und formte daraus eine Programmiersprache, die neben der Objektorientierung mehrere weitere Programmierparadigmen unterstützt, unter anderem
prozedurale und funktionale Programmierung.
Ruby bietet darüber hinaus Garbage Collection, Ausnahmen (Exceptions), Reguläre
Ausdrücke, Introspektion, Code-Blöcke als Parameter für Iteratoren und Methoden, die
Erweiterung von Klassen zur Laufzeit, Threads und vieles mehr. Damit ist diese Sprache
für unterschiedlichste Problemstellungen einsetzbar.
Matsumoto folgte verschiedenen Design-Prinzipien4 . Beispielsweise können Programmierende gleiche Probleme mit unterschiedlichen Sprachmitteln lösen, was dem – aus
der Programmiersprache Perl entlehnten – Prinzip: „There is more than one way to do
it“ (TIMTOWTDI) entspricht und Ruby ausdrucksstark machen soll.
Eine ebenso im Sprachdesign angelegte Flexibilität bietet Matsumoto durch das Konzept „Duck Typing“5 . Ein Objekt kann über Operationen, die es zur Verfügung stellt,
angesprochen werden. In dem Kontext, in dem das Objekt benutzt wird, gilt der Typ
dann als korrekt, wenn das Objekt die erwarteten Operationen anbietet.
Eines der elementaren Prinzipien, das Matsumoto im Design berücksichtigt hat, ist
das „Principle of least surprise“ (POLS)6 . Im Hinblick auf Programmiersprachen besagt
es, dass (erfahrene) Entwickelnde relativ intuitiv programmieren können sollen, ohne
durch beispielsweise unverständliche Methoden oder Bibliotheksnamen überrascht zu
1
Ruby steht für Rubin in Anspielung auf die Programmiersprache Perl (vgl. [SM01]).
Die JRuby-Implementierung enthält seit 2007 zusätzlich einen Compiler (vgl. [NSEBD11, S. 78-96]).
3
Vgl. http://www.ruby-lang.org/en/about/
4
Vgl. http://wiki.ruby-portal.de/Rubys_Prinzipien, Abruf am 10.03.2012.
5
Vgl. ebd.
6
http://www.canonical.org/~kragen/tao-of-programming.html, Abruf am 17.02.2012.
2
21
werden. Darüber hinaus wurde Ruby als eine Sprache entworfen, die auch für NichtProgrammierende lesbar sein soll. (vgl. [MO08, S. 29])
Vor allem das Prinzip POLS zeigt, dass der Entwurf der Sprache darauf ausgerichtet
wurde, dass bei der konkreten Programmierung das Problem und nicht die Sprache selbst
im Vordergrund steht.
Matsumoto entwickelte Ruby mit Emacs7 und gcc8 unter Linux und stellte seine Programmiersprache (mit Quelltext) als freie Software9 zur Verfügung.
Ruby ist heute für alle gängigen Betriebssysteme10 verfügbar. Der Ruby-Interpreter
und die Standardbibliothek sind unter den Bedingungen der GNU General Public License
(GPL)11 nutzbar.
3.1.2 Merkmale der Programmiersprache Ruby
Ruby wurde als Multiparadigmen-Sprache entworfen. Das heißt, dass es dem/der Entwickelnden offen steht, neben der Objektorientierung weitere Programmierparadigmen zur
Erstellung der Programme zu nutzen. Die Umsetzung der verbreitetsten Paradigmen in
Ruby soll im Folgenden beschrieben werden.
Objektorientierte Programmierung
In Anlehnung an Smalltalk ist Ruby rein objektorientiert. Matsumoto verfolgte im RubySprachdesign die Zielsetzung: Everything is an Object (EIAO)12 . Beispielsweise sind
auch die – in vielen anderen Sprachen als primitive Typen geltenden – Zahlen und
Zeichenketten Objekte (s. Listing 3.1 und 3.2).
p u t s 3 . 1 4 1 5 . c l a s s # Ausgabe => float
Listing 3.1: Zahlen sind Objekte
’A B C ’ . s p l i t ( ’ ’ ) # Ergebnis = >[ ’A ’, ’B ’, ’C ’]
Listing 3.2: Zeichenketten sind Objekte
Ruby unterstützt verschiedene Ansätze der Objektorientierung:
• Klassenbasierte Objektorientierung:
Eine Klasse dient als Vorlage. Von ihr werden Instanzen erstellt. Klassen können von
anderen abgeleitet werden.
7
http://www.gnu.org/software/emacs/
GNU Compiler Collection (http://gcc.gnu.org/).
9
http://www.ruby-lang.org/de/about/license.txt, Abruf am 15.02.2012.
10
Alle Windows-Versionen ab Windows 95 und NT 4.0, MS-Dos, Mac OS und Mac OS X, IMB OS/2,
alle Linux Distributionen, FreeBSD, OpenBSD, NetBSD und Sun Solaris u. v. m.
11
http://www.gnu.org/licenses/gpl-3.0.html, Abruf am 14.02.2012.
12
http://wiki.ruby-portal.de/EIAO, Abruf am 16.02.2012.
8
22
1
2
3
4
5
6
class A
def m
@x=1 # Membervariable
p u t s @x
end
end
7
8
9
c l a s s B < A # Ableitung
end
10
11
12
b = B . new
b .m # Ausgabe = >1
Listing 3.3: Ruby klassenbasiert
• Prototypenbasierte Objektorientierung:
Objekte werden durch das Klonen bereits existierender Objekte erzeugt.
1
2
3
4
o1 = Obj ect . new
def o1 . m1 # definiere Methode fuer Objekt
puts 1
end
5
6
7
8
9
o2 = o1 . c l o n e
def o2 . m2
puts 2
end
10
11
12
o2 . m1 # Ausgabe = >1
o2 . m2 # Ausgabe = >2
Listing 3.4: Ruby prototypbasiert
• Objektorientierung mit Mixins:
Das Modul MA aus Listing 3.5 kann mit Objekten über extend und Klassen über
include kombiniert werden.
1
2
3
4
5
module MA
def m
p u t s ’m ’
end
end
6
7
8
9
10
# mit Objekten kombiniert
o = O bjec t . new
o . e x t e nd MA
o .m # Aufruf m aus MA
11
23
12
13
14
15
# mit Klassen kombiniert
class A
i n c l u d e MA
end
16
17
(A. new ) .m # Aufruf m aus MA
Listing 3.5: Mixins mit Klassen und Objekten
Prozedurale Programmierung
Im Gegensatz zu Sprachen wie Java und C# ist es in Ruby nicht notwendig, Klassen zu
verwenden. Jedes Ruby-Programm liegt implizit in einem globalen main-Objekt. Damit
kann ein Programm scheinbar aus globalen Prozeduren aufgebaut werden, die jedoch
Methoden des globalen main-Objekts sind.
1
2
3
def globales_m
puts ’ g l o b a l ’
end
Listing 3.6: Ruby prozedural
Funktionale Programmierung
Ruby ermöglicht funktionale Programmierung. Es enthält anonyme Funktionen in Form
von Blöcken und Closures.
Methoden können, wie in Listing 3.7, mit Blöcken aufgerufen werden.
1
2
3
def f ( a , x )
a . map { | a | a+x} # Closure , schliesst x mit ein
end
4
5
6
array = [ 2 , 3 , 4 ]
p u t s f ( a r r a y , 2 5 ) # Ausgabe : [27 ,28 ,29]
Listing 3.7: Ruby funktional
Weitere Paradigmen
Ruby unterstützt Metaprogrammierung, aspektorientierte und kontextorientierte13 Programmierung.
13
http://contextr.rubyforge.org/
24
3.1.3 Dynamische Aspekte der Programmiersprache Ruby
In Ruby werden Klassen oder Module zur Laufzeit aufgebaut. Daher können zur Laufzeit
Manipulationen vorgenommen werden. Im Unterschied zu einer statisch typgeprüften
Sprache können beispielsweise neue Definitionen bekannt gemacht oder die Sichtbarkeiten (public, private, protected) von Methoden verändert werden. Solche Aspekte
erschweren oder verhindern das Ableiten semantischer Informationen (s. Kapitel 5).
An dieser Stelle sollen exemplarisch Möglichkeiten der Veränderung des Ruby-Quelltextes vorgestellt werden.
Die Methoden instance_eval und class_eval
Durch instance_eval können einer Instanz Methoden hinzugefügt werden. Wie in Listing 3.8 gezeigt wird, gehört die Methode newMethodInstance zum Objekt r. Die Klasse
RubyClass selbst kennt diese Methode jedoch nicht.
Anders bei der Nutzung von class_eval: Hier wird die Klasse RubyClass um die
Methode newMethod erweitert.
1
2
3
4
5
c l a s s RubyClass
def m
puts 1
end
end
6
7
r = RubyClass . new
8
9
10
11
12
13
r . i n s t a n c e _ e v a l do # fuege r eine instanzspezifische Methode hinzu
def newMethodInstance
p u t s ’ I n s t a n z s p e z i f i s c h e Methode ’
end
end
14
15
16
17
18
19
RubyClass . c l a s s _ e v a l do # fuege RubyClass eine Methode hinzu
def newMethod
p u t s ’ Methode ’
end
end
20
21
22
r . newMethod # Ergebnis => Klassenmethode
r . newMethodInstance # Ergebnis => Instanznmethode
23
24
25
( RubyClass . new ) . newMethodForClass # Ausgabe => Methode
( RubyClass . new ) . newMethodInstance # Ausgabe => undefined method
Listing 3.8: instance_eval und class_eval
25
Die Sichtbarkeiten public, protected und private
Mit Ausnahme der privaten Methode initialize sind alle Methoden einer Klasse per
Default öffentlich. Die Sichtbarkeit einer Methode kann durch eine Angabe von public,
protected oder private festgelegt werden.14 Sichtbarkeiten sind in Ruby folgendermaßen definiert:
• public heißt öffentlich, die Methode kann von überall aufgerufen werden.
• protected heißt, die Methode kann nur von Instanzen der eigenen oder abgeleiteter
Klassen aufgerufen werden.
• private heißt, dass Methoden nicht mit einem Empfänger aufgerufen werden dürfen,
also der Empfänger implizit immer self ist. Daher kann eine private Methode nur
innerhalb der Klasse, aber auch innerhalb abgeleiteter Klassen aufgerufen werden.15
1
2
3
4
5
6
7
8
9
10
c l a s s RubyClass
def m
1
end
def ==(o t h e r )
m==o t h e r .m
end
# private :m # nicht erlaubt
p r o t e c t e d :m # erlaubt
end
11
12
13
14
r 1 = RubyClass . new
r 2 = RubyClass . new
p u t s r 1==r 2 # Ausgabe => true
Listing 3.9: Unterschied protected und private
In Ruby können Sichtbarkeiten zur Laufzeit geändert werden (s. Listing 3.10).
1
2
3
4
5
6
c l a s s RubyClass
private
def m
puts 1
end
end
7
8
x = g e t s . chomp
9
10
i f (1==x . to_i )
# Eingabe ...
11
# Erweitere die Klasse , m wird public
12
14
15
Wegen fehlender Pakete existiert die Sichtbarkeit „package“ nicht.
Der einfache Zugriff ist jedoch trotzdem über (RubyClass.new).send(:m) möglich.
26
13
14
15
16
RubyClass . c l a s s _ e v a l do
p u b l i c :m
end
end
17
18
( RubyClass . new ) .m
Listing 3.10: Sichtbarkeiten ändern
3.2 Methodenaufrufe zwischen Ruby und Java
Einleitend wurde JRuby, eine Ruby-Implementierung für die Java Virtual Machine vorgestellt. Für diese Arbeit wird JRuby als Brücke zwischen Ruby und Java herangezogen.16
Wie Java und Ruby programmiersprachenübergreifend zusammenarbeiten, wird im
Folgenden exemplarisch anhand von Methodenaufrufen vorgestellt.
3.2.1 Methodenaufruf von Java nach Ruby
Im Listing 3.11 wird ein Methodenaufruf von Java nach Ruby gezeigt. Java bietet über
die Java Scripting API17 den Zugriff auf JRuby.
1
import j a v a x . s c r i p t . ∗ ;
2
3
public c l a s s J a v a C l a s s {
4
public s t a t i c void main ( S t r i n g [ ] a r g s ) {
5
6
S c r i p t E n gi n e M a n a g e r f a c t o r y = new S c r i p t E n gi n e M a n a g e r ( ) ;
S c r i p t E n g i n e e n g i n e = f a c t o r y . getEngineByName ( " j r u b y " ) ;
7
8
9
try {
e n g i n e . e v a l ( " c l a s s RubyCl ; d e f m; p u t s 1 ; end ; end ; ( RubyCl . new ) .m" ) ;
// Ausgabe = >1
} catch ( S c r i p t E x c e p t i o n e x c e p t i o n ) {
exception . printStackTrace () ;
}
10
11
12
13
14
15
}
16
17
}
Listing 3.11: Aufruf einer Ruby-Methode aus Java
16
Weitere Implementierungen für die Java Virtual Machine, beispielsweise die Ruby Java Bridge (RJB),
http://rjb.rubyforge.org/, werden in dieser Abeit nicht berücksichtigt.
17
http://docs.oracle.com/javase/6/docs/technotes/guides/scripting/programmer_guide/
index.html, Abruf am 16.02.2012.
27
3.2.2 Methodenaufruf von Ruby nach Java
Von Ruby aus können Methoden der Java-Standardbibliothek oder eigene in Java implementierte Methoden genutzt werden. Exemplarisch für einen Java-Aufruf wird im Listing
3.12 println gerufen.18
1
2
3
4
5
6
7
require ’ java ’
i n c l u d e _ c l a s s ’ j a v a . l a n g . System ’
c l a s s JavaTest
def m
j a v a . l a n g . System : : out . p r i n t l n ( 1 )
end
end
8
9
( JavaTest . new) .m # Ausgabe=>1
Listing 3.12: Aufruf einer Java-Methode aus Ruby
3.3 Konvertierungsregeln
Für eine automatisierte Refaktorisierung sprachübergreifender Software ist eine Auseinandersetzung mit Konvertierungsregeln notwendig. Es muss beispielsweise die Frage
beantwortet werden, welche Auswirkung eine Sichtbarkeitsänderung oder Verschiebung
einer Methode in Java hat, wenn die jeweilige Methode in Ruby genutzt wird.
Die Zusammenarbeit von Java und Ruby ist weitgehend geregelt: Es existieren Vorschriften für die Abbildung spezifischer Sprachelemente auf die jeweils andere Sprache.
Jedoch sind diese Regeln nicht allein abhängig von der Implementierung des JRubyInterpreters19 ; zusätzlich können Sicherheitseinstellungen der Java Virtual Machine zu
unterschiedlichen Ergebnissen führen.
Im Folgenden sollen elementare Regeln herausgegriffen und beschrieben werden, beispielsweise die Abbildung von Datentypen auf die jeweils andere Sprache oder der Umgang mit den oben genannten sprachspezifisch unterschiedlichen Sichtbarkeitsregeln.20
Weitere Konvertierungsregeln sind in [NSEBD11] und http://kenai.com/projects/
jruby/pages/CallingJavaFromJRuby zu finden.
3.3.1 Konvertierungsregeln für Sichtbarkeiten
JRuby erlaubt den Zugriff von Ruby auf Java-Methoden, unabhängig davon, welche
Sichtbarkeit diese haben, sofern der Zugriff auf private nicht durch eine Sicherheitseinstellung explizit verboten wird. Damit können mit private und protected gekennzeichnete Methoden ebenso aufgerufen werden wie Methoden, die öffentlich sind.
Exemplarisch wird in Listing 3.14 eine Ruby-Klasse von der Java-Klasse des Listings
3.13 abgeleitet. Die private Java-Methode privateJavaMethod wird in der abgeleiteten
18
Weitere Beispiele für Methodenaufrufe in Abschnitt 5.
Für die folgenden Beispiele wurde die Version JRuby 1.6.5 herangezogen.
20
Sichtbarkeiten besitzen in Java eine andere Bedeutung als in Ruby (s. Abschnitt 3.1.3).
19
28
Ruby-Klasse gesehen und kann über das Objekt r gerufen werden. Ein folgender Test
mit Hilfe der Ruby-Methode private_method_defined? zeigt, dass die private JavaMethode vorhanden ist, jedoch die Sichtbarkeit, die die Methode in Java besaß, nicht
bekannt ist. Analog verhält es sich mit Methoden, die in Java mit protected gekennzeichnet sind.
1
class JavaClass {
2
private void privateJavaMethod ( ) {
System . out . p r i n t l n ( " privateJavaMethod " ) ;
}
3
4
5
6
}
Listing 3.13: Private Java-Methode
1
2
c l a s s RubyClass<J a v a C l a s s
end
3
4
5
6
r = RubyClass . new
r . privateJavaMethod # Ausgabe : " privateJavaMethod "
p u t s RubyClass . private_method_defined ? " privateJavaMethod " # Ausgabe=>f a l s e
Listing 3.14: Sichtbarkeitstest einer Methode
Felder werden nicht abgebildet: „On its own, JRuby doesn’t seek out a class’s fields and
try to map them to Ruby attributes.“ [NSEBD11, S. 45]
Jedoch kann ein Feld im Nachhinein zugreifbar gemacht werden (s. Listing 3.15 und
3.16).
1
2
3
class JavaClass {
private i n t p r i v a t e J a v a F i e l d = 1 ;
}
Listing 3.15: Privates Java-Feld
1
2
3
c l a s s RubyClass<J a v a C l a s s
f i e l d _ a c c e s s o r : p r i v a t e J a v a F i e l d=> : myField
end
4
5
6
r = RubyClass . new
p u t s r . myField # Ausgabe=>1
Listing 3.16: Zugriff auf das private Java-Feld
3.3.2 Regeln für die Abbildung von Datentypen
Die Abbildungen von Datentypen in die jeweils andere Sprache sind wie folgt festgelegt21 :
21
Vgl. https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby, Abruf am 20.03.2012.
29
Ruby nach Java
Ruby Typ
"foo"
1
1.0
true/false
1 << 128
Java nach
Java Typ
public String
public byte
public short
public int
public long
public float
public double
Java Typ
java.lang.String
java.lang.Long
java.lang.Double
java.lang.Boolean
java.math.BigInteger
Ruby
Ruby Typ
String
Fixnum
Fixnum
Fixnum
Fixnum
Float
Float
Abbildung 3.1: Abbildung von Datentypen über JRuby
Problematisch werden die tabellarisch dargestellten Abbildungsregeln bei der Nutzung
von überladenen Methoden in Java. Zwar findet jeder Datentyp eine Entsprechung, jedoch kann es durch eine Erweiterung durch Überladen von Methoden in Java ggf. zu
nicht bedachten Auflösungen kommen (s. Listing 3.18).
public void m( i n t i ) {
System . out . p r i n t l n ( " i n t " ) ;
}
public void m( long l ) {
System . out . p r i n t l n ( " l o n g " ) ;
1
2
3
4
5
6
}
Listing 3.17: Überladene Methoden
1
( J a v a C l a s s . new) .m 3 # Ausgabe=>long
Listing 3.18: Problematik bei überladenen Methoden
Ausgangspunkt ist an dieser Stelle, dass eine ganze Zahl von JRuby grundsätzlich auf
Long abgebildet wird. Wird das Verhalten gewünscht, dass eine Zahl als Integer eingeordnet wird, muss, wie in Listing 3.19 gezeigt, mit java_send der Typ angegeben werden.
1
( J a v a C l a s s . new) . java_send :m, [ Java : : i n t ] , 3
Listing 3.19: Zahl wird als Integer erkannt
Der/die Programmierende ist an dieser Stelle gefordert, die richtige Syntax für sein/ihr
Vorhaben zu wählen.
3.4 Bedingungen für eine Refaktorisierung
Für die im Folgenden als Grundlage einer automatisierten Refaktorisierung vorgestellte
Constraint-Sprache (s. Kapitel 4) müssen für die beteiligten Programmiersprachen und
30
für die Übergriffe auf die jeweils andere Sprache Bedingungen dafür herausgearbeitet
werden, wann eine automatisierte sprachübergreifende Refaktorisierung erlaubt werden
kann.
Einleitend wurde jedoch das Problem aufgezeigt, dass semantische Informationen nur
für statisch typgeprüfte Sprachen ohne Weiteres zur Verfügung stehen. Daher wird im
Folgenden der Schwerpunkt darauf gelegt, solche Informationen auch für dynamische
Sprachen zu finden.
Im beiliegenden Prototyp wird eine Refaktorisierung erlaubt, wenn versucht wird,
Java-Quellcode zu refaktorisieren, ohne dass Zugriffe aus Ruby enthalten sind. Werden
Zugriffe aus Ruby gefunden, wird als Zwischenlösung die Refaktorisierung verboten (s.
Abschnitt 4.2). Das Refaktorisieren von Ruby-Quellcode wurde nicht in die Diskussion
mit einbezogen.
Trotz dieser Eingrenzung des Themas sollen im folgenden Exkurs einige markante
Bedingungen für Sprachübergriffe von Java und Ruby als Ausblick skizziert werden.
Denn das endgültige Ziel ist ein Rückschreiben des korrekt veränderten Quellcodes. Die
Diskussion beschränkt sich auf Java-Refaktorisierungen, die ggf. Auswirkungen auf den
Ruby-Quellcode haben. Genutzt wird ausschließlich JRuby.22
3.4.1 Sichtbarkeiten sind nicht relevant
Im Abschnitt 3.3.1 wurde beschrieben, dass JRuby den Zugriff auf Java-Methoden über
Objekte erlaubt, unabhängig davon, welche Sichtbarkeit die Methoden haben. Konkret werden alle public und protected Methoden über Java-Reflexion gesucht. (vgl.
[NSEBD11, S. 290])
Je nach Java-Sicherheitseinstellungen werden auch die privaten Methoden in die Auswahl mit einbezogen.
Danach wird der passende Parametertyp nach der obigen Tabelle Abbildung 3.1 ausgewählt. Die Sichtbarkeiten selbst sind an dieser Stelle nicht mehr relevant. Würde eine
Refaktorisierung in Java erfolgen, die eine öffentliche Methode auf protected setzt,
würde aus Ruby heraus diese Methode trotzdem weiter aufgerufen.
Ob beim Auffinden eines sprachübergreifenden Aufrufs dieser Methode die Sichtbarkeitsumbenennung verboten werden muss, also die Methode weiter public bleiben sollte,
um hier eine Klarheit zu erhalten, muss diskutiert werden. Denn es ist ja ausdrücklich
erlaubt, protected Methoden über Objekte in Ruby aufzurufen.
3.4.2 Getter und Setter
Methoden, die die Zugriffe auf Membervariablen haben, besitzen in Ruby einen besonderen Status. Da ein direkter Zugriff nicht möglich ist, werden Methoden gleichen Namens
definiert, die in Ausdrücken und auf der linken Seite von Zuweisungen genutzt werden
können (s. Listing 3.20).
22
Andere Portierungen von Ruby auf die Java Virtual Machine mögen andere Regeln haben, wurden
hier jedoch nicht berücksichtigt.
31
1
2
3
4
5
6
7
8
c l a s s RubyClass
def m
@m
end
d e f m=(x )
@m=x
end
end
9
10
11
12
r=RubyClass . new
r .m=4
p u t s r .m # Ausgabe=>4
Listing 3.20: Zugriff auf Membervariablen in Ruby
Die in Java üblichen „Getter“ und „Setter“ (setM, getM) werden von JRuby auf die
gleiche Weise abgebildet: „As an added bonus, Java-style getters and setters are callable
as Ruby-style attribute accessors.“ [NSEBD11, S. 44]
Die folgenden Aufrufe in Listing 3.21 sind für die Java-Methode setM(long l) äquivalent:
setM ( 1 )
m=1
Listing 3.21: Äquivalente Zugriffe auf eine Java-Membervariable von Ruby aus
Für den Wunsch, die Methode setM in einem Refaktorisierungsprozess umzubenennen, heißt das, dass im Ruby-Quellcode nicht allein der eigentliche Name der Methode,
sondern ebenso das Wort hinter „set“ (in Kleinschrift) einschließlich Zuweisungoperator
beachtet werden muss.
3.4.3 to_s
Folgende Sonderregelung muss beachtet werden, wenn in Java eine Methode nach to_s
umbenannt werden soll: bei dem Versuch, die String-Repräsentation eines Java-Objektes
in Ruby auszugeben, sucht JRuby zuerst die Methode to_s. Ist to_s nicht vorhanden
und wird eine Methode toString gefunden, wird diese genutzt. Findet JRuby keine der
beiden Methoden, wird eine Java-String-Repräsentation des Objektes ausgegeben.
1
2
3
4
5
6
7
8
9
class JavaClass {
public S t r i n g to_s ( ) {
return " to_s " ;
}
// Ueberschreibe die Methode toString
public S t r i n g t o S t r i n g ( ) {
return " t o S t r i n g " ;
}
}
Listing 3.22: to_s und toString
32
1
p u t s J a v a C l a s s . new # Ausgabe=>to_s
Listing 3.23: Ausgabe einer String-Repräsentation eines Java-Objektes in Ruby
Unabhängig vom Rückgabetyp wird to_s als erstes gerufen, wenn das Java-Objekt in
Ruby ausgegeben wird.
Das bedeutet für eine Refaktorisierung, dass durch ein Umbenennen einer Methode
nach to_s das normale Verhalten der Erzeugung einer String-Repräsentation überdeckt
wird. Wurde in Ruby die vorherige Methode genutzt, verhält sich das Programm nach
Umbenennen anders.
33
4 Refacola – Constraint-Sprache und Framework
Als Basis für Refaktorisierungen in sprachübergreifender Software wird im Folgenden ein
existierendes Projekt: Refacola1 genutzt. Der Begriff „Refacola“ wird in zwei Bedeutungen verwendet: Zum einen als Bezeichnung einer deklarativen Sprache, die am Lehrgebiet
Programmiersysteme der FernUniversität Hagen zur Spezifizierung constraintbasierter
Refaktorisierungen entwickelt wurde (vgl. [SKP11]). Zum anderen bezeichnet „Refacola“
ein dort erstelltes programmiersprachenunabhängiges Framework für Refaktorisierungsprozesse.
Als Technologien werden die Werkzeuge Xtext2 /Xtend3 , ein Open-Source-Framework
für die Entwicklung von Programmiersprachen und domänenspezifischer Sprachen, sowie
Xpand4 aus dem Eclipse Modeling Project5 genutzt.
Xpand, eine statisch typisierte Templatesprache, generiert auf Basis sprachenspezifischer Templates aus vorher definierten Constraintregeln Java-Quellcode. Zusammen
mit dem Refacola Framework und einem Constraint Solver können Refaktorisierungen
angelegt und durchgeführt werden (vgl. [SKP11, S. 13]).
Im Hinblick auf ein Entwerfen einer sprachübergreifenden Software werden in diesem
Kapitel die wichtigsten Sprachmittel von Refacola skizziert.
Exemplarisch wird eine Constraint-Regel formuliert, durch die das Umbenennen einer Java-Methode verboten wird, wenn die Methode von Ruby aus aufgerufen wird.
Als Grundlage für eine Refaktorisierung sprachübergreifender Projekte mit dynamisch
und statisch typgeprüften Sprachen können perspektivisch weitere Constraint-Regeln
definiert werden.
4.1 Die Sprache Refacola
Die Sprache Refacola besitzt drei Arten von Modulen, die für die zu refaktorisierende
Sprache definiert werden müssen:
• Language Definition (languages)
• Rule Definitions (rulesets)
• Refactoring Definitions (refactorings)
1
http://www.fernuni-hagen.de/ps/
http://www.eclipse.org/Xtext/
3
http://www.eclipse.org/Xtext/#xtend2
4
http://www.eclipse.org/modeling/m2t/?project=xpand
5
http://www.eclipse.org/modeling/
2
34
4.1.1 Sprachdefinition
Die Sprachdefinition (Language Definition) besteht aus Programmelementen, die je nach
Programmiersprache unterschiedlicher Art sein können. Für die meisten objektorientierten Sprachen sind es Klassen, Felder, Methoden, Referenzen und lokale Programmelemente wie lokale Variablen und Parameter (vgl. [SKP11, S. 4]).
Daneben besitzt jedes Programmelement Eigenschaften (properties), beispielsweise Bezeichner, Sichtbarkeit oder die umgebene Klasse. Die Eigenschaften werden auf
Constraintvariablen abgebildet und mit Wertebereichen (domains) verknüpft (s. Listing
4.1).
Durch Refaktorisierungsvorgänge werden die Eigenschaften verändert, nicht die Programmelemente selbst (vgl. [SKP11, S. 4ff.]).
Als Beispiel für Wertebereiche von Eigenschaften führen Steimann et al. die Menge aller gültigen Bezeichner für die Eigenschaft identifier oder die Menge {private,
package, protected, public} für die Eigenschaft Sichtbarkeit an.
1
2
3
4
/*
* Refacola Language Definition
*/
language Java
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
kinds
abstract F i e l d <: TypedMember , HideableMember , NamedEntity /* ... */
I n s t a n c e F i e l d <: InstanceMember , F i e l d
S t a t i c F i e l d <: StaticMember , F i e l d
/* ... */
properties
identifier
" \\ i o t a " : I d e n t i f i e r
a c c e s s i b i l i t y " \\ a l p h a " : A c c e s s M o d i f i e r
owner " \\ c h i " : Type " "
t l o w n e r " \\ lambda " : LocationType " "
h os t Package " \\ p i " : L o c a t i o n P a c k a g e " "
d e c l a r e d T y p e " \\ tau " : Type
inferredType
" \\ tau " : Type
domains
A c c e s s M o d i f i e r = { p r i v a t e , package , p r o t e c t e d , p u b l i c }
LocationType = [ TopLevelType ]
queries
binds ( r e f e r e n c e : Reference , e n t i t y : Entity ) " r e f e r e n c e binds to e n t i t y "
/* ... */
Listing 4.1: Auszug aus der Refacola Language Definition: java.language.refacola
Neben den dargestellten Wertebereichen existieren sogenannte programmabhängige
Wertebereiche (Program-dependent domains), die dann auftreten, wenn sich Wertebereiche erst aus dem Programm selbst ergeben. Beispielsweise kann ein Programmelement
der Art Klasse ebenso Wert einer Eigenschaft eines Programmelements Methode sein,
die in dieser konkreten Klasse definiert wurde. Der Bereich oder die Stelle (location)
ist eine Eigenschaft, die nun als Wert die konkrete Klasse erhält. Aufgelöst wird der
35
Zusammenhang durch eine Abbildung von der Art eines Programmelements auf den
entsprechenden Wertebereich.
Wenn für Wertebereiche eine explizite Ordnung (Ordered Program-Dependent Domains) benötigt wird, also wenn in programmabhängigen Wertebereichen beispielsweise
eine Vererbungshierarchie existiert, wird dies mit berücksichtigt (vgl. [SKP11, S. 5]).
4.1.2 Definition der Constraintregeln
In Refacola werden Constraints über Regeln definiert. Nur wenn die Bedingung erfüllt
ist, wird der unter then definierte Constraint dem Constraintproblem hinzugefügt.
Jede Constraintregel hat im Allgemeinen folgendes Aussehen:
program queries
constraints
Für das Listing 4.2 würde die Regel in folgender Formel (4.2) abstrahiert:
binds(r, d)
r.ι = d.ι r.τ = d.τ
1
(4.1)
(4.2)
Java . language . r e f a c o l a
2
3
4
r u l e s e t RubyJavaRules
language Java
5
6
7
8
9
10
11
12
13
14
15
16
17
rules
exampleRule
for a l l
r : Java . NamedReference
d : Java . NamedEntity
do
if
Java . b i n d s ( r , d )
then
r . identifier = d. identifier ,
r . inferredType = d . declaredType
end
Listing 4.2: Beispiel-Regel
Die Query binds(r, d) ergibt ’true’, wenn die Referenz r an das Feld d gebunden ist.
Die Constraints r.ι = d.ι r.τ = d.τ erzwingen die Gleichheit der beiden Bezeichner
(Identifier ι) und die Gleichheit der Typen (Declared/Inferred Type τ ). Die Formel (4.2)
beschreibt, dass, wenn die Referenz r an die Entity d gebunden ist, der Identifier und
der Typ gleich sein müssen.
36
4.1.3 Definition von Refaktorisierungen
Neben einer vollständigen Sprachdefinition für eine Programmiersprache und ConstraintRegeln, durch die die notwendigen Constraints erzeugt werden können, muss definiert
werden, welche Eigenschaften geändert werden müssen. Unterschieden wird zwischen Eigenschaften, deren Änderungen erzwungen werden (forced), und Eigenschaften, deren
Änderungen erlaubt werden (allowed). Alle weiteren Eigenschaften werden nicht geändert. In Listing 4.6 werden über import die Sprachspezifikation und Constraintregeln
eingebunden. Dem Refactoring wird ein Name gegeben (refactoring), die Programmiersprache (language) und die Constraintregeln (uses) festgelegt.
4.2 Definition eines Verbots für das Refaktorisieren sprachübergreifender
Methodenaufrufe
Ziel ist, das Umbenennen von Java-Methoden zu verbieten, wenn sie von Ruby aus
aufgerufen werden, d. h. das Constraint-Problem darf dann keine Lösung haben.
Bei einem Aufruf einer Java-Methode aus Ruby wird die Gleichheit des Originalnamens mit dem Methodennamen verlangt (s. Listing 4.3). Nun muss jede Lösung des
Constraint-Problems diese Bedingung erfüllen. Lautet das Ziel der Refaktorisierung, die
Methode nach einem neuen Namen umzubenennen, hat das Constraint-Problem keine
Lösung und das Verbot ist durchgesetzt. Die Regel besagt, dass für jede Methode mit
Hilfe einer einstelligen Query isCalledFromRuby geprüft wird, ob sie aus Ruby aufgerufen wird. Wenn ja, wird das obige Constraint aktiv.
methodRenameNotAllowedCollision
for a l l
m: Java . Method
do
if
Java . isCalledFromRuby (m)
then
m. i d e n t i f i e r =m. i d e n t i f i e r . o r i g i n a l # Pseudocode
end
1
2
3
4
5
6
7
8
9
Listing 4.3: Methodenumbenennung verbieten: einstellige Query
Da der Originalidentifier nicht trivial zu erhalten war, wird ein neues Programmelement
RubyReference eingeführt, das für den Aufruf aus Ruby steht und als Eigenschaft nur
den Identifier hat. Die Query isCalledFromRuby wird nun durch eine zweistellige Query
bindsFromRuby ersetzt. Diese Query bekommt als Übergabe die eingeführte RubyReference und die Methode m und liefert true, wenn die RubyReference ein Aufruf dieser
Methode darstellt. Nun wird nicht mehr Gleichheit mit dem Originalnamen, sondern
mit der RubyReference verlangt.6
6
Die Sonderregeln aus den Abschnitten 3.4.2 und 3.4.3 wurden noch nicht berücksichtigt.
37
Folgende Regel wurde definiert:
bindsF romRuby(r, m)
r.ι = m.ι
(4.3)
In das Listing 4.1 wurde die Art ReferenceFromRuby und die Query bindsFromRuby
aufgenommen, s. Listing 4.4.
1
2
3
4
/*
* Refacola Language Definition
*/
language Java
5
6
7
8
9
10
11
kinds
/* ... */
ReferenceFromRuby <: NamedReference { } //Ruby
queries
/* ... */
bindsFromRuby ( r e f e r e n c e : ReferenceFromRuby , method : Method ) //Ruby
Listing 4.4: Auszug aus der Refacola Language Definition: java.language.refacola
Als Regel wurde die methodRenameNotAllowedCollision definiert, s. Listing 4.5.
1
2
3
4
5
6
7
8
9
10
methodRenameNotAllowedCollision
for a l l
r : Java . ReferenceFromRuby
m: Java . Method
do
if
Java . bindsFromRuby ( r , m)
then
r . i d e n t i f i e r = m. i d e n t i f i e r
end
Listing 4.5: Methodenumbenennung verbieten: java.ruleset.refacola
Als Definition der Refaktorisierung wurde der Identifier der Java.Method auf forced
gesetzt, s. Listing 4.6.
1
2
import " Java . l a n g u a g e . r e f a c o l a "
import " RubyJavaRules . r u l e s e t . r e f a c o l a "
3
4
5
6
refactoring RubyJava
language Java
uses RubyJavaRules
7
8
9
forced
i d e n t i f i e r o f Java . Method a s X
10
38
11
12
allowed
/* .. */
Listing 4.6: Definition von Refaktorisierungen: java.refactoring.refacola
Wird dieser Constraint erzeugt, wird ein identifizierter Java-Methodenaufruf aus Ruby
verboten.
39
5 Codeanalyse als Voraussetzung für Refaktorisierung
Für eine automatisierte Refaktorisierung wird ein Modell benötigt, das den Programmcode mit den für die Refaktorisierung notwendigen Informationen repräsentiert. Meist
wird dies in Form eines abstrakten Syntaxbaums (Abstract Syntax Tree, AST) realisiert.
Ein AST bildet den Programmcode in einer Baumstruktur ab (vgl. [ASU99, S. 60]).
Für eine Refaktorisierung müssen Programmelemente, beispielsweise Klassen, Methoden oder Felder, als Knoten im AST vorliegen. Darüber hinaus müssen spezielle Eigenschaften der jeweiligen Elemente bekannt sein. Beispiele hierfür sind der Typ des
jeweiligen Programmelements oder der Bereich, in dem das Element definiert wurde.
Erst wenn die spezifizierten Eigenschaften von Programmelementen vollständig vorliegen, kann eine Entscheidung getroffen werden, in welcher Form refaktorisiert wird bzw.
ob die gewünschte Refaktorisierung überhaupt erlaubt ist.
Im Framework Refacola werden die gefundenen Informationen in einer Faktendatenbank gespeichert, die als Basis für Abfragen genutzt wird. In Abhängigkeit von den
Abfragen (Queries) werden Constraints erzeugt, die später dem Constraintsolver übergeben werden (vgl. [SKP11, S. 6]).
In der statisch typgeprüften Sprache Java sind solche Informationen statisch verfügbar, d. h. der jeweilige Quellcode kann eingelesen, ein AST kann aufgebaut und Knoten
können mit den in einer semantischen Analyse gefundenen Eigenschaften attributiert
werden.
Es stellt sich jedoch die Frage, wie diese für eine Refaktorisierung notwendigen Informationen gefunden werden können, wenn Java mit der dynamischen Sprache Ruby
in einem Projekt verwoben ist. Denn nun müssen über die Quelltexte beider Sprachen
Informationen vorliegen, aus denen eine Faktendatenbank als Grundlage für die Constraintgenerierung erstellt wird.1
Zur Verdeutlichung sollen im Folgenden anhand des Refaktorisierungswunsches, eine
Java-Methode umzubenennen (s. Listing 5.1 und Abbildung 5.1), die existierenden Probleme umrissen werden.
1
2
3
4
5
public c l a s s J a v a C l a s s {
public void m( ) {
System . out . p r i n t l n ( " Java m" ) ;
}
}
Listing 5.1: Java-Klasse mit Methode
1
Darüber hinaus müssen von beiden Sprachen die Sprachdefinitionen und die Constraintregeln vorliegen
(vgl. [SKP11]).
40
Abbildung 5.1: Umbenennen der Java-Methode im Framework Refacola
In Listing 5.2 wird gezeigt, dass über einen AST nicht eindeutig bestimmt werden
kann, ob die Methode m über ein Java- oder ein Rubyobjekt gerufen wird, da über
eine Eingabe entschieden wird, ob der Wert von o ein Java- oder Ruby-Objekt ist.
Diese Möglichkeit in Ruby basiert auf dem Prinzip Duck Typing (s. Abschnitt 3.1.1).
In Java wäre der Typ der Variablen o durch das statische Typsystem bekannt. Eine
Übersetzung des Quellcodes wäre dann nur mit einem Interface möglich, das von den
Klassen RubyClass und JavaClass implementiert wird.
1
o = RubyClass . new
2
3
4
5
i f ( 1 == e i n g a b e )
o = J a v a C l a s s . new
end
6
7
o .m # Uneindeutig
Listing 5.2: Bindung an unterschiedliche Objekte mit Ruby und Java
Dieses Beispiel zeigt, dass hier zwar ein AST für Ruby und Java aufgebaut werden
kann, die semantischen Inhalte der Programmelemente jedoch statisch nicht eindeutig
identifiziert werden können.
An dieser Stelle soll das eigentliche Problem schon genannt werden: Die in einem AST
mit semantischer Analyse über einen Ruby-Quelltext gesammelten Informationen sind
prinzipiell unzureichend. Zwar können für einen Ruby-Quellcode die Reihenfolge der Anweisungen identifiziert und Programmelemente gefunden werden, wenn jedoch in Ruby
zur Laufzeit neue Klassen bekannt gemacht oder neue Sichtbarkeiten (public, private,
protected) bestimmt werden oder darüber hinaus die Programme selbst insgesamt abgeändert werden können (s. Abschnitt 3.1.3), dann wird eine semantische Analyse extrem
schwierig oder versagt insgesamt (s. Abschnitt 5.1.3).
Jedoch soll nochmals darauf hingewiesen werden, dass solche Sprachmöglichkeiten
Ausdruck der Dynamik von Ruby sind und die eingangs beschriebene gewünschte Flexibilität dieser Sprache ausmacht; ein Grund, weshalb Ruby in Verbindung mit Java für
Software-Projekte herangezogen wird.
Im Folgenden wird als erstes eine statische Codeanalyse diskutiert und die Probleme
des Ansatzes aufgezeigt. Danach wird ein zweiter Ansatz vorgestellt, der von der Programmiersprache Smalltalk und dem damals für diese Sprache entwickelten Refactoring
41
Browser inspiriert ist: das Sammeln der notwendigen Informationen wird losgelöst von
der statischen Analyse des Quellcodes und in die Laufzeit verschoben.
Abschließend werden die Ansätze miteinander verbunden, mit dem Ziel, dass eine
Refaktorisierung weder Gefahr läuft, die Semantik des Ursprungsprogramms durch eine
falsche Umstrukturierung zu verändern, noch dass eine Refaktorisierung sofort verboten
wird, obwohl sie im Grunde möglich gewesen wäre.
Die Ansätze sind prototypisch als Software umgesetzt worden, die in einem Folgekapitel vorgestellt wird.
5.1 Statische Codeanalyse
Eingangs wurde der Blick darauf gelenkt, dass eine statische Codeanalyse von RubyQuelltexten zumindest schwierig, wenn nicht sogar unmöglich ist. Aufgrund dieser Problematik stellt sich die grundsätzliche Frage, ob es überhaupt sinnvoll ist, eine statische
Codeanalyse in die Diskussion einer automatisierten Refaktorisierung sprachübergreifender Software mit einzubeziehen. Hier wird eine mögliche sinnvolle Verbindung zwischen
den im Folgenden skizzierten, unterschiedlichen Ansätzen einschließlich der statischen
Analyse in Aussicht gestellt (s. Abschnitt 5.3). Daher wird zunächst ein Weg beschrieben, wie durch eine statische Codeanalyse eines Ruby-Skripts die für einen sprachübergreifenden Refaktorisierungsvorgang notwendigen Informationen zumindest begrenzt gesammelt werden können.
Natürlich kann aus einem Ruby-Quelltext ein AST aufgebaut und dann versucht werden, per semantischer Analyse entsprechend der Sprachdefinition die jeweiligen Eigenschaften zu finden. Dafür kann ausgenutzt werden, dass statische Informationen in einer
Entwicklungsumgebung meist ohnehin zumindest in Ansätzen vorhanden sind, z. B. um
Syntaxhighlighting, Auto-Vervollständigung und Quellcode-Navigation anbieten zu können.2
Im Folgenden sollen Möglichkeiten, aber auch auftretende Probleme einer statischen
Codeanalyse anhand von Beispielen gezeigt werden. Der einleitend beschriebene Refaktorisierungswunsch, eine Java-Methode umzubenennen, dient als Faden, nach dem die
Beispiele ausgesucht wurden. Das Eclipse-Plugin AST4CongenJRubyView3 wurde entwickelt, um diese statische Codeanalyse prototypisch auszuloten.4
5.1.1 Exkurs – Mehrdeutigkeit in Ruby-Quelltexten
Im folgenden Exkurs werden Ruby-Quelltexte noch ohne Zugriff auf Java behandelt.
Es soll gezeigt werden, dass statisch nicht entschieden werden kann, welche Typen die
2
In der Entwicklungsumgebung Eclipse werden beispielsweise über das Paket DLTK Möglichkeiten dazu
bereit gestellt.
3
Das Plugin AST4CongenJRubyView wurde im Zuge der Untersuchung entwickelt, um die Möglichkeiten eines statischen AST-Aufbaus zu testen. Eine Beschreibung der Möglichkeiten und Grenzen des
Plugins findet sich in Abschnitt 6.1.
4
Die Erkenntnis, dass ein solches Vorhaben für eine dynamische Sprache in eine Sisyphusarbeit mündet,
da eine Unzahl von sprachspezifischen Fällen berücksichtigt werden müssen, wird im Abschnitt 6.1
mit einbezogen.
42
Objekte besitzen, die die Variablen im Verlauf des Programms referenzieren. Für einen
Refaktorisierungswunsch in einem Ruby-Projekt müssen daher über eine statische Codeanalyse ggf. viele unterschiedliche Programmabläufe im Quellcode nachverfolgt werden.
Je mehr im Programm zur Laufzeit entschieden wird, desto komplexer wird der Versuch,
die entsprechenden Fakten statisch zu sammeln. Dieser Exkurs dient u. a. als Grundlage, dasselbe Verhalten später im Hinblick auf Zugriffe von Ruby auf Java diskutieren zu
können.
Als Beispiel wird ein Programm vorgestellt, in dem erst zur Laufzeit entschieden wird,
welchen Typs die Objekte sind, an die die Variable o gebunden wird (s. Listing 5.3).
Variablen sind in Ruby typenlos. Spätestens bei einer Nutzereingabe, die den Ablauf
verändern kann, ist es nicht mehr möglich, eine Aussage über den darauf folgenden
Variablenzustand zu treffen.
1
2
3
4
5
c l a s s RubyClass1
def m
puts ’ 1 ’
end
end
6
7
8
9
10
11
c l a s s RubyClass2
def m
puts ’ 2 ’
end
end
12
13
o = RubyClass1 . new
14
15
x = g e t s . chomp
16
17
18
19
i f (2==x . to_i )
# Eingabe ...
o = RubyClass2 . new
end
20
21
o .m
# Ueber Eingabe wurde das Objekt bestimmt , das nun o referenziert
Listing 5.3: Bindung an unterschiedliche Objekte in Ruby
Nach Nutzereingabe wird entweder die Klasse RubyClass2 instanziiert oder nicht. Über
welches Objekt am Ende des Listings 5.3 die Methode m wirklich gerufen wird, ist nicht
vorhersehbar.
Mit Hilfe des Plugins AST4CongenJRubyView können der AST des Programms und
die Eigenschaften beobachtet werden (s. Abbildung 5.2). Die Variable o wird mit den
entsprechend möglichen Klassennamen der Objekte angezeigt, die sie referenzieren könnte.
43
Abbildung 5.2: Ruby AST Darstellung des AST4CongenJRubyView-Plugins mit geöffnetem Informationsfeld
Innerhalb dieses Ruby-Quelltextes kommt es zu Mehrdeutigkeiten, die beispielsweise
vom DLTK5 nachverfolgt und dort als „Ambigous“[sic] gekennzeichnet werden.
Über ein Durchlaufen der Kontrollfluss-Pfade werden die Java-Methodenaufrufe verfolgt und darüber die jeweiligen Typen der Empfänger gefunden. Mehrdeutigkeiten werden gespeichert und können als Informationen für einen Refaktorisierungsprozess genutzt
werden.6
Für den Refaktorisierungswunsch, die Methode m der Klasse RubyClass1 umzubenennen, müsste diese Methode ebenso in der Klasse RubyClass2 umbenannt werden (<RubyClass2,RubyClass1>, s. Abbildung 5.2).7 Im Grunde ist also eine Refaktorisierung für
dieses einfache Beispiel (noch) möglich.
Jedoch lässt sich erahnen, dass schon ein etwas komplexerer Code die Analyse immens erschweren würde. Denn dann muss nach weiteren möglichen Aufrufen der Methode m gesucht werden, um diese Methode dort ebenso umzubenennen. Exemplarisch
wäre folgendes Szenario möglich: Eine Klasse RubyClass3 existiert und hat ebenfalls
eine Methode m definiert. Würde an einer weiteren Aufrufstelle eine Mehrdeutigkeit zwischen RubyClass3 und RubyClass2 existieren, zeigt sich, dass es nicht ausreicht, nur die
5
org.eclipse.dltk.evaluation.types
Eine prototypische eigene Implementierung wird in Abschnitt 6.1 skizziert.
7
Da an dieser Stelle noch kein sprachübergreifender Methodenaufruf existiert, wird als Äquivalent für
das einleitende Beispiel exemplarisch mit einen Ruby-Methodenaufruf gearbeitet.
6
44
anfangs gefundenen Klassen RubyClass1 und RubyClass2 zu beachten. Nun muss der
Vorgang für RubyClass3 wiederholt werden, also auch für diese Klasse Methodenaufrufe
gesucht und umbenannt werden. Und auch nun können wiederum weitere Abhängigkeiten auftreten.
Ein anschließendes Problem ist, dass die existierenden DLTK-Pakete zwar für Rubyquellcode implementiert wurden,8 der Javaquellcode von dem DLTK jedoch ignoriert
wird, was im Folgenden gezeigt wird.
5.1.2 Mehrdeutigkeit bei Sprachübergriffen zwischen Ruby und Java
In folgenden Listing 5.4 wird ein Beispiel für Mehrdeutigkeit in sprachübergreifenden
Projekten gezeigt. Die Variable o referenziert entweder auf ein Java- oder auf ein RubyObjekt, die Auswahl erfolgt wie im vorigen Beispiel über Eingabe.
1
2
3
4
5
6
import " a . J a v a C l a s s "
c l a s s RubyClass
def m
puts ’ 1 ’
end
end
7
8
9
o = RubyClass . new
x = g e t s . chomp
10
11
12
13
i f (2==x . to_i ) # Eingabe
o = J a v a C l a s s . new
end
14
15
o .m # Es wird entweder ein Java - oder ein Rubyobjekt referenziert
Listing 5.4: Bindung an unterschiedliche Objekte in Ruby und Java
Hier versagt das DLTK. Wie in Abbildung 5.3 ersichtlich, wird nun nur noch die RubyKlasse RubyClass erkannt, die Java-Klasse wird ignoriert. Dadurch wird die Mehrdeutigkeit nicht erkannt. Das DLTK bietet also momentan keine Möglichkeit, sprachübergreifende Methodenaufrufe zu erfassen.
8
Stand Februar 2012.
45
Abbildung 5.3: Informationsfeld des AST4CongenJRubyView-Plugins
Für JRuby und sprachübergreifende Methodenaufrufe ist dementsprechend eine Erweiterung nötig: In Ruby-Quelltexten müssen die Java-Aufrufe eindeutig identifiziert und
im AST des DLTK mit notiert werden.
In dem Plugin AST4CongenJRubyView wurden die Voraussetzungen dafür prototypisch implementiert: hier werden die Kontrollfluss-Pfade von Java-Methodenaufrufen
verfolgt. Referenzen auf Java-Objekte werden festgehalten und die Methodenaufrufe über
das jeweilige Objekt im AST mit abgespeichert (s. Abschnitt 6.1).
Visualisiert zeigt das Plugin AST4CongenJRubyView die gefundenen Methodenaufrufe von Ruby nach Java durch einen Pfeil an. Weitere Informationen über den Java-Aufruf
befinden sich in dem zum Knoten gehörigen Informationsfeld (s. Abbildung 5.4).
46
Abbildung 5.4: Ruby AST mit visualisierten Zugriffen nach Java
Damit ist eine Verbindung der Java-Aufrufe mit der zuvor beschriebenen Möglichkeit,
mit Hilfe des DLTK Mehrdeutigkeiten in Ruby-Quelltexten zu finden, hergestellt.
Ist der Java-Methodenaufruf eindeutig identifiziert, müssten für den eingangs formulierten Wunsch, die Methode m im Java-Quellcode umzubenennen, nun die abstrakten
Syntaxbäume beider Quellcodes mit entsprechenden semantischen Informationen ähnlich
dem Exkurs im Abschnitt 5.1.1 für eine Refaktorisierung herangezogen werden.9
Jedoch tritt im folgenden Beispiel eine Verschärfung durch ein Laden von Klassen
mit zur Laufzeit generierten Namen, die z. B. aus einem externen Speicher wie einer
Konfigurationsdatei geladen werden, ein. Das ist ein nicht unübliches Vorgehen in Softwareprojekten.10
9
In der vorliegenden Ausarbeitung wird nicht mit dem Aufbau eines Java-ASTs, sondern mit Reflexion der Java-Klassen gearbeitet. Dieses Verfahren wird genutzt, weil sprachübergreifende Aufrufe
vorerst nur gefunden werden sollen, ein Rückschreiben eines refaktorisierten Quellcodes jedoch nicht
Ziel dieser Ausarbeitung ist und daher das Refaktorisieren per Regel verboten wird (s. Abschnitt
4.2). Ein positiver Nebeneffekt ist, dass zum Testen der Binärcode einer Java Klasse ausreicht. Da
für JRuby ebenso nur der Binärcode bei Sprachübergriffen für die Lauffähigkeit von Programmen
erforderlich ist, kann ggf. die übersetzte Java-Klasse oder die jar-Datei in das Ruby-Projekt gelegt
werden. Für ein endgültiges, im Zuge dieser Arbeit nur angedachtes, Rückschreiben von refaktorisiertem Code müssen dann die jeweiligen Quelltexte der beteiligten Sprachen zur Verfügung stehen.
Da bisher noch kein einheitliches Projekt angelegt werden kann (s. Abschnitt 2.2), müssten hier über
Konfigurationsdateien die jeweiligen Projektpfade angepasst werden.
10
Vgl. Dependency-Injection.
47
5.1.3 Endgültiges Versagen der Typanalyse bei dynamischen Klassennamen
In Listing 5.5 kommen die Klassennamen exemplarisch aus einer Datei oder Datenbank
und werden über eval ausgeführt. Die Klassennamen selbst, die Anzahl und die Reihenfolge sind vorher nicht bestimmbar.
1
2
# Aus einer Datenbank / XML - Datei . Die Zeichenkette wird hier zum
Klassenobjekt
c l a s s e s =[ " RubyClass1 " , " RubyClass2 " , " J a v a C l a s s " ]
3
4
5
6
7
x
#
#
#
= g e t s . chomp
Eingabe 0 => Ausgabe RubyClass1
Eingabe 1 => Ausgabe RubyClass2
Eingabe 2 => Ausgabe JavaClass
8
9
10
# Das Klassenobjekt wird genutzt
o = e v a l ( c l a s s e s [ x . to_i ] ) . new
11
12
o .m
Listing 5.5: Zeichenkette als Klassenname
Wenn nun die Klassennamen nicht mehr aus einer Datenbank kämen, sondern dynamisch
über einen nicht trivialen Algorithmus errechnet würden, würde eine automatisierte Codeanalyse versagen.11
Damit tritt der Fall ein, dass im Grunde alle Möglichkeiten in Betracht kämen: Über
welches Objekt nun der Aufruf der Methode m erfolgt, ist nicht nachzuvollziehen.
5.1.4 Vorläufiger Ablauf einer statischen Codeanalyse
Ein Refaktorisierungsprozess (ohne Rückschreiben) in Refacola, in der eine Java-Methode umbenannt werden soll, benötigt mit statischer Codeanalyse folgenden Aufbau:
• Ein AST vom Ruby-Quellcode wird erstellt.
• Eine semantische Analyse des Programmcodes wird nach der Ruby-Sprachdefinition
durchgeführt.
• Aufrufe nach Java werden über einen AST des Javaquelltextes oder über Reflexion
unter Zuhilfenahme der schon übersetzt vorliegenden Class-Datei überprüft und mit
in den Ruby-AST eingehängt.
• Die Informationen werden genutzt, um Fakten bzw. Constraints für Refacola zu generieren.
Im Ganzen fasst der Ansatz der statischen Codeanalyse zu kurz. Denn für eine automatisierte Umstrukturierung muss der gesamte Ruby-Code mit den korrekten Eigenschaften
11
Theoretisch ist dieses Problem nicht entscheidbar.
48
als Modell zur Verfügung stehen. Da in einem Ruby-Programm beispielsweise der Programmablauf zur Laufzeit geändert werden kann (s. Abschnitt 3.1.2) und darüber hinaus
über Eingabe oder Algorithmus Klassennamen abgeholt oder generiert werden können,
die dann geladen werden, ensteht eine schwer nachverfolgbare Komplexität bis hin zu einer nicht mehr entscheidbaren Situation. Das erschwert oder verhindert, die notwendigen
Eigenschaften für eine Refaktorisierung statisch zu sammeln. Eine statische Codeanalyse
(alleine) reicht also für eine automatisierte Refaktorisierung nicht aus.
5.2 Test-Suite zum Sammeln von Informationen für eine Refaktorisierung
Weiterführend für eine Refaktorisierung der dynamischen Sprache Ruby (und vermutlich weiterer dynamischer Sprachen) und daraus folgend ebenso für sprachübergreifende
Projekte ist ein Ansatz, der in Anlehnung an den zuvor genannten Refactoring Browser
für Smalltalk12 entwickelt wird. Die ursprüngliche Idee stammt von Don Roberts, John
Brant und Ralph Johnson, die diesen Ansatz wie folgt beschrieben:
„The Refactoring Browser uses method wrappers to collect runtime information. These
wrappers are activated when the wrapped method is called and when it returns. The
wrapper can execute an arbitrary block of Smalltalk code. To perform the rename method
refactoring dynamically, the Refactoring Browser renames the initial method and then
puts a method wrapper on the original method. As the program runs, the wrapper
detects sites that call the original method. Whenever a call to the old method is detected,
the method wrapper suspends execution of the program, goes up the call stack to the
sender and changes the source code to refer to the new, renamed method. Therefore, as
the program is exercised, it converges towards a correctly refactored program.“ [RBJ97,
S. 12]
Für diese Arbeit soll jedoch keine Umbenennung der originalen Methode erfolgen, sondern eine von Roberts an anderer Stelle formulierte Idee genutzt werden, dass Programme zur Laufzeit beobachtet und Informationen über das Programm gesammelt werden
können: „The analysis then consists of data that was observed in a running program.
Since it is impossible to observe every possible run of the program, the result is still an
approximation.“ [Rob99, S. 64]
Denn Smalltalk ist im Unterschied zu Ruby imagebasiert. Ruby basiert auf Dateien.
Das Image von Smalltalk wird zur Laufzeit verändert. In Ruby müssten die Umbenennungen in Dateien rückgeschrieben werden, was einen immensen Aufwand mit sich
bringen würde.
Daher wird hier der Weg eingeschlagen, Programme zur Laufzeit zu beobachten. Dafür
wird eine in vielen Projekten schon vorhandene Test-Suite eingesetzt.13
Idee ist, während eines Testdurchlaufs die für eine Refaktorisierung notwendigen Informationen zu sammeln.
12
Ein gleichnamiger Ruby Refactoring Browser ist für Emacs entwickelt worden, jedoch seit 2007 anscheinend nicht mehr aktualisiert worden. Vgl. http://www.xref.sk/about.html, Abruf 28.02.2011.
13
Vorteil des Heranziehens von existierenden Test-Suites ist, dass hier kein Mehraufwand für die Programmierenden eintritt, da das Testen in vielen heutigen Projekten zum Standard gehört.
49
Um dieses Vorgehen für ein sprachübergreifendes Projekt sinnvoll einsetzen zu können,
wird es ausdrücklich Pflicht für die Programmierenden, eine notwendige Test-Suite zum
jeweiligen Projekt zu entwickeln. Im Zuge eines Refaktorisierungsprozesses wird diese
dann vor dem Refaktorisierungsprozess durchlaufen, damit alle potentiellen Java-Aufrufe
auch tatsächlich stattfinden. Das Durchlaufen der Test-Suite ist ein Äquivalent zum
Ausführen des Smalltalkprogramms im Refactoring Browser.
Für das eingangs formulierte Beispiel, den Refaktorisierungswunsch, eine Methode
im Java-Quellcode umzubenennen, gilt als Zwischenziel, diejenigen Stellen zu finden,
an denen diese Java-Methode im Ruby-Quelltext gerufen wird. Diesmal wird statt der
statischen Codeanalyse (über Java-AST oder Reflexion) ein Testvorgang vorgeschaltet,
mit dem die Java-Methodenaufrufe im Ruby-Quelltext gefunden und dann gespeichert
werden. Damit sind die für einen Refaktorisierungsvorgang notwendigen Informationen
schließlich vorhanden.
Als vorläufiges Ergebnis der prototypischen Implementierung dieser Ausarbeitung werden diese gespeicherten Informationen genutzt, um – mit Hilfe einer entwickelten Software XRefact14 in Verbindung mit dem existierenden Framework Refacola – eine Methodenumbenennung in Java zu verbieten (s. Abbildung 5.5), wenn diese Methode von
Ruby aus aufgerufen wird. Ansonsten wird die Methode umbenannt (s. Abbildung 5.6).
Abbildung 5.5: Refacola – Methodenumbenennung verboten
14
XRefact wurde als Arbeitstitel der Software gewählt.
Paket de.feu.ps.refacola.jruby mit Modifizierung der Java Sprachdefinition des Refacola-Frameworks
(s. Abschnitt 6.2).
50
Abbildung 5.6: Refacola – Methodenumbenennung erlaubt
Schwerpunkt muss nun die Suche nach praktikablen Möglichkeiten sein, die sprachübergreifenden Aufrufe während der Laufzeit zu erkennen. Im Folgenden werden zwei
Ansätze vorgestellt, die für eine Refaktorisierung notwendigen Informationen über ein
zuvor erfolgtes Durchlaufen der möglichen Programmpfade zu erhalten: zum einen das
Nutzen eines modifizierten JRuby-Interpreters, zum anderen das Verwenden eines JavaPakets, durch das Methodenaufrufe von Ruby nach Java auf der Java Virtual Machine
beobachtet werden können. Die Vor- und Nachteile dieser Ansätze werden diskutiert und
im Folgekapitel die prototypischen Umsetzungen der Ansätze beschrieben (s. Kapitel 6).
5.2.1 Test-Suite mit modifiziertem JRuby-Interpreter
Für das Erkennen von sprachübergreifenden Aufrufen kann der JRuby-Interpreter selbst
modifiziert werden. Da der Interpreter eine freie Software (s. Abschnitt 3.1.1) ist, besteht ohne Weiteres die Möglichkeit, einen für Refaktorisierungsmöglichkeiten weiter
entwickelten Interpreter bereitzustellen.
Grundsätzlich muss dafür eine Speicherung der Java-Aufrufe in die Interpreter-Software integriert werden. Im JRuby-Interpreter bieten sich dafür diejenigen Stellen an, an
denen die Verantwortlichkeit für die Sprachübergriffe implementiert wurde (s. Abschnitt
6.2.1). Vorteil ist, dass an diesen Stellen ebenso weitere, später für das Rückschreiben
eines veränderten, refaktorisierten Quellcodes notwendige, Informationen zur Verfügung
stehen, beispielsweise die Zeilennummer des Methoden-Aufrufs in Ruby, denn der Interpreter nutzt die Zeilennummer selbst für ein Schreiben eines Methoden-Rückgabewerts
nach Ruby. Mit anderen Worten könnte die gesamte Infrastruktur des Interpreters gleich
auch für den Refaktorisierungsprozess genutzt werden.
Ein Refaktorisierungsprozess mit Test-Suite in Verbindung mit Refacola und einem
modifizierten Interpreter hat folgende Reihenfolge:
51
1. Eine Testsuite wird implementiert.
2. Die Testsuite wird vor jedem Refaktorisierungsprozess gestartet; die potentiellen JavaAufrufe finden statt.
3. Der modifizierte JRuby-Interpreter zeichnet die Methodenaufrufe von Ruby nach Java
auf.
4. Die Aufzeichnungen werden genutzt, um Fakten bzw. Constraints für Refacola zu
generieren.
5.2.2 Test-Suite mit Instrumentierung
Als zweite Idee für die Suche nach sprachübergreifenden Aufrufen wird das Paket java.
lang.instrument15 (s. Abschnitt 6.2.3) herangezogen. Diese API wird genutzt, um in
einer laufenden Java Virtual Machine Klassen zu verändern. Für die Refaktorisierung
reicht jedoch ein Nachverfolgen der sprachübergreifenden Methodenaufrufe über das Instrumentieren der JVM-Instanz aus.
Ein großer Vorteil gegenüber dem im vorigen Abschnitt vorgeschlagenen Ansatz, den
JRuby-Interpreter zu modifizieren, ist, dass der Einsatz des Pakets java.lang.instrument die Möglichkeit bietet, die Interaktion zwischen mehreren unterschiedlichen Programmiersprachen über die Java Virtual Machine zu loggen. Für Projekte, die neben
Java und Ruby (über JRuby) noch Python (über Jython), Groovy oder Clojure nutzen, könnte der Ansatz praktikabler sein, da nicht alle Interpreter modifiziert werden
müssten, sondern einheitlich die Aufrufe über die gleiche JVM-Instanz geloggt werden.
Nachteil dieses Ansatzes ist, dass an dieser Stelle weitere Informationen wie Zeilennummern nicht mehr vorhanden sind.
Ein Refaktorisierungsprozess mit Test-Suite in Verbindung mit Refacola und JavaInstrument ersetzt in der vorigen Aufzählung den Punkt 3:
3. Die JVM-Instanz wird instrumentiert und Methodenaufrufe von Ruby (oder anderen
Programmiersprachen, die auf der JVM laufen) nach Java aufgezeichnet.
5.3 Ausblick auf eine sinnvollen Verbindung der unterschiedlichen Ansätze
Im Jahre 2006 sagte Charles Nutter, einer der Entwickler von JRuby, bezüglich Refaktorisierung, dass erst eine Kombination aus statischer und dynamischer Analyse ein
zufriedenstellendes Ergebnis für eine Refaktorisierung von Ruby liefern wird: „Even then,
Ruby’s ability to rewrite itself through a wide variety of eval calls makes refactoring with
100% confidence nearly impossible. I believe that some combination of static analysis
and online refactoring will be necessary to sufficiently solve the refactoring question.“
[ENEB06]
15
http://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html
52
Eine Verbindung der skizzierten statischen Codeanalyse mit dem Ansatz, über TestSuites die notwendigen Informationen bereit zu stellen, könnte ein Schritt in diese von
Nutter nur angedeutete Richtung sein.16
Denn beide Ansätze für sich genommen haben Vor- und Nachteile. Der im vorigen
skizzierte Vorteil der statischen Codeanalyse ist, dass in der hier genutzten Entwicklungsumgebung Eclipse bereits notwendige statische Informationen bereitgestellt werden, da sie für beispielsweise die Quelltextdarstellung (Syntax Highlighting) ebenfalls
benötigt werden. Der ebenso dargelegte Nachteil der statischen Codeanalyse, dass im
Zusammenhang mit den dynamischen Eigenschaften der Sprache Ruby die Nachverfolgung komplex bis nicht mehr entscheidbar wird (s. die Abschnitte 5.1.2 und 5.1.3),
macht jedoch ein alleiniges Nutzen der statischen Codeanalyse für eine Refaktorisierung
unzureichend. Im Gegensatz dazu ist es der Vorteil des Ansatzes des Sammelns von
notwendigen Informationen über eine Test-Suite, dass bei korrekt erstellten Tests alle
potentiellen sprachübergreifenden Methodenaufrufe (und alle Ruby-Methodenaufrufe)
gefunden werden.
Für eine Verbindung beider Ansätze spricht, dass der Fall eintreten kann, dass Programmierende Test-Suites vernachlässigen oder sie unvollständig führen. Dann würde
der Vorteil des Ansatzes, über eine Test-Suite Informationen für eine Refaktorisierung
zu sammeln, negiert, denn es käme zu falschen Ergebnissen. Eine vorher mit einbezogene
statische Codeanalyse könnte hier die Fehler zumindest abmildern.
Mit anderen Worten bietet eine Verbindung der statischen Analyse mit dem Ansatz des
Nutzens von Test-Suites die Möglichkeit, in programmiersprachenübergreifenden Projekten mit JRuby und Java nahezu die gleichen Möglichkeiten für Refaktorisierung zu
erhalten, wie in Refaktorisierungsprozessen über die Sprache Java ohne Sprachübergriff.
Erweiternd kann dieser Ansatz m. E. für alle für die Java Virtual Machine implementierten Sprachen gelten. Zumindest dann, wenn eine Test-Suite mit Instrumentierung
genutzt wird (s. Abschnitt 5.2.2), können über mehrere beteiligte Sprachen die Aufrufe
über die Java Virtual Machine geloggt werden.
Ob das ebenso auf der Microsoft .net Plattform möglich ist, wird an dieser Stelle nicht
weiter untersucht. Jedoch existiert hier ebenso eine Möglichkeit zur Instrumentierung
(vgl. [Pie01]).
16
Eine weitere für Ruby vorhandene Möglichkeit, Quellcode nachzuverfolgen, die an dieser Stelle nicht
mehr diskutiert wird:
http://wiki.ruby-portal.de/Referenz:Kernel#set_trace_func, Abruf 05.07.2011.
53
6 Beschreibung der Software
6.1 Das Plugin AST4CongenJRubyView
Das Eclipse-Plugin AST4CongenJRubyView zeigt den abstrakten Syntaxbaum des Ruby-Quelltextes als Baumansicht. Über Doppelklick auf einzelne Knoten des Baumes werden Informationen über das jeweilige Element angezeigt(s. Abbildung 5.2). AST4CongenJRubyView arbeitet nur mit Ruby-Dateien, bei Aufruf einer Java-Datei wird eine
Fehlermeldung angezeigt. Gefundene sprachübergreifende Aufrufe werden in der Baumansicht durch Pfeile gekennzeichnet.
Das Plugin wurde über File>New>Project>Plug-in Project angelegt. Im EclipseWizard wurde als Ansicht der Tree viewer ausgewählt, der eine Baumansicht zur Verfügung stellt.
Die Inhalte der jeweiligen Knoten des Baums werden durch das Überschreiben der
gewünschten Funktionen der Klasse AstVisitor gesammelt; dafür wurde die Klasse
AstInformationFinder von AstVisitor abgeleitet (s. Abbildung 6.1).
Zusätzlich wurde eine sprachenunabhängige Möglichkeit des DLTK für Typinferenz
herangezogen, die für Ruby implementiert ist: Im Paket org.eclipse.dltk.ruby.typeinference befindet sich dafür die Klasse RubyTypeInferencer. Da die DLTK-Unterstützung für Ruby noch im Aufbau ist, galt es, die Möglichkeiten auszuloten, die diese
Klasse bietet. Die Klasse AstInformationFinder integriert beide Ansätze.
Abbildung 6.1: AST4CongenJRubyView - Aufbau der Plugin-Software
Java-Methodenaufrufe aus Ruby werden prototypisch über Reflexion gefunden (s. Ab-
54
schnitt 5.1). Die Klasse JavaPerClassLoadReflector übernimmt die Aufgabe, nach den
entsprechenden Methodennamen zu suchen.
Insgesamt kann das Plugin herangezogen werden, um sich einen Überblick über das
aktuelle Ruby-Skript einschließlich existierender Java-Methodenaufrufe zu verschaffen.
Die ursprüngliche Idee, damit genug Informationen für einen Refaktorisierungsprozess
zu erhalten, wurde im Abschnitt 5.1.3 bereits theoretisch negiert.
6.2 XRefact
XRefact wurde in Anlehnung an den Refactoring Browser aus Smalltalk (s. Abschnitt
1.2.1) angedacht. XRefact ist in das Framework Refacola integriert und dient zum Sammeln der Informationen, die für einen Refaktorisierungsprozess in Refacola benötigt werden.
XRefact vereint einen modifizierten JRuby-Interpreter, modifizierte Teile des Frameworks Refacola, Teile des Plugins AST4CongenJRubyView und ein Programm, das mit
Hilfe des Java-Pakets java.lang.instrument (s. Abschnitt 5.2.2) und der Software des
Projekts Javassist1 entwickelt wurde.
Für XRefact wurde
• JRuby modifiziert, so dass dort Methodenaufrufe von Ruby nach Java aufgezeichnet
werden,
• Refacola modifiziert, so dass die Aufzeichnungen benutzt werden können, um Fakten
bzw. Constraints zu generieren,
• eine Software mit Hilfe des Pakets java.lang.instrument und Javassist erstellt, um
über die Java Virtual Machine selbst Methodenaufrufe zu finden.
Im Outline-Fenster kann eine Java-Methode ausgewählt werden, die umbenannt werden soll (s. Abbildung 6.2).
1
http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/
55
Abbildung 6.2: Umbenennen einer Java-Methode im Outline
Ein Refaktorisierungs-Wizard bietet die Möglichkeit, einen neuen Methodennamen
einzugeben. Als nächstes wird im Wizard angezeigt, ob das Umbenennen der JavaMethode erlaubt oder, wenn ein Aufruf dieser Methode aus Ruby gefunden wurde, verboten wird (s. Abbildung 5.6 und 5.5). Ist das Umbenennen erlaubt, wird die Methode im
Java-Quelltext umbenannt, bei Verbot werden die einzelnen Aufrufe mit Zeilennummern
angezeigt und der Wizard beendet.
Wird versucht, eine Ruby-Methode im Ruby-Quelltext umzubenennen, wird eine Meldung generiert, dass dies in diesem Prototyp noch nicht erlaubt ist.
Als Speichermedium wurde eine XML-Datei gewählt. Diese Datei wird durch den modifizierten JRuby-Interpreter geschrieben, wenn ein Test ausgeführt wird (s. Abbildung
6.3).
56
Abbildung 6.3: Test-Suite
Nur die sprachübergreifenden Methodenaufrufe werden gespeichert. Wird eine Methode mehrmals aufgerufen, können die Aufrufe über die Zeilennummern identifiziert
werden (s. Abbildung 6.4).
Abbildung 6.4: XML-Datei
6.2.1 Modifizierter JRuby-Interpreter
Der JRuby-Interpreter wurde an den Stellen modifiziert, an denen die Java-Methoden
gesucht und aufgerufen werden.2 Dort wurde ebenso das Speichern von Informationen
2
Klasse InstanceMethodInvoker in org.jruby.java.invokers.
57
in die später benötigte XML-Datei eingefügt (s. Abbildung 6.4).
6.2.2 Modifiziertes Refacola-Framework
Zum einen wurde Refacola durch den Refaktorisierungs-Wizard erweitert. In einem Ordner JRuby befindet sich dafür u. a. das Ruleset, in dem definiert wird, dass eine Refaktorisierung (vorerst) verboten wird, wenn eine Java-Methode umbenannt werden soll, die
aus Ruby aufgerufen wird (s. Listing 4.2).
Zum anderen wurde das Auslesen der XML-Datei eingefügt. Mit Hilfe der Informationen aus dieser Datei werden die Fakten generiert, die schließlich ggf. zum Verbot der Refaktorisierung führen. Dafür wurde die im Framework existierende Klasse
PropertyFactGenerator um das Auffinden und Vergleichen des Ruby-Methodenaufrufs
mit der Java-Methodendefinition erweitert. In der Refacola Language Definition für Java
wurde die passende Query hinzugefügt (s. Listing 4.1). Auf der Konsolenausgabe kann
der Fakt bindsFromRuby beobachtet werden, der generiert wird, wenn der Verbotsfall
eintritt.
Vorerst nur zur Beobachtung (mit Ausblick auf eine mögliche Verbindung der Ansätze, wie in Abschnitt 5.3 beschrieben) wurde darüber hinaus der AST-Aufbau des
Plugins AST4CongenJRubyView über eine Refacola-Extension mit eingebunden in die
Konsolenausgabe.
6.2.3 Instrumentation
Abbildung 6.5: Interpreter-Argument
Ein Java-Agent wird als Argument an JRuby übergeben. Die Software instrumentation.jar besitzt eine premain-Methode, die vor der main-Methode ausgeführt wird
und in derselben Java Virtual Machine arbeitet wie der JRuby-Interpreter-Prozess. Im
Manifest der Jar-Datei muss die Premain-Class notiert werden. Nun können Methodenaufrufe in der Java Virtual Machine nachverfolgt werden und mit Hilfe der Software
Javassist, die in die instrumentation.jar integriert wurde, die entsprechenden Namen,
Parameter, Parametertypen und Rückgabewerte gefunden werden (s. Listing 6.1).
58
E n t e r i n g a . J a v a C l a s s . m1( i n t ) −− m1 ( I )V
4711
E x i t i n g a . J a v a C l a s s . m1( i n t )
Listing 6.1: Konsolenausgabe des Agenten instrumentation.jar
59
A Auflistung, Installations- und Bedienungsanleitung der
beigelegten Software
A.1 Beigelegte Software
• Software XRefact:
XRefact als Eclipse-Projekte (mit Quellcode, gepackt als Zip-Datei).
Modifizierter JRuby-Interpreter (Version 1.6.5).
• Software zum Paket java.lang.instrument:
instrumentation.jar (mit Quellcode, gepackt als Zip-Datei).
Hinweise zum Erstellen und Ausführen der jar-Datei (instrumentation.txt).
• Test-Projekte:
Eclipse-Projekt: javaproject (mit Quellcode, gepackt als Zip-Datei).
Eclipse-Projekt: rubyproject (mit Quellcode, gepackt als Zip-Datei).
• Plugin
AST4CongenJRuby (mit Quellcode, gepackt als jar-Plugin-Datei).
A.2 Installation und Bedienung
A.2.1 XRefact
• Notwendige Voraussetzungen: Eclipse mit Eclipse Modeling Framework einschließlich
Xtext und Xpand SDK; DLTK einschließlich Ruby-Unterstützung.
• XRefact basiert auf Refacola. Einige Pakete von Refacola wurden für den vorliegenden
Prototypen erweitert. Daher sollte XRefact mit den veränderten Refacola-Sourcen und
dem eigens für JRuby implementierten Paket importiert werden.
• Refacola, Stand Oktober 2011. Im Detail wurden folgende Veränderungen vorgenommen:
Hinzufügt: Projekt de.feu.refacola.jruby.
Hinzufügt: In Projekt de.feu.ps.refacola.lang.java.jdt den Ordner
de.feu.ps.refacola.lang.jruby.facts.
Verändert:
refacola/lang/java/jdt/factgeneration/PropertyFactGenerator.java
(Veränderungen gekennzeichnet).
Verändert: refacola/Java.language.refacola (Veränderungen gekennzeichnet).
60
• mwe2-Workflow mit run ausführen:
GenerateJRubyApi.mwe2 in Projekt de.feu.ps.refacola.jruby,Ordner refacola.
GenerateJavaApiJava.mwe2 in Projekt de.feu.ps.refacola.lang.java,
Ordner refacola.
• Das de.feu.ps.refacola.jruby-Paket muss als Eclipse-Applikation gestartet werden.
• Die mitgelieferten Java- und Rubyprojekte müssen in die Eclipse Runtime Application
importiert werden. Hinweis: Die Pfade sind für diesen Prototypen fest notiert, siehe
dazu Hilfsdatei xrefact.txt. Daher sollten die Projekte nicht umbenannt werden,
andernfalls müssen die in der xrefact.txt festgehaltenen Pfade in den jeweiligen
Dateien mit umbenannt werden.
• Der modifizierte JRuby-Interpreter ist einsatzbereit. Er kann jedoch auch über die
beiliegende ant-Datei1 übersetzt werden.
• Der modifizierte JRuby-Interpreter muss als Ruby-Interpreter in der Eclipse eingetragen sein.
• Die Applikation zur Instrumentierung liegt im JRuby-Ordner.
• Soll zusätzlich instrumentiert werden, muss der Ruby-Interpreter mit dem Aufruf
-J-javaagent:/pfad/instrumentation.jar gestartet werden (s. Abbildung 6.5).
• Zum Umbenennen einer Java-Methode: Aufruf Outline-View, rechte Maustaste auf
umzubenennende Methode, Rename Java...(Refacola) starten.
A.2.2 Plugin AST4CongenJRubyView
• Notwendige Voraussetzungen: Eclipse mit DLTK einschließlich Ruby-Unterstützung.
• Die jar-Datei AST4CongenJRubyView.jar als Plugin installieren.
• Zu finden ist das Plugin unter Window>Show View>Other...
• Bei geöffnetem Rubyskript den AST-TreeView öffnen. Informationen über einzelne
Knoten können über Doppelklick abgerufen werden.
1
http://ant.apache.org
61
B Abkürzungen
API
AST
DLTK
EIAO
GPL
irb
JVM
POLS
RJB
TIMTOWTDI
Application Programming Interface
Abstract Syntax Tree
Dynamic Languages Toolkit
Everything is an Object
GNU General Public License
Interactive Ruby
Java Virtual Machine
Principle of least surprise
Ruby Java Bridge
There is more than one way to do it
62
C Liste bekannter Refaktorisierungswerkzeuge
• IntelliJ IDEA
http://www.jetbrains.com/idea/features/refactoring.html
• RubyMine
http://www.jetbrains.com/idea/features/refactoring.html
• Eclipse
http://www.eclipse.org/articles/Article-LTK/ltk.html
• NetBeans RefactoringNG
http://kenai.com/projects/refactoringng
• Visual Studio Refactor! Pro
http://www.devexpress.com/Products/Visual_Studio_Add-in/Refactoring
• ReSharper
http://www.jetbrains.com/resharper
• Smalltalk Refactoring Browser
http://st-www.cs.illinois.edu/users/brant/Refactory/RefactoringBrowser.html
• Emacs XRefactory
http://www.xref.sk/about.html
• Ruby Refactoring Browser
http://www.kmc.gr.jp/proj/rrb/index-en.html
63
Abbildungsverzeichnis
2.1
Projekte im Package Explorer . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.1
Abbildung von Datentypen über JRuby . . . . . . . . . . . . . . . . . . . 30
5.1
5.2
5.3
5.4
5.5
5.6
Umbenennen der Java-Methode im Framework Refacola . . . . . . . . .
Ruby AST Darstellung des AST4CongenJRubyView-Plugins mit geöffnetem Informationsfeld . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Informationsfeld des AST4CongenJRubyView-Plugins . . . . . . . . . .
Ruby AST mit visualisierten Zugriffen nach Java . . . . . . . . . . . . .
Refacola – Methodenumbenennung verboten . . . . . . . . . . . . . . . .
Refacola – Methodenumbenennung erlaubt . . . . . . . . . . . . . . . .
.
.
.
.
.
44
46
47
50
51
6.1
6.2
6.3
6.4
6.5
AST4CongenJRubyView - Aufbau der Plugin-Software
Umbenennen einer Java-Methode im Outline . . . . .
Test-Suite . . . . . . . . . . . . . . . . . . . . . . . . .
XML-Datei . . . . . . . . . . . . . . . . . . . . . . . .
Interpreter-Argument . . . . . . . . . . . . . . . . . .
.
.
.
.
.
54
56
57
57
58
64
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 41
Listingverzeichnis
2.1
3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
3.9
3.10
3.11
3.12
3.13
3.14
3.15
3.16
3.17
3.18
3.19
3.20
3.21
3.22
3.23
4.1
4.2
4.3
4.4
4.5
4.6
5.1
5.2
5.3
5.4
5.5
6.1
Bekanntmachen des Javaprojektes . . . . . . . . . . . . . . . . . . .
Zahlen sind Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . .
Zeichenketten sind Objekte . . . . . . . . . . . . . . . . . . . . . . .
Ruby klassenbasiert . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ruby prototypbasiert . . . . . . . . . . . . . . . . . . . . . . . . . . .
Mixins mit Klassen und Objekten . . . . . . . . . . . . . . . . . . . .
Ruby prozedural . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ruby funktional . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
instance_eval und class_eval . . . . . . . . . . . . . . . . . . . .
Unterschied protected und private . . . . . . . . . . . . . . . . . .
Sichtbarkeiten ändern . . . . . . . . . . . . . . . . . . . . . . . . . .
Aufruf einer Ruby-Methode aus Java . . . . . . . . . . . . . . . . . .
Aufruf einer Java-Methode aus Ruby . . . . . . . . . . . . . . . . . .
Private Java-Methode . . . . . . . . . . . . . . . . . . . . . . . . . .
Sichtbarkeitstest einer Methode . . . . . . . . . . . . . . . . . . . . .
Privates Java-Feld . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Zugriff auf das private Java-Feld . . . . . . . . . . . . . . . . . . . .
Überladene Methoden . . . . . . . . . . . . . . . . . . . . . . . . . .
Problematik bei überladenen Methoden . . . . . . . . . . . . . . . .
Zahl wird als Integer erkannt . . . . . . . . . . . . . . . . . . . . .
Zugriff auf Membervariablen in Ruby . . . . . . . . . . . . . . . . . .
Äquivalente Zugriffe auf eine Java-Membervariable von Ruby aus . .
to_s und toString . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ausgabe einer String-Repräsentation eines Java-Objektes in Ruby . .
Auszug aus der Refacola Language Definition: java.language.refacola
Beispiel-Regel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Methodenumbenennung verbieten: einstellige Query . . . . . . . . .
Auszug aus der Refacola Language Definition: java.language.refacola
Methodenumbenennung verbieten: java.ruleset.refacola . . . . . . . .
Definition von Refaktorisierungen: java.refactoring.refacola . . . . . .
Java-Klasse mit Methode . . . . . . . . . . . . . . . . . . . . . . . .
Bindung an unterschiedliche Objekte mit Ruby und Java . . . . . . .
Bindung an unterschiedliche Objekte in Ruby . . . . . . . . . . . . .
Bindung an unterschiedliche Objekte in Ruby und Java . . . . . . .
Zeichenkette als Klassenname . . . . . . . . . . . . . . . . . . . . . .
Konsolenausgabe des Agenten instrumentation.jar . . . . . . . . .
65
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
20
22
22
23
23
23
24
24
25
26
26
27
28
29
29
29
29
30
30
30
32
32
32
33
35
36
37
38
38
38
40
41
43
45
48
59
Literaturverzeichnis
[ASU99] Alfred V. Aho, Ravi Sethi, Jeffrey D. Ullmann: Compilerbau Teil 1. 2. Auflage,
Oldenburg Wissenschaftsverlag, München, Wien, Oldenburg, 1999.
[Bec00] Kent Beck: Extreme Programming. Addison-Wesley Verlag, München, 2000.
[Dij68] Edsger W. Dijkstra: Go To Statement Considered Harmful. In: Communications
of the ACM, Vol. 11, No. 3, März, 1968, S. 147-148.
[DW02] Thomas Dudziak, Jan Wloka: Tool-Supported Discovery and Refactoring of
Structural Weaknesses in Code. Diploma Thesis of the Faculty of Computer Science,
Technical University of Berlin, 2002.
[ES92] Stefan Eicker, Thomas Schnieder: Reengineering. Arbeitsbericht Nr. 13, Institut
für Wirtschaftsinformatik, Wilhelms-Universität Münster, 1992.
[ENEB06] Pat Eyler, Charles Nutter, Thomas Enebo, Ola Bini: A New JRuby Interview
and More. Linux Journal, Pat Eyler’s blog, 29. September, 2006.
Von: http://www.linuxjournal.com/node/1000103, Abruf am 10.03.2012.
[Fow05] Martin Fowler. Mit Beiträgen von: Kent Beck, John Brant, William Opdyke, Don Roberts: Refactoring. Wie Sie das Design vorhandener Software verbessern.
Addison-Wesley Verlag, München, 2005.
[Gri91] William G. Griswold: Program Restructuring as an Aid to Software Maintenance. Ph D. Thesis, University of Washington, Department of Computer Science
and Engineering, 1991.
[JO93] Ralph E. Johnson, William F. Opdyke: Refactoring and Aggregation. In: Proceedings of the JSSST International Symposium on Object Technologies for Advanced
Software, 1993, S. 264-278.
[Mar09] Robert C. Martin: Clean Code. Refactoring, Patterns, Testen und Techniken für
sauberen Code. 1. Auflage, mitp-Verlag, Heidelberg, München, Landsberg, Frechen,
Hamburg, 2009.
[MO08] Hussein Morsy, Tanja Otto: Ruby on Rails 2. Das Entwickler-Handbuch. 1. Auflage, Galileo Press, Bonn, 2008.
[NBDDGW05] Oscar Nierstrasz, Alexandre Bergel, Marcus Denker, Stephane Ducasse, Markus Gälli, Roel Wuyts: On the Revival of Dynamic Languages. In: Thomas
Gschwind, Uwe Aßmann, Oscar Nierstrasz (Hrsg.): Software Composition. 4th International Workshop, SC 2005 Edinburgh, UK, 9. April, 2005, S. 1-13.
66
[New08] Ted Neward: Einführung in F#. Funktionale Programmierverfahren im .NET
Framework. In: MSDN Magazine, Microsoft Deutschland, Unterschleißheim, Mai,
2008.
[NSEBD11] Charles O. Nutter, Nick Sieger, Thomas Enebo, Ola Bini, Ian Dees: Using
JRuby. Bringing Ruby to Java. The Pragmatic Programmers LLC, The Pragmatic
Bookshelf, Raleigh, North Carolina; Dallas, Texas, 2011.
[Opd92] William F. Opdyke: Refactoring Object-Oriented Frameworks. University of
Illinois at Urbana-Champaign, 1992.
[Paw07] Monica Pawlan: JRuby and the Java Platform. Oracle, Sun Developer Network
(SDN), 2007.
Von: http://java.sun.com/developer/technicalArticles/scripting/jruby/,
Abruf am 10.06.2011.
[Pie01] Matt Pietrek: The .NET Profiling API and the DNProfiler Tool. In: MSDN
Magazine, Microsoft, San Francisco, Dezember, 2001.
[RBJ97] Don Roberts, John Brant, Ralph E. Johnson: A Refactoring Tool for Smalltalk.
In: Theory and Practice of Object Systems, Vol. 3, Nr. 4, 1997, S. 253-263.
[RL04] Stefan Roock, Martin Lippert: Refactorings in großen Softwareprojekten.
1. Auflage, dpunkt.verlag, Heidelberg, 2004.
[Rob99] Donald Bradley Roberts: Practical Analysis for Refactoring. Ph D., University
of Illinois at Urbana-Champaign, 1999.
[SDSTT10] Max Schäfer, Julian Dolby, Manu Sridharan, Emina Torlak, Frank Tip: Correct Refactoring of Concurrent Java Code. In: ECOOP’10 Proceedings of the 24th
European conference on Object-oriented programming, Springer-Verlag Berlin, Heidelberg, 2010, S. 225-249.
[SKP11] Friedrich Steimann, Christian Kollee, Jens von Pilgrim: A Refactoring Constraint Language and its Application to Eiffel. In: ECOOP 2011, Lehrgebiet Programmiersysteme, FernUniversität in Hagen, 2011. S. 255-280.
[Sli05] Stefan Slinger: Code Smell Detection in Eclipse. Thesis Report, Delft University
of Technology, März, 2005.
[SM01] Bruce Stewart, Yukihiro Matsumoto: An Interview with the Creator of Ruby.
Interview, linuxdevcenter, O’REILLY, 29. November, 2001
Von: http://linuxdevcenter.com/pub/a/linux/2001/11/29/ruby.html, Abruf
am 10.03.2012.
[SRK07] Daniel Speicher, Tobias Rho, Günter Kniesel: JTransformer – Eine logikbasierte
Infrastruktur zur Codeanalyse. WSR’07 - 9. Workshop Software-Reengineering der GIFachgruppe Software-Reengineering, Bad Honnef, 2.-4. Mai, 2007.
67
[ST10] Friedrich Steimann, Andreas Thies: From behaviour preservation to behaviour
modification: Constraint-based mutant generation. In: ICSE ’10 Proceedings of the
32nd ACM/IEEE International Conference of Software Engineering – Volume 1, 2010,
S. 425-434.
[Ste10] Friedrich Steimann: Korrekte Refaktorisierungen: Der Bau von Refaktorisierungswerkzeugen als eigenständige Disziplin. In: OBJEKTspektrum, SIGS DATACOM GmbH, Troisdorf, Nr. 4, 2010, S. 24-29.
[TFKEBS09] Frank Tip, Robert M. Fuhrer, Adam Kiezun, Michael D. Ernst, Ittai Balaban, Bjorn De Sutter: Refactoring using Type Constraints. IBM T.J. Watson Research
Center technical report RC24804, Hawthorne, NY, 2009.
[TKB03] Frank Tip, Adam Kiezun, Dirk Bäumer: Refactoring for generalization using
type constraints. In: Proceedings of the 18th annual ACM SIGPLAN conference on
Object-oriented programing, systems, languages, and applications (OOPSLA ’03),
New York, 2003, S. 13-26.
[TR10] Andreas Thies, Christian Roth: Recommending Rename Refactorings. In: 2nd
International Workshop on Recommendation Systems for Software Engineering (RSSE), 2010, S. 1-5.
68
Herunterladen