Sprachübergreifendes Refaktorisieren zwischen Ruby und Java - FernUniversität in Hagen Fakultät für Mathematik und Informatik Lehrgebiet ...
←
→
Transkription von Seiteninhalten
Wenn Ihr Browser die Seite nicht korrekt rendert, bitte, lesen Sie den Inhalt der Seite unten
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-Quell- texten unter Beibehaltung des beobachtbaren Verhaltens. Refaktorisierungen sind Teil der Implementierungsphase. Werkzeuge sollen helfen, die Entwickler und Entwicklerin- nen in der Komplexität des Refaktorisierungsvorgangs zu unterstützen. Bisher exis- tieren entwicklungsumgebungs- und programmiersprachenabhängige Refaktorisierungs- werkzeuge. 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 dynami- schen Programmiersprachen aufgezeigt und programmiertechnisch konkretisiert werden.
Inhaltsverzeichnis Erklärung 2 Zusammenfassung 3 1 Einleitung 6 1.1 Refaktorisierung – ein in der Softwareentwicklung integrierter Prozess . . 7 1.2 Werkzeuge für den Refaktorisierungsprozess . . . . . . . . . . . . . . . . . 9 1.2.1 Refaktorisierungswerkzeuge aus historischem Blickwinkel . . . . . 10 1.2.2 Refaktorisierungswerkzeuge als eigenes Fachgebiet . . . . . . . . . 12 1.3 Flexibilität durch eine programmiersprachen-übergreifende Entwicklung . 13 1.3.1 Vorteile der Interaktion mit dynamischen Programmiersprachen . . 14 1.3.2 Java Virtual Machine als Basis mehrerer Programmiersprachen . . 15 1.4 Sprachübergreifende Refaktorisierungswerkzeuge . . . . . . . . . . . . . . 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 21 3.1 Einordnung der Programmiersprache Ruby . . . . . . . . . . . . . . . . . 21 3.1.1 Die Ideale des Ruby-Erfinders . . . . . . . . . . . . . . . . . . . . . 21 3.1.2 Merkmale der Programmiersprache Ruby . . . . . . . . . . . . . . 22 3.1.3 Dynamische Aspekte der Programmiersprache Ruby . . . . . . . . 25 3.2 Methodenaufrufe zwischen Ruby und Java . . . . . . . . . . . . . . . . . . 27 3.2.1 Methodenaufruf von Java nach Ruby . . . . . . . . . . . . . . . . . 27 3.2.2 Methodenaufruf von Ruby nach Java . . . . . . . . . . . . . . . . . 28 3.3 Konvertierungsregeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 3.3.1 Konvertierungsregeln für Sichtbarkeiten . . . . . . . . . . . . . . . 28 3.3.2 Regeln für die Abbildung von Datentypen . . . . . . . . . . . . . . 29 3.4 Bedingungen für eine Refaktorisierung . . . . . . . . . . . . . . . . . . . . 30 3.4.1 Sichtbarkeiten sind nicht relevant . . . . . . . . . . . . . . . . . . . 31 3.4.2 Getter und Setter . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 3.4.3 to_s . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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.1.3 Definition von Refaktorisierungen . . . . . . . . . . . . . . . . . . . 37 4.2 Definition eines Verbots für das Refaktorisieren sprachübergreifender Me- thodenaufrufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 5 Codeanalyse als Voraussetzung für Refaktorisierung 40 5.1 Statische Codeanalyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 5.1.1 Exkurs – Mehrdeutigkeit in Ruby-Quelltexten . . . . . . . . . . . . 42 5.1.2 Mehrdeutigkeit bei Sprachübergriffen zwischen Ruby und Java . . 45 5.1.3 Endgültiges Versagen der Typanalyse bei dynamischen Klassenna- men . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 5.1.4 Vorläufiger Ablauf einer statischen Codeanalyse . . . . . . . . . . . 48 5.2 Test-Suite zum Sammeln von Informationen für eine Refaktorisierung . . 49 5.2.1 Test-Suite mit modifiziertem JRuby-Interpreter . . . . . . . . . . . 51 5.2.2 Test-Suite mit Instrumentierung . . . . . . . . . . . . . . . . . . . 52 5.3 Ausblick auf eine sinnvollen Verbindung der unterschiedlichen Ansätze . . 52 6 Beschreibung der Software 54 6.1 Das Plugin AST4CongenJRubyView . . . . . . . . . . . . . . . . . . . . . 54 6.2 XRefact . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 6.2.1 Modifizierter JRuby-Interpreter . . . . . . . . . . . . . . . . . . . . 57 6.2.2 Modifiziertes Refacola-Framework . . . . . . . . . . . . . . . . . . 58 6.2.3 Instrumentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 A Auflistung, Installations- und Bedienungsanleitung der beigelegten Software 60 A.1 Beigelegte Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 A.2 Installation und Bedienung . . . . . . . . . . . . . . . . . . . . . . . . . . 60 A.2.1 XRefact . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 A.2.2 Plugin AST4CongenJRubyView . . . . . . . . . . . . . . . . . . . 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 Refaktorisierun- gen für programmiersprachenübergreifende Softwareprojekte erfolgen können. Der Fokus wird auf solche Stellen gelegt, an denen die unterschiedlichen Programmiersprachen mit- einander interagieren. Jedoch muss deutlich unterschieden werden zwischen der Zusammenarbeit von aus- schließ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 dyna- mische Sprachen. Beim Refaktorisieren werden Informationen genutzt, die in statisch typgeprüften Spra- chen 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 Pro- jekte, in denen statisch typisierte und dynamische Programmiersprachen in einer Soft- ware genutzt werden. In der Einleitung wird zuerst skizziert, zu welchem Zeitpunkt Refaktorisierungen ein- setzen sollen. Hier wird der Refaktorisierungsprozess auf startbare Programme mit be- obachtbarem Verhalten eingegrenzt. Weiter werden exemplarisch Refaktorisierungswerk- zeuge historisch verfolgt, um Ideen und Ansätze, aber auch existierende Schwierigkeiten solcher Werkzeuge in die weitere Diskussion mit einbeziehen zu können. Zur Konkretisie- rung 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 ver- wendeten 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äng- lich 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 Entwickle- rinnen 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 Softwareentwick- lung zusammengefasst werden, integrieren von vornherein den Prozess der Refaktorisie- rung in den Ablauf der Softwareerstellung. Im agilen Ansatz wird es sogar als Ziel for- muliert, 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 Quell- codes 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. Refaktori- sierung 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 Implemen- tierung kombiniert. Um den Begriff Refaktorisierung inhaltlich zu konkretisieren, soll noch einmal Fow- ler 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 aufge- griffen als eine Änderung an der internen Struktur zur besseren Verständlichkeit (vgl. [Fow05, S. 41]). Erweiternd fügt Fowler dem Begriff eine Aktion hinzu: „Refaktorisie- ren(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 Re- faktorisierungsprozess 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 angesproche- nen Problem, dass ein Softwaredesign vorher zwar geplant wird, im Zuge der Imple- mentierung 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 Soft- ware 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 Refak- torisieren 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 kontinuierli- ches 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: Re- factoring 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 geworde- nes – Redesign des Gesamtsystems verhindern. Sie fordern, dass eine Refaktorisierung der Software in den Entwicklungsprozess mit eingebaut wird. Bei auftretenden Schwach- stellen 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 Mo- dulen2 und Verhalten (Behavior Driven Development3 ) gebündelt wird. Damit entfällt jegliche Idee einer eigenständigen „Phase der Refaktorisierung“, die zusätzlich Zeit for- dern 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 Hand- lungsablauf 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 we- nig unter den hier verwendeten Begriff von Refaktorisierung wie Architekturänderun- gen. Grenzfälle, beispielsweise eine Änderung von Quellcode, ohne dass ein Programm lauffähig ist, werden durch die klare Definition herausgefiltert, dass das Verhalten be- obachtbar sein muss. Strukturänderungen am Ende der Implementierung werden durch die vorherige Aussage eingeschlossen. Refaktorisierung impliziert nicht zwingend das formulierte Ziel einer Strukturverbes- serung. Nur die Programmierenden selbst können entscheiden, welche Umstrukturierun- gen 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 auto- matisierte 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 da- mit mühelos von den Programmierenden nachverfolgbar sind. Denn den Überblick über Quellcode zu behalten, der auf mehrere Dateien in unterschiedlichen Ordnern bzw. Pa- keten 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 skizzier- ten 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 Exemplarisch http://www.junit.org, http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/ 3 Exemplarisch http://cukes.info 9
Änderung vollständig übernommen. Die im Folgenden exemplarisch herausgegriffenen frühen Werkzeuge für eine Ände- rung 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 Ver- lauf 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 Entwick- lung heutiger Werkzeuge. Denn die zuvor skizzierten Probleme der Fehleranfälligkeit und natürlich auch des immensen Zeitaufwandes durch manuelles Refaktorisieren ma- chen 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 Refaktorisierungswerk- zeuge gesucht wird. 1.2.1 Refaktorisierungswerkzeuge aus historischem Blickwinkel Nach einer Abhandlung über Strukturierungswerkzeuge für Softwaresysteme von Tho- mas 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 ein- gegrenzten Begriff der Refaktorisierung. Ob damalige Restrukturierungen im Zuge der Diskussion um GOTO jedoch schon als ein Anfang von Refaktorisierung verstanden wer- den 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 wer- den 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 resul- ting flow diagram cannot be expected to be more transparent than the original one.“ [Dij68] Jedoch soll im Hinblick auf eine automatisierte Refaktorisierung die Aufmerksam- keit darauf gelenkt werden, dass zu jener Zeit im Zuge der Auseinandersetzung mit Restrukturierungsprozessen erste Werkzeuge entwickelt wurden, die eine automatisier- te 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 Pro- gramm in Prozedurblöcke und ersetzt vorwärtsgerichtete Goto-Anweisungen durch If- then-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 Lauf- zeit 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 objektori- entierten Softwaresystemen weiterverfolgt. Für imperative Programmiersprachen untersuchte William G. Griswold das Restruk- turieren ohne Verhaltensänderung. Er verwendete die semi-funktionale Programmier- sprache 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 ei- nes 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 schu- fen 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 Spra- che 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 Ei- genschaften, 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 da- mit auf einem anderen Konzept als die eingangs skizzierte Idee von Refaktorisierungen, in denen die Quelltexte verändert werden, ohne dass das jeweilige Programm ausge- 4 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 Ent- wicklungsumgebung (IDE) und Programmiersprache. Für sprachübergreifende Werkzeu- ge muss jedoch nach einer allgemeinen Basis gesucht werden. Interessant werden hier exemplarisch Entwicklungsumgebungen wie Eclipse, Netbeans oder RubyMine,8 in de- nen 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 Re- faktorisierungswerkzeugen als eigenständige Disziplin aus. Denn verglichen mit dem Bau von Compilern fehlen ihm anerkannte Ansätze, die identifizierten Refaktorisierungspro- bleme zu lösen. Er bezieht sich auf in Entwicklungsumgebungen integrierte Werkzeuge, die trotz hohem Qualitätsstandard dem Praxistest nicht standhalten würden: „Vollstän- dige und korrekte Spezifikationen von Refaktorisierungen sind also nur im Rahmen der Entwicklung von Refaktorisierungswerkzeugen sinnvoll. Leider sind auch die heute ver- fügbaren Refaktorisierungswerkzeuge alles andere als korrekt [...].“ [Ste10, S. 24] Zusammengefasst schätzt Steimann existierende Werkzeuge als bisher nicht ausrei- chend 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 exis- tierenden Leitfäden und Muster direkt in Refaktorisierungsprogrammen umzusetzen, beinhaltet für Steimann die Schwierigkeit, sich in zu vielen Fallunterscheidungen zu ver- lieren. 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 Refaktorisie- rung getrennt zu formulieren und anschließend mit einigem Aufwand in den Kontext der jeweiligen Entwicklungsumgebung einzubetten, wäre es hilfreich, wenn Refaktorisie- rungswerkzeuge nach dem Baukastenprinzip aus erprobten Komponenten zusammenge- 7 Eine Liste gängiger Refaktorisierungswerkzeuge im Anhang. 8 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 constraint- basierte 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. Sprach- spezifisch müssen die Programmelemente mit ihren Eigenschaften und Wertebereichen bestimmt werden, einschließlich der Beziehungen, die sich hieraus ergeben, und den Cons- traintregeln, 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 Refaktori- sierungen 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 Stei- mann, 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 Konzep- ten 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 herange- zogen werden. Auf der .NET Plattform laufen genauso unterschiedliche Programmier- sprachen, beispielsweise die zu den objektorientierten Programmiersprachen zählende Sprache C# oder die funktionale Programmiersprache F# (an OCaml angelehnt). In ei- nem 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 Program- miersprachen 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 ver- folgt werden. Im Hinblick auf den eingangs festgelegten Schwerpunkt der Arbeit, dem Zu- sammenspiel 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 zu- kü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 Programmier- sprachen 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 Anwen- dungen mit dynamischen Anforderungen: „Inherently static languages will always pose an obstacle to the effective realization of real applications with essentially dynamic re- quirements.“ [NBDDGW05, S. 1] Sie schließen daraus, dass dringend eine Forschung nach Programmiersprachen not- wendig 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 umrisse- ner) dynamischer Konzepte11 unkompliziert unterstützt, gilt die Sprache als dynamische Programmiersprache. Unkompliziert deswegen, weil sich eine dynamische Programmier- sprache nicht unbedingt dadurch abgrenzt, dass eine dagegen stehende, nicht-dynamische Sprache eine der im Folgenden skizzierten Techniken nicht beherrscht, sondern die Ab- grenzung 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 dyna- mische 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 Pro- grammiersprache ausgegangen werden, indem gefragt wird, wo solche Programmierspra- chen heute typischerweise eingesetzt werden. Damit lassen sich eine Reihe von Program- miersprachen einordnen nach ihrer Möglichkeit, elegante Lösungen für Probleme zu bie- ten, die bei statisch getypten Sprachen nur umständlich gelöst werden können. Beispiele wären Verbindungen zwischen Schnittstellen (Glue Code), Entwicklung von Benutzero- berflä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 Kommuni- kation 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 lie- genden Maschinen-Architektur ermöglicht die Entwicklung und den Einsatz von Kompo- nenten, 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 Einsatzbe- reiche nicht flexibel genug sei.13 Darüber hinaus gilt Java verglichen mit Sprachen wie Ruby, Python, Lisp oder Small- talk 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 Groovy- Klassen 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-Dynamische- Programmierung-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ön- nen 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 Multipara- digmen-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) her- angezogen. 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 Programmierparadig- men 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 langua- ges, such as easy development of large programs, rapid prototyping, almost-real-time development, and compact code. Ruby is a reflective, dynamic, and interpreted object- oriented 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 hin- aus 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 Spra- chen 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 16 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
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 Varia- blen typenlos (sozusagen void). Dementsprechend gibt es keine Type-Casts und keine typedefs.“21 Das macht Ruby als dynamische Sprache flexibel, eröffnet jedoch ein komple- xes 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 Projek- te, 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 diffe- rences 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 ver- langte 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 ver- sehen, 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 ablau- fen können. 21 http://www.ruby-lang.org/de/documentation/ruby-from-other-languages/to-ruby-from-c- and-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 Weite- ren vorgestellte Dynamic Languages Toolkit (DLTK)24 . Das Eclipse Framework wird als Open-Source-Umgebung für viele Projekte genutzt. Es unterstützt gängige Program- miersprachen.25 Im Folgenden wird als Grundlage eine Möglichkeit der Zusammenarbeit von unter- schiedlichen Sprachen im Eclipse-Framework beschrieben. Danach wird die Sprache Ru- by 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 vorge- stellt und exemplarisch eine Regel für einen sprachübergreifenden Methodenaufruf ent- wickelt. 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 http://www.eclipse.org/dltk/ 25 Gewählt wurde die Umgebung nicht zuletzt, weil u. a. das aktuelle Projekt: Ruboto (JRuby für An- droid) 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 Pro- grammiersprachen in einem einzigen Projekt vereinen zu können. Dies ist in der für diese Arbeit genutzten Entwicklungsumgebung Eclipse aktuell noch nicht möglich, je- doch in Planung. Im Folgenden wird die Unterstützung von Ruby (hier in der Java- Implementierung JRuby) in Eclipse skizziert und der Umgang mit den existierenden Schwierigkeiten der Nutzung zweier unterschiedlicher Projekte (Java und Ruby) aufge- zeigt. 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 exis- tieren 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 um- zusetzen. 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 Spra- chen 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-dynamische- Sprachen-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 require " java " 2 $CLASSPATH
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 Unzufrieden- heit ü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 Ob- jektorientierung 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 Program- mierende 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 Kon- zept „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]). 2 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. 21
werden. Darüber hinaus wurde Ruby als eine Sprache entworfen, die auch für Nicht- Programmierende 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 Pro- grammiersprache (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 Entwi- ckelnden 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 Ruby- Sprachdesign 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/ 8 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. 22
Sie können auch lesen