Extract Closure für C# - WS 2013/2014 - Fakultät für Informatik und Mathematik Lehrgebiet Programmiersysteme Masterarbeit im Studiengang ...

Die Seite wird erstellt Hanna Seidel
 
WEITER LESEN
Extract Closure für C# - WS 2013/2014 - Fakultät für Informatik und Mathematik Lehrgebiet Programmiersysteme Masterarbeit im Studiengang ...
Fakultät für Informatik und Mathematik
   Lehrgebiet Programmiersysteme

             Masterarbeit
           im Studiengang
        Praktische Informatik

             eingereicht von
            Alexander Ennen
        Matrikelnummer : 8548226

              Betreut durch
       Prof. Dr. Friedrich Steimann

  Extract Closure für C#

            WS 2013/2014
Extract Closure für C# - WS 2013/2014 - Fakultät für Informatik und Mathematik Lehrgebiet Programmiersysteme Masterarbeit im Studiengang ...
Erklärung

Hiermit erkläre ich, dass ich die vorliegende Arbeit selbstständig verfasst, noch nicht ander-
weitig für Prüfungszwecke vorgelegt, keine anderen als die angegebenen Quellen oder Hilfs-
mittel verwendet, sowie wörtliche und inhaltliche Zitate als solche gekennzeichnet habe.

Hilpoltstein, den 10. März 2014

Alexander Ennen

                                              2
Abstract

Deutsch

In der objektorientierten Programmierung (OOP) werden oftmals Funktionen in Form von
Parametern anderen Funktionen übergeben. Diese Funktionen werden zumeist in Form von
anonymen Funktionen realisiert. Dies hat den Vorteil, dass die benötigten Anweisungen als
Inlineanweisung formuliert werden können und somit keine Instanzmethode im eigentlichen
Sinne benötigt wird. Gerade durch die Integration von Konstrukten aus der funktionalen Pro-
grammierung wird diese Technik in modernen Frameworks immer häufiger eingesetzt. Besitzt
die entstandene anonyme Funktion dabei Referenzen auf ihren Erstellungskontext, so spricht
man von einem Closure. Im Rahmen dieser Arbeit entstand ein Werkzeug, welches die Erzeu-
gung dieser Closures ermöglicht. Es extrahiert dabei vom Anwender selektierte Anweisungen
unter Berücksichtigung einer Typkonfiguration in ein eigenständiges Closure.

Englisch

In todays object-oriented programming, functions are often given to other functions as parame-
ters. These functions are most often implemented as anonymous functions. This is beneficial
because these functions can be declared inline without the need to create an actual instance
method. Especially in the course of the integration of functional programming structures to
object-oriented programming this feature is used more and more frequently. If such a function
keeps references to the scope it was first declared in, it is called a Closure. During this work a
refactoring tool was developed which performs the extraction of user selected statements and
creates the responding Closure out of it while considering a specific type configuration.
Inhaltsverzeichnis

Inhaltsverzeichnis                                                                                                                  4

Abbildungsverzeichnis                                                                                                               7

Quellcodeverzeichnis                                                                                                                8

Tabellenverzeichnis                                                                                                                10

1. Einführung                                                                                                                      11
   1.1. Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                         11
   1.2. Problemstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                          13
   1.3. Aufbau der Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                          13

2. Grundlagen                                                                                                                      17
   2.1. .NET Framework . . . . . . . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   17
   2.2. Delegate . . . . . . . . . . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   19
   2.3. Closures . . . . . . . . . . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   21
        2.3.1. Anonyme Funktionen . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   21
        2.3.2. Lambda-Ausdrücke . . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   23
        2.3.3. Erfasste Variablen . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   25
   2.4. Microsoft Roslyn . . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   26
        2.4.1. Einführung . . . . . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   26
        2.4.2. Syntaxbaum . . . . . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   27
        2.4.3. Manipulation des Syntax Trees . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   31
   2.5. Microsoft Visual Studio . . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   31
        2.5.1. Erweiterbarkeit von Visual Studio       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   32

3. Lösungsansatz                                                                                                                   34
   3.1. Rahmenbedingungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                              34

                                               4
Inhaltsverzeichnis

   3.2. Grundsätzlicher Ablauf der Refaktorisierung .                  .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   35
   3.3. Vorbedingungen . . . . . . . . . . . . . . . .                 .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   36
   3.4. Konfigurationsmöglichkeiten . . . . . . . . .                  .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   38
   3.5. Vorgehensweise . . . . . . . . . . . . . . . .                 .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   40
        3.5.1. Analyse des Quellcodes . . . . . . .                    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   40
        3.5.2. Erstellung des Closures . . . . . . . .                 .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   41
   3.6. Manipulation des Syntaxbaumes . . . . . . .                    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   44
   3.7. Zusatzbeobachtungen . . . . . . . . . . . . .                  .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   47
        3.7.1. Namenskonflikte . . . . . . . . . . .                   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   48
        3.7.2. Erfasste Schleifenvariablen . . . . . .                 .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   49

4. Implementierung                                                                                                                         51
   4.1. Struktur . . . . . . . . . . . . . . . . . .           .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   51
   4.2. Integration in Visual Studio . . . . . . . .           .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   53
   4.3. Realisierung von Extract Closure . . . . .             .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   55
        4.3.1. Überprüfung der Vorbedingungen                  .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   56
        4.3.2. Konfiguration . . . . . . . . . . .             .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   57
        4.3.3. Erzeugung des Delegattyps . . . .               .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   58
        4.3.4. Manipulation der Anweisungen .                  .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   60

5. Ergebnisse                                                                                                                              63
   5.1. Evaluation durch Stichprobe . . . . . . . . . . . . . . . . . . . . . . . . . .                                                    64
        5.1.1. Vorgehensweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                                    65
        5.1.2. Ergebnisse der Stichprobe . . . . . . . . . . . . . . . . . . . . . . .                                                     66
   5.2. Grenzfälle der Extraktion . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                                  67
        5.2.1. Erfassen der Schleifenvariablen . . . . . . . . . . . . . . . . . . . .                                                     67
        5.2.2. Einfluss der Parametrisierbarkeit auf den Erhalt von Variablenbindungen                                                     68

6. Diskussion                                                                                                                              72
   6.1. Bewertung der Ergebnisse . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   72
   6.2. Vergleich mit ähnlichen Arbeiten   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   73
        6.2.1. Lambdaficator . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   73
        6.2.2. ReLooper . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   75
   6.3. Schlussbetrachtung . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   78

7. Zusammenfassung                                                                                                                         79

                                               5
Inhaltsverzeichnis

8. Ausblick                                                                                                                                            81

Literatur                                                                                                                                              83

A. Installation des Plugins                                                                                                                            85
   A.1. Voraussetzungen . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   85
   A.2. Installation . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   85
   A.3. Ausführung des Plugins     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   86
   A.4. Inhalt der CD . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   87

                                                           6
Abbildungsverzeichnis

 2.1. Darstellung der Klasse Person als Syntaxbaum . . . . . . . . . . . . . . . .      30

 3.1. Darstellung der ursprünglichen return-Anweisung . . . . . . . . . . . . . . .     46
 3.2. Resultierende lokale Variablendeklaration . . . . . . . . . . . . . . . . . . .   47

 4.1. Struktur der in Extract Closure verwendeten Elemente . . . . . . . . . . . .      52
 4.2. Benutzerdialog mit beispielhafter Konfiguration . . . . . . . . . . . . . . . .   57

 A.1. Das korrekt installierte Package wird im Manager angezeigt . . . . . . . . .      86
 A.2. Die ausführbare Refaktorisierung im Kontextmenü . . . . . . . . . . . . . .       87

                                           7
Listings

 2.1.   Instanziierung mit benannter, nicht statischer Methode . . . . . . . . . . . .               20
 2.2.   generischer Delegattyp Func . . . . . . . . . . . . . . . . . . . . . . . . . .              21
 2.3.   Instanziierung mit anonymer Methode . . . . . . . . . . . . . . . . . . . . .                22
 2.4.   Ausschnitt aus dem erzeugten IL Code von Listing 2.3 . . . . . . . . . . . .                 23
 2.5.   Instanziierung einer anonymen Funktion mit Hilfe eines Anweisungslambdas                     24
 2.6.   Instanziierung einer anonymen Funktion mittels implizierter Typisierung . . .                24
 2.7.   Einfaches Beispiel einer erfassten Variablen innerhalb einer anonymen Methode                25
 2.8.   Einfache Implementierung einer Klasse Person zur Visualisierung des Syntax-
        baumes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .             29

 3.1.   Beispielhafte, nicht eindeutige Ausgangssituation für extract Closure    .   .   .   .   .   38
 3.2.   Mögliche Closures aus 3.1 mittels generischer Delegattypen . . . .       .   .   .   .   .   39
 3.3.   equivalente delegate, explizit und generisch . . . . . . . . . . . . .   .   .   .   .   .   42
 3.4.   Umwandeln von expliziten zu generischen Delegattypen . . . . . .         .   .   .   .   .   42
 3.5.   Simple return-Anweisung . . . . . . . . . . . . . . . . . . . . . . .    .   .   .   .   .   45
 3.6.   Anpassung der ursprünglichen return-Anweisung . . . . . . . . . .        .   .   .   .   .   45
 3.7.   Ursprüngliche Bindung an die globale Variable x . . . . . . . . . .      .   .   .   .   .   48
 3.8.   Änderung der Variablenbindung innerhalb der anonymen Funktion .          .   .   .   .   .   49

 4.1.   Implementierung der Statusabfrage im Visual Studio . . . . . . . . . .           .   .   .   54
 4.2.   Überprüfung der Anweisungen auf Sprunganweisungen . . . . . . . . .              .   .   .   57
 4.3.   Erzeugung einzelner Parameter für explizite Delegattypen mittels Roslyn          .   .   .   60
 4.4.   Erzeugung des expliziten Delegattyps mittels Roslyn . . . . . . . . . .          .   .   .   60
 4.5.   Rückgabe des neu erstellten BlockSyntax . . . . . . . . . . . . . . . . .        .   .   .   62

 5.1. Ausgangssituation zur Untersuchung der Verhaltensänderung bei der Erfas-
      sung von Schleifenvariablen . . . . . . . . . . . . . . . . . . . . . . . . . .                68

                                             8
Listings

5.2. Ausgangssituation zur Untersuchung der Aufrechterhaltung von
     Variablenbindungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                        69
5.3. Erstellte anonyme Funktion mit korrekten Bindungen . . . . . . . . . . . . .                            70
5.4. Erstellte anonyme Funktion mit manipulierten Bindungen . . . . . . . . . . .                            70

6.1.   Ausgangszustand For-Schleife . . . . . . . . . . . . . . . .      .   .   .   .   .   .   .   .   .   74
6.2.   Erzeugte Lambda-Anweisung mit Hilfe des Lambdaficators            .   .   .   .   .   .   .   .   .   74
6.3.   Ursprüngliche, nicht parallelisierte Ausführung . . . . . . .     .   .   .   .   .   .   .   .   .   76
6.4.   Refaktorisierte, parallelisierte Ausführung . . . . . . . . . .   .   .   .   .   .   .   .   .   .   76
6.5.   Mögliche Anwendung zur Parallelisierung . . . . . . . . . .       .   .   .   .   .   .   .   .   .   77

                                            9
Tabellenverzeichnis

 3.1. Vorbelegung der Parameterliste . . . . . . . . . . . . . . . . . . . . . . . . .   43

 5.1. Überblick der Ergebnisse . . . . . . . . . . . . . . . . . . . . . . . . . . . .   66

 A.1. Überblick der Ergebnisse . . . . . . . . . . . . . . . . . . . . . . . . . . . .   87

                                           10
1. Einführung

1.1. Motivation

Durch den ständigen Änderungsprozess dem eine Softwarekomponente im Laufe ihres Le-
benszyklus unterliegt, wandelt sich ihre Architektur und ihre Strukturierung. Viele dieser
Änderungen fügen neue Funktionalität hinzu oder passen bereits bestehende an neue An-
forderungen an. Den oftmals damit einhergehenden Verfall der Software bezeichnet man als
Softwareerosion oder Softwarezerfall. Dabei handelt es sich in den seltensten Fällen um tat-
sächliche Programmierfehler innerhalb des Quellcodes. Vielmehr thematisiert der Begriff den
Verfall der Qualität des Quellcodes hinsichtlich seiner Strukturierung. Ein heutzutage als be-
währt geltendes Mittel gegen das Fortschreiten dieser Degeneration stellen Refaktorisierungen
dar.

Bereits 1990 führten Ralph Johnson und William Obdyke (Obdyke und Johnson, 1990) den
Begriff Refactoring (engl., von hier ab soll die deutsche Bezeichnung Refaktorisierung ver-
wendet werden) ein. Unter einer Refaktorisierung versteht man die Änderung von Quellcode,
ohne dessen beobachtetes Verhalten zu verändern (Fowler, 1999). Die Änderung dient somit
ausschließlich der Verbesserung von Lesbarkeit, Wartbarkeit und Erweiterbarkeit und fügt (im
Normalfall) keinerlei neue Funktionalität hinzu und ändert auch keine bestehende ab. Durch
die Verbesserung der Strukturierung des Quellcodes wird dem Softwarezerfall aktiv entgegen-
gewirkt und die Arbeit mit dem Quellcode erleichtert. So können beispielsweise neue Funktio-
nalitäten einfacher und effizienter integriert werden oder ermöglicht durch erhöhte Lesbarkeit,
neue Mitarbeiter schneller in die bestehende Codebasis eingearbeitet werden.

Dabei handelt es sich bei vielen Refaktorisierungen um eine feste Abfolge von Tätigkeiten,
die einen vorhandenen Istzustand in einen gewünschten Sollzustand überführen. Die dabei
zu verrichtenden Aufgaben sind oftmals umfangreich und fehleranfällig. Deshalb ist für die

                                              11
1. Einführung

Durchführung von Refaktorisierungen grundsätzlich eine Werkzeugunterstützung erstrebens-
wert. Aus diesem Grund bieten die gängigen Entwicklungsumgebungen (wie z.B. Visual Stu-
dio oder Eclipse) und diversen Zusatzwerkzeuge (z.B. ReSharper 1 oder Whole tomato 2 )
bereits eine Vielzahl von Refaktorisierungen als integrierte Operationen an, um die Softwa-
reentwickler bei der Durchführung dieser qualitätserhaltenden Maßnahmen zu unterstützen.
Somit können viele anfallende Arbeitsschritte automatisiert durchgeführt werden, was Fehler-
quellen minimiert.

Ein weiteres Einsatzgebiet von Refaktorisierungen, welches oftmals weniger Aufmerksam-
keit erfährt, ist die unterstützte Verwendung neuer programmiersprachlicher Konstrukte. So
halten beispielsweise immer mehr Konstrukte funktionaler Programmiersprachen Einzug in
objektorientierte Sprachen (beispielsweise Lambda-Ausdrücke in .NET oder JAVA). Diese er-
höhen teilweise deutlich die Lesbarkeit oder verbessern die Erweiterbarkeit von Ausdrücken,
Anweisungen und Abfragen. Die Verwendung neuer Features in bestehenden Quellcode und
somit die Änderung bereits bestehender Funktionalität, erfordert jedoch ein genaues Verständ-
nis beider Varianten. Auch hier wäre eine automatisierte Unterstützung bei der Portierung des
deprecated Codes3 wünschenswert.

Die vorliegende Arbeit versucht dafür einen Grundstein zu legen. In vielen Fällen des Um-
stiegs, vor allem bei der Arbeit mit Auflistungen und dem in .NET eingeführten LINQ (Lan-
guage Integrated Query) (Microsoft, 2014), müssen die Anweisungen, welche innerhalb der
neuen Möglichkeiten genutzt werden sollen, in Methoden überführt werden. Oftmals müsste
für diese Methoden zusätzlich eine kapselnde Klasse erstellt werden, welche alle benötigten
Daten und Eigenschaften verwaltet. Eine zumeist elegantere und direktere Variante ist das
Erstellen einer anonymen Methode, welche zusätzlich Informationen aus ihrem Erstellungs-
kontext referenziert, um alle benötigten Informationen zu erhalten - einem sogenannten Clos-
ure. Die Refaktorisierung Extract Closure soll dabei den Schritt der Erstellung des Closures
übernehmen.

 1
   http://www.http://www.jetbrains.com/resharper/
 2
   http://www.wholetomato.com/
 3
   Als Deprecated Code werden veraltete oder nicht mehr erwünschte Konstrukte bezeichnet

                                                   12
1. Einführung

1.2. Problemstellung

Im Rahmen dieser Abschlussarbeit soll eine Refaktorisierung entstehen, welche es dem An-
wender ermöglicht aus selektierten Anweisungen ein Closure zu erzeugen. Um dies zu er-
reichen, muss zunächst eine Validierung des selektierten Quellcodes stattfinden, um sicher-
zustellen das dieser in ein Closure überführt werden kann. Danach müssen alle notwendigen
Änderungen im Quellcode bestimmt und durchgeführt werden. Da innerhalb der meisten se-
lektierten Anweisungen eine Vielzahl von Möglichkeiten besteht verschiedenste Closures zu
erzeugen, soll zudem ein Benutzerdialog entstehen, der es dem Anwender ermöglicht, einzel-
ne Komponenten des entstehenden Closures zu editieren bzw. zu konfigurieren.

1.3. Aufbau der Arbeit

In Kapitel 2 sollen zunächst alle für die Realisierung relevanten Voraussetzungen erläutert
und vorgestellt werden. Dies beinhaltet, gegeben durch die verwendeten Rahmenbedingungen,
eine kurze Einführung in das .NET Framework und eine kontextspezifische Definition des
Begriffes Closure in C# sowie der dafür notwendigen Begrifflichkeiten.

Als verwendete Werkzeuge sollen außerdem Microsofts Visual Studio und dessen Erweite-
rungsmöglichkeiten kurz erläutert werden. Das zur Durchführung der Refaktorisierung ver-
wendete Framework Roslyn wird ebenfalls kurz in seiner grundlegenden Funktionalität und
Verwendungsweise vorgestellt.

Kapitel 3 stellt den zur Realisierung des Plugins verwendeten Lösungsansatz vor und stellt ei-
ne prinzipielle Erläuterung der Lösung dar. Hier sollen unter anderem für die Implementierung
notwendige Vorbedingungen beschrieben und erläutert werden. Zusätzlich soll der prinzipielle
Ablauf der Refaktorisierung skizziert werden und auf die Notwendigkeit der Konfigurierbar-
keit des Plugins eingegangen werden.

Das darauf folgende Kapitel 4 beschreibt die konkrete Implementierung, die innerhalb die-
ser Abschlussarbeit zur Lösung der Anforderungen erstellt wurde. Sie ist spezifisch für die
verwendeten Rahmenbedienungen und im Gegensatz zum vorhergehenden Kapitel nur inner-
halb dieser verwendbar. Es beinhaltet konkrete Realisierungen der Integration in Visual Stu-

                                             13
1. Einführung

dio sowie der Anwendung von Roslyn. Des Weiteren erläutert es die Implementierung des zur
Überführung des Quellcodes notwendigen Algorithmus.

Die Evaluation der erstellten Refaktorisierung wird in Kapitel 5 durchgeführt. Da sich die
benötigten Eingangsdaten nur bedingt für eine automatisierte Durchführung der Refaktori-
sierung eignen, wird sie in Form mehrerer Fallstudien durchgeführt. Dabei sollen vor allem
Grenzfälle untersucht werden, welche entweder aufgrund der Konfigurationsmöglichkeiten
oder aufgrund von Uneindeutigkeiten problematisch sind.

Innerhalb der Diskussion in Kapitel 6 soll das entstandene Plugin mit ähnlichen Ansätzen und
Implementierungen verglichen werden. Insbesondere die Arbeiten (Franklin u. a., 2013) und
(Gyori u. a., 2013) sollen innerhalb dieser diskutiert werden.

Abgeschlossen wird die Arbeit durch Kapiel 7 und Kapitel 8, welche eine kurze Zusammen-
fassung der Arbeit sowie einen Ausblick auf mögliche Einstiegsgedanken für eine Weiterfüh-
rung werfen sollen.

                                            14
Glossar

Anonyme Funktion

Der Begriff anonyme Funktion wird innerhalb dieser Arbeit als Überbegriff verwendet. Reali-
siert werden kann eine anonyme Funktion entweder durch eine anonyme Methode oder durch
einen Lambda-Ausdruck. Der Begriff wird immer dann verwendet, wenn beide Realisierungs-
möglichkeiten verwendet werden können oder es schlicht keine Rolle spielt.

Anwender

Als Anwender wird, innerhalb dieser Arbeit, der tatsächliche Bediener der entstandenen Re-
faktorisierung bezeichnet. Er wählt die zu extrahierenden Anweisungen aus, startet die Refak-
torisierung und konfiguriert diese.

Delegat

Der im Kontext von C# mehrfach überladene Begriff Delegat wird im Rahmen dieser Arbeit
mehrdeutig verwendet. Zum einen bezeichnet delegate das programmiersprachliche Schlüs-
selwort zur Erstellung von Referenztypen, welche Methoden kapseln. Zum anderen beschreibt
Delegat die tatsächliche Instanz eines Delegat-Objektes.

                                             15
1. Einführung

Delegattyp

Als Delegattyp wird der tatsächlich aus der Deklaration eines Delegates entstehende Typ be-
zeichnet. Er spezifiziert somit die Signatur der Funktion, welche von ihm gekapselt werden
kann. Jedes tatsächliche Delegat ist dabei vom Typ eines bestimmten Delegattypen.

Refaktorisieren

Als Refaktorisieren wird der Vorgang des Ausführens einer bestimmten Refaktorisierung oder
der Tätigkeit im Allgemeinen verstanden. Der Anwender einer Refaktorisierung soll jedoch
nicht als ”der Refaktorisierende” sondern schlicht als Anwender bezeichnet werden.

Refaktorisierung

Als Refaktorisierung soll einerseits die allgemeine Tätigkeit der Änderung von Quellcode oh-
ne ihr beobachtetes Verhalten zu manipulieren bezeichnet werden, andererseits eine tatsächli-
che, bestimmte Refaktorisierung wie beispielsweise Extract Closure.

Plugin

Als Plugin wird eine Erweiterung für ein bestehendes Softwareprodukt bezeichnet. Zumeist
unterliegen diese Plugins bestimmten Anforderungen denen sie gerecht werden müssen, um
in eine gewünschte Anwendung integriert werden zu können. Das ”entstandene Plugin” be-
zeichnet weiterhin die im Rahmen dieser Arbeit entstandene Erweiterung für Visual Studio im
Allgemeinen.

                                             16
2. Grundlagen

Das folgende Kapitel soll einen kurzen Überblick über die im Rahmen dieser Arbeit verwen-
deten Frameworks, Werkzeuge und Entwicklungsumgebungen geben. Es sollen vor allem die
Zusammenhänge der einzelnen Faktoren und deren Einfluss auf die erzeugte Lösung erläutert
werden.

2.1. .NET Framework

Das .NET Framework stellt eine aus dem Hause Microsoft stammende Programmierplatt-
form dar, welche zusammen mit Visual Studio.NET und der Programmiersprache C# erstmals
2002 veröffentlicht wurden. Die .NET Plattform selbst besteht dabei aus der Laufzeitumge-
bung Common Language Runtime (CLR) und der Klassenbibliothek Framework Class Library
(FCL).

Die Laufzeitumgebung CLR ist für die Ausführung von .NET Programmen zuständig. Sie
übersetzt die Assemblies in ausführbaren Maschinencode und übergibt diesen an das Betriebs-
system, welches diesen ausführt. Die Übersetzung in ausführbaren Maschinencode erfolgt da-
bei zur Laufzeit, man spricht deswegen von einem Just in time Compiler (JIT). Die CLR bein-
haltet zusätzlich eine Vielzahl von Grundlegender Funktionalitäten wie beispielsweise einer
automatischen Speicherbereinigung, Debugdiensten oder die Behandlung von Ausnahmen.

Die FCL ist eine objektorientierte Bibliothek, welche eine Vielzahl von Klassen für verschie-
denste Zwecke beinhaltet. Sie bietet unter anderem Basisfunktionalität für die Datenerfassung,
Datenbankanbindung oder die Entwicklung und Erstellung von Benutzungsschnittstellen.

Bei der Entwicklung von Programmen für das .NET Framework wird der erzeugte Quellcode

                                             17
2. Grundlagen

folglich nicht direkt in ausführbaren Maschinencode übersetzt. Vielmehr übersetzen die .NET
sprachabhängigen Compiler den Quellcode in eine sprachunabhängige Zwischensprache, die
Common Intermediate Language (CIL). Unter der Voraussetzung, das eine betreffende Pro-
grammiersprache CIL-Konformen Code erzeugen kann, ermöglicht diese Vorgehensweise den
Einsatz einer Vielzahl von Programmiersprachen zum Erstellen von .NET Komponenten. So
können unter anderem die Programmiersprachen Visual Basic, Smalltalk oder Ruby eingesetzt
werden um CIL Code zu erzeugen. Die am weitesten verbreitete .NET Sprache ist dabei C#.
Durch Verwendung der CIL wird es ermöglicht Programmbibliotheken verschiedener .NET
Programmiersprachen untereinander zu verwenden.

Ein weiterer Vorteil des Einsatzes der Zwischensprache ist die grundsätzlich mögliche Platt-
formunabhängigkeit. Durch den starken Fokus Microsofts auf hauseigene Betriebssysteme
wird dies allerdings vor allem durch die Projekte Mono1 und DotGNU2 verfolgt und vorange-
trieben.

Die Ursprungsidee des .NET Frameworks ist dabei die Realisierung der Common Language
Infrastructure (CLI), eines von Microsoft initiierten ISO/IEC/ECMA Standards. Inhalt des
Standards ist die Definition Sprach- und Plattform unabhängiger Anwendungsentwicklung
und -ausführung. Das .NET Framework ist dabei eine vollständige Implementierung dieses
Standards.

Im Rahmen dieser Arbeit wird die Programmiersprache C# in der von Microsoft 2012 her-
ausgegebenen Version 5.0 verwendet. Alle Ergebnisse und Implementierungen stützen sich
auf die Version 4.5 des .NET Frameworks. Ebenfalls sind alle in der Arbeit aufgeführten
Quellcodeausschnitte innerhalb dieser Rahmenbedingungen erzeugt. Zum Ausführen des bei-
gelegten Quellcodes wird Visual Studio 20123 sowie die CTP Version des Roslyn Frameworks
vom September 20124 benötigt.

 1
   http://www.mono-project.com
 2
   http://www.gnu.org/software/dotgnu
 3
   http://www.visualstudio.com/
 4
   http://msdn.microsoft.com/de-DE/roslyn

                                            18
2. Grundlagen

2.2. Delegate

Seit der Framework Version 1.1 aus dem Jahr 2003 bietet Microsoft Delegate als Teil des
Sprachumfanges von .NET an. Ein Delegat ist dabei ein Referenztyp, welcher eine Metho-
densignatur definiert. Diesem Referenztyp kann dann eine tatsächliche Methode zugewiesen
werden. Das Objekt vom Typ des Delegates kapselt somit eine Methode und ermöglicht es
grundsätzlich, die in der Methode enthaltenen Anweisungen beim Aufrufen des Delegates aus-
zuführen. Durch die Verwendung von Delegaten ist es unter anderem möglich Methoden (in
Form eines Delegates) als Rückgabewert von anderen Methoden zu erhalten, sie als Funktions-
parameter anderen Methoden zu übergeben oder sie an Variablen (vom Typ eines zulässigen
Delegattyps) zu binden. Man spricht davon, dass Methoden als First-Class-Objekt behandelt
werden können und somit First-Class-Funktionen darstellen. Delegate erlauben es außerdem,
Methodenaufrufe zur Laufzeit zu ändern und somit beispielsweise neue Funktionalität in be-
reits bestehende Klassen zu integrieren.

Im Gegensatz zu Methodensignaturen, beinhaltet die Signatur eines Delegattyps auch seinen
Rückgabewert. Somit ist darauf zu achten, dass bei der Zuweisung einer Methode an einen
Delegattypen neben Typ und Art ihrer Methodenparameter auch der Rückgabewert zulässig
und gültig ist.

Delegate in C# sind somit typsicher und unterscheiden sich deshalb stark von dem Prinzip der
Funktionszeiger, wie man diese z.B. aus der Programmiersprache C++ kennt. Seit der .NET
Framework Version 3.5 realisiert C# zudem Ko- und Kontravarianz für Methodensignaturen
und Delegattypen. Es ist somit möglich Methoden an ein Delegat zu binden deren Rückgabe-
wert Kindklasse des Delegat Rückgabetypes ist (Kovarianz), sowie Parameter zu übergeben
die Elternklasse des Delegat Parametertypes sind (Kontravarianz).

Erzeugt wird die Instanz eines Delegates dabei entweder durch die Zuweisung einer statischen
Methode oder unter Angabe des tatsächlichen Objektes (das Objekt auf dem die Methode
später aufgerufen wird), anhand einer Instanzmethode (siehe Listing 2.1).

                                            19
2. Grundlagen

                  Listing 2.1: Instanziierung mit benannter, nicht statischer Methode
 1   public class Converter
 2   {
 3     private delegate double ConvertIntToDouble(int parameter);
 4

 5       private double ConvertMethod(int parameter)
 6       {
 7         return System.Convert.ToDouble(parameter);
 8       }
 9

10       public double Convert(int parameter)
11       {
12         ConvertInToDouble converter = this.ConvertMethod;
13         return converter.Invoke(parameter);
14       }
15   }

     Im Listing 2.1 wird dem Delegattypen ConvertIntToDouble unter Verwendung des Delegates
     converter die Instanzmethode ConverterMethod zugewiesen. Das Ziel des Aufrufes ist dabei
     die erstellende Instanz selbst (this).

     Aufgerufen und somit ausgeführt werden kann die im Delegat gebundene Methode mittels
     der Invoke Funktion, die jedes .NET Delegat-Objekt zur Verfügung stellt. Diese nimmt die
     im Delegattypen festgelegten Parameter entgegen und liefert den spezifizierten Rückgabewert
     zurück. Als zusätzliche Vereinfachung ist es möglich auf Variablen des Delegattypen (in den
     Beispielen converter) die Funktion auszuführen, als ob das Objekt die Methode selbst wäre.
     Somit kann auf den expliziten Aufruf der Methode Invoke verzichtet werden.

     Generische Delegattypen

     Um zu verhindern, einer Klasse für jede gekapselte Methode (oder jeder gekapselten Me-
     thode mit eigener Signatur) einen eigenen Delegattypen hinzufügen zu müssen, stellt .NET
     verschiedene, generische Delegattypen zur Verfügung.

     Die beiden am häufigsten verwendeten Delegattypen sind dabei Action und Func. Das Acti-
     on-Delegat kapselt eine Methode, welche über bis zu 16 Parameter verfügt und keinen Rück-
     gabewert liefert. Es wird beispielsweise im Eventhandling der gängigen C# GUI Frameworks

                                                  20
2. Grundlagen

    verwendet.

    Das Func-Delegat kapselt eine Methode mit bis zu 16 Parametern welche außerdem einen
    Rückgabewert liefert. Es findet überwiegend als Parameter für anderer Methoden und in
    Microsofts LINQ Verwendung. Listing 2.2 zeigt Ausschnitte der Deklaration von Func.

                             Listing 2.2: generischer Delegattyp Func
1   TResult Func()
2   TResult Func (T arg)
3   {...}
4   TResult Func (T arg)

    2.3. Closures

    Unter einem Closure versteht man in der Informatik allgemein eine Methode, die Zugriff auf
    den Kontext hat, aus welchem heraus sie erstellt wurde. Im Falle von C# wird dies durch
    das Erfassen von Variablen in anonymen Funktionen realisiert. Eine anonyme Funktion wird
    dabei entweder durch eine anonyme Methode oder durch die Verwendung eines Lambda-
    Ausdruckes repräsentiert. Im Folgenden sollen die drei Begrifflichkeiten anonyme Methode,
    Lambda-Ausdrücke und erfasste Variable erklärt und damit verbundene Möglichkeiten erläu-
    tert werden.

    2.3.1. Anonyme Funktionen

    Unter einer anonymen Funktion versteht man in .NET eine Funktion ohne Namen. Sie wird
    nicht, wie bei Instanzmethoden üblich, innerhalb der umschließenden Klasse erstellt, sondern
    direkt an einen Delegattypen gebunden. Dies kann in Form eines benannten Delegates gesche-
    hen (Vergleich mit Listing 2.3) oder indem die Funktion als Parameter einer anderen Methode
    übergeben wird. Listing 2.3 zeigt die Zuweisung einer anonymen Methode an die Variable
    converter vom expliziten Delegattypen ConvertToDouble.

                                                21
2. Grundlagen

                          Listing 2.3: Instanziierung mit anonymer Methode
 1   public class Converter
 2   {
 3     private delegate double ConvertToDouble(int parameter);
 4

 5       public double Convert(int parameter)
 6       {
 7         ConvertToDouble converter = delegate(int arg)
 8         {
 9            return System.Convert.ToDouble(arg);
10         };
11          return converter(parameter);
12       }
13   }

     Anonyme Methoden bieten die Möglichkeit eine explizite Methode durch eine Inlineanwei-
     sung zu ersetzen. Dies ist besonders dann vorteilhaft, wenn der einzige Zweck dieser Methode
     die Bindung an das Delegat war. Grundsätzlich können anonyme Methoden überall dort ver-
     wendet werden wo ein konkreter Delegattyp gefordert ist. Sie können unter anderem direkt als
     Parameter einer anderen Methode übergeben werden, welche als Parameter einen speziellen
     Delegattypen erwartet.

     Für jede in der Klasse enthaltene anonyme Methode erstellt der Compiler beim Erstellen des
     IL Codes eine tatsächliche Methode innerhalb der Klasse. Dennoch kann diese Methode, im
     Gegenteil zu gewöhnlichen Instanzmethoden, nicht direkt aufgerufen werden. Dies wird ver-
     hindert indem für die automatisch generierten anonymen Methoden Namen vergeben werden,
     die nur innerhalb der IL gültig sind, jedoch nicht innerhalb der verwendeten Programmier-
     sprache. Ein Ausführen des an das Delegat gebundenen Funktionsblockes ist somit nur durch
     den Aufruf des Delegates selbst möglich.

     Listing 2.4 zeigt einen Ausschnitt aus dem erzeugten IL Code. Die automatisch generierte,
     kryptisch benannte Methode b__0 befindet sich dabei, zusätzlich zu den Instanz-
     methoden, im generierten IL Code. Der Compiler unterscheidet bei der Ausführung des IL
     Codes also nicht zwischen normalen und automatisch generierten anonymen Methoden.

                                                 22
2. Grundlagen

                 Listing 2.4: Ausschnitt aus dem erzeugten IL Code von Listing 2.3
 1

 2   .class public auto ansi beforefieldinit Converter
 3      extends [mscorlib]System.Object
 4   {
 5     {...}
 6      .method public hidebysig specialname rtspecialname instance void
           .ctor() cil managed
 7      {
 8      }
 9

10       .method private hidebysig static float64 b__0(int32
             arg) cil managed
11       {
12         {...}
13       }
14       {...}
15   }

     2.3.2. Lambda-Ausdrücke

     Seit der Version 3.5 des .NET Frameworks besteht neben der Möglichkeit anonyme Funk-
     tionen mit anonymen Methoden zu erstellen die Möglichkeit dies durch sogenannte Lamb-
     da-Ausdrücke zu realisieren. Lambda-Ausdrücke können als Weiterentwicklung anonymer
     Methoden verstanden werden. Der größte Benefit, der durch die Verwendung von Lambda-
     Ausdrücken erzielt werden kann, ist eine enorme Verkürzung und Vereinfachung des notwen-
     digen Syntax zum Erstellen anonymer Funktionen (Microsoft, 2012).

     Zum Erstellen eines Lambda-Ausdruckes wird der Lambda Operator => verwendet. Er trennt
     die Eingangsparameter der linken von den definierten Anweisungen auf der rechten Seite.
     Listing 2.5 zeigt die Realisierung von Listing 2.3 unter Zuhilfenahme eines Anweisungslamb-
     das.

                                                 23
2. Grundlagen

         Listing 2.5: Instanziierung einer anonymen Funktion mit Hilfe eines Anweisungslambdas
 1   public class Converter
 2   {
 3     private delegate double ConvertToDouble(int parameter);
 4

 5       public double Convert(int parameter)
 6       {
 7         ConvertToDouble converter = (int arg) =>
 8         { return System.Convert.ToDouble(arg); };
 9         return converter(parameter);
10       }
11   }

     Ein weiterer Vorteil der Verwendung von Lambda-Ausdrücken ist die Möglichkeit in vielen
     Fällen Parameterlisten und Rückgabetyp des Ausdruckes implizit statt explizit zu typisieren.
     Der Typ der Eingangsparameter kann im Falle eines nicht generischen Delegattyps aus dessen
     Signatur übernommen werden und muss nicht explizit angegeben werden. Somit kann im
     obigen Beispiel auf die Typisierung des Eingangsparameters verzichtet werden.

     Zusätzlich kann bei allen Lambda-Ausdrücken, welche ausschließlich aus einer return-
     Anweisung bestehen, auf die Verwendung des Schlüsselwortes return und die geschweiften
     Klammern, welche den Ausdruck umschließen, verzichtet werden. In diesen Fällen spricht
     man von sogenannten Ausdrucklambdas.

     Die Erstellung des Delegates kann somit auf 2.6 reduziert werden.

           Listing 2.6: Instanziierung einer anonymen Funktion mittels implizierter Typisierung
 1   ConvertToDouble converter = arg =>
 2       System.Convert.ToDouble(arg);

     Der impliziten Typisierung der Parameterliste sind jedoch einige Grenzen gesetzt. So ist es
     nicht möglich eine Mischung von implizit und explizit typisierten Parametern zu verwenden.
     Alle verwendeten Parameter müssen entweder ausschließlich explizit oder ausschließlich im-
     plizit typisiert sein. Zusätzlich ist die implizite Typisierung ausgeschlossen wenn es sich bei
     den verwendeten Parametern um out oder ref Parameter handelt.

     Lambda-Ausdrücke werden überwiegend in Fällen eingesetzt, in denen die anonyme Metho-

                                                   24
2. Grundlagen

     de einen Rückgabewert besitzt. Dies wird eingesetzt um eine Aneinanderreihung von Lamb-
     da-Ausdrücken zu ermöglichen und somit z.B. geschachtelte Ausdrücke zu vereinfachen. In
     diesen Fällen wird selten mit expliziten Delegattypen gearbeitet sondern fast ausschließlich
     generische Delegattypen eingesetzt.

     2.3.3. Erfasste Variablen

     Von einer erfassten Variablen spricht man immer dann, wenn eine anonyme Funktion eine
     Variable verwendet, die in der gleichen Methode deklariert wurde wie die anonyme Methode
     selbst, diese Variable aber kein Parameter des Delegates ist (Skeet, 2011).

     Listing 2.7: Einfaches Beispiel einer erfassten Variablen innerhalb einer anonymen Methode
 1   public void CaptuerVariable()
 2   {
 3       int legalAge = 18;
 4       List Persons = new List() {
 5       new Person("Marry", 20),
 6       new Person("Peter", 16),
 7       new Person("Gilbert", 25)};
 8

 9       Action ageFilter = delegate (Person p)
10       {
11            if (p.Age < legalAge)
12            {
13                Console.WriteLine(p.Name);
14            }
15       };
16       Persons.ForEach(ageFilter);
17   }

     Listing 2.7 zeigt ein einfaches Beispiel einer erfassten Variablen. Während dem Delegat age-
     Filter eine Person als Parameter zur Verfügung steht, greift das Delegat zusätzlich auf die
     äußere Variable legalAge zu. Das Delegat kann somit nur ausgeführt werden wenn es seine
     Bindung zu dieser Variablen behält. Da die Möglichkeit besteht, dass das so eben erstellte
     Delegat den Gültigkeitsbereich der Methode verlässt und weiter existiert, selbst wenn die Me-

                                                  25
2. Grundlagen

thode zurückgekehrt ist, muss sicher gestellt werden, dass eine erfasste lokale Variable ihren
eigentlichen Gültigkeitsbereich überleben kann. Ermöglicht wird dies, wie bereits im Falle der
anonymen Methoden, mit Hilfe der IL. Für jede erfasste Variable erstellt der Compiler im IL
Code eine Klasse, welche diese Variable hält. Die das Delegat umgebende Methode, sowie
das Delegat selbst, halten jeweils eine Referenz auf die gleiche Instanz dieser Klasse. Daraus
folgt das der anonymen Methode die tatsächliche Variable übergeben wird und nicht der Wert
der Variablen zum Zeitpunkt der Erstellung des Delegates. Dies führt dazu das die erfass-
te Variable nicht, wie sonst üblich, beim Verlassen der Methode zerstört wird. Dies ist nicht
möglich da noch eine Referenz des Delegates vorhanden sein könnte. Somit verlängert sich
die Lebenszeit der Variablen bis die letzte Referenz zerstört wurde. Der Garbage Collector 5
tritt also erst in Aktion wenn auch das referenzierende Delegat zerstört wurde.

Ein häufig in diesem Zusammenhang auftretende Problem wird als outer variable trap be-
zeichnet. (Lippert, 2009) Es tritt genau dann ein, wenn erwartet wird, dass der Wert der Va-
riablen übergeben wird, anstatt der Variablen selbst. Dies ist besonders beim Erfassen von
Schleifenvariablen oftmals der Fall. Eine Betrachtung dieses Problems wird in Abschnitt 3.7.2
durchgeführt.

2.4. Microsoft Roslyn

Im Rahmen dieser Arbeit wurde mit der im September 2012 erschienen Community Technolo-
gy Preview (CTP) Version von Microsoft Roslyn gearbeitet. Die entstandene Refaktorisierung
und somit auch das implementierte Visual Studio Plugin stützen sich auf dieses Framework. In
diesem Kapitel sollen kurz die grundlegenden Funktionalitäten dieses Frameworks vorgestellt
werden und zusätzlich alle benötigten Begrifflichkeiten erläutert werden.

2.4.1. Einführung

Mit dem Roslyn Framework stellt Microsoft eine Programmierschnittstelle (API - Application
Programming Interface) zur Verfügung, die einen tiefen Einblick in die vom Compiler erzeug-
ten Daten gewährt. Dabei beschränkt sich das Framework nicht auf die reine Bereitstellung

 5
     Der Garbage Collector stellt eine Form automatischer Speicherbereinigung dar.

                                                       26
2. Grundlagen

dieser Daten. Mit Hilfe des Frameworks ist es möglich Quellcode Transformationen und Ana-
lysen auf Basis tatsächlicher programmiersprachlicher Konstrukte durchzuführen. Durch die
gebotene Funktionalität unterstützt es die Erstellung von Quellcode erzeugenden oder Quell-
code manipulierenden Softwaretools.

2.4.2. Syntaxbaum

Um Modifikationen am Quellcode zu ermöglichen stellt das Roslyn Framework diesen in Form
eines Syntaxbaumes zur Verfügung. Der Syntaxbaum ist dabei eine vollständige Darstellung
des Quellcodes als Baumstruktur. Durch die verkettete Darstellungsform können die Bezie-
hungen zwischen programmiersprachlichen Konstrukten leichter dargestellt und analysiert
werden. Innerhalb des Baumes werden Attribute als Variablen der Knotenelemente verwaltet
während Beziehungen zwischen einzelnen Knotenelementen durch Referenzen repräsentiert
werden.

Im Gegensatz zum Abstract Syntax Tree (AST) enthält der Syntaxbaum alle im Quellcode vor-
handenen Informationen. Er repräsentiert also neben dem gesamten, sprachabhängigen Syn-
tax auch alle Leerzeichen, Kommentare und Formatierungseigenschaften, die keinen direkten
Einfluss auf die Ausführung des Programmes nehmen.

Der Syntaxbaum selbst ist dabei aus einer Kombination von verschiedenen Syntaxelemen-
ten zusammengesetzt. Die einzelnen Elemente Syntax Node, Syntax Token und Syntax Trivia
sollen im Folgenden kurz erläutert werden.(Ng u. a., 2012)

Syntax Nodes

Syntax Nodes bilden den Hauptbestandteil eines Syntaxbaumes. Durch sie werden alle im
Quellcode vorhandenen syntaktischen Bestandteile repräsentiert. So werden beispielsweise
Deklarationen, Anweisungen oder Ausdrücke als Syntax nodes dargestellt. Jede Syntax No-
de selbst, verwaltet über die Eigenschaften Parent und ChildNodes den jeweiligen Vorgänger
bzw. ihre nachfolgenden Nodes. Somit kann, ausgehend von jeder beliebigen Syntax Node,
ein Unter -oder Teilbaum abgeleitet werden. Für jede mögliche Form von Deklarationen, An-
weisungen oder Ausdrücken existiert eine tatsächliche Implementierung in Roslyn. So wird

                                            27
2. Grundlagen

beispielsweise konkret zwischen PropertyDeclarationSyntax und FieldDeclarationSyntax un-
terschieden.

Syntax Tokens

Syntax Tokens stellen den kleinsten Bestandteil des Syntaxbaumes dar. Sie repräsentieren un-
ter anderem Schlüsselwörter oder Bezeichner. Da sie, im Gegensatz zu Syntax Nodes, ein
atomarer Bestandteil des Quellcodes sind, verwalten sie keine Kind Knoten. Sie selbst werden
jedoch in exakter Reihenfolge, in der sie im Quellcode vorkommen, dem zugehörigen Syntax
Node als Kinder zugeordnet. Da nicht für jeden benötigten Syntax Token eine konkrete Klasse
existiert, wird die dem Token zugehörige Information über die Eigenschaft Value wiederge-
geben. Diese beinhaltet im Falle des PublicKeywords beispielsweise die Zeichenkette public,
während im IdentifierToken der tatsächliche Name des Bezeichners beinhaltet ist.

Syntax Trivia

Syntax Trivia repräsentieren die im Quellcode vorkommenden Leerzeichen und Kommentare.
Jeder geparste Token beinhaltet eine vorgehende und eine nachfolgende Syntax Trivia. Durch
die ebenfalls als Trivia erfassten Leerzeichen und Zeilenumbrüche wird unter anderem auch
die Formatierung des geparsten Quellcodes erfasst und editiert.

Listing 2.8 zeigt eine einfache Implementierung einer Klasse Person. Abbildung 2.1 zeigt
die daraus resultierende Darstellung in Form des Syntaxbaumes. In der mit Hilfe des Roslyn
Syntax Visualizer 6 erzeugten Darstellung als Syntaxbaum sind alle Syntax Nodes blau, alle
Syntax Token grün und alle Formen von Syntax Trivia in rot dargstellt.

 6
     http://blogs.msdn.com/b/visualstudio/archive/2011/10/19/
      roslyn-syntax-visualizers.aspx

                                            28
2. Grundlagen

     Listing 2.8: Einfache Implementierung einer Klasse Person zur Visualisierung des Syntaxbau-
                  mes
 1   using System;
 2

 3   public namespace Roslyn
 4   {
 5       public class Person
 6       {
 7           public int Age { get; set; }
 8           public string Name { get; set; }
 9

10           public Person(string name, int age)
11           {
12               this.Name = name;
13               this.Age = age;
14           }
15       }
16   }

                                                 29
2. Grundlagen

Abbildung 2.1.: Darstellung der Klasse Person als Syntaxbaum

                            30
2. Grundlagen

2.4.3. Manipulation des Syntax Trees

Die Modifikation von Quellcode ermöglicht Roslyn über die Manipulation des daraus er-
stellten Syntaxbaumes. Eine explizite Änderung wird dabei durch das Austauschen, Hinzu-
fügen oder Löschen von einzelnen Syntaxelementen realisiert. Dabei muss ein Syntaxelement
nicht durch ein gleichartiges ersetzt werden. Das Zusammenfassen einzelner Anweisungen zu
Blocksyntax ist genauso möglich wie eine einfache Änderungen des Bezeichners. Elemente
können mit Roslyn grundsätzlich auf zwei unterschiedliche Arten und Weisen erzeugt wer-
den. Die erste Möglichkeit ist das Verwenden von Factory Methoden. Diese sind für die meis-
ten mögliche Syntaxelemente vorhanden und erlauben eine stark parametrisierte Erzeugung
einzelner Elemente. Die zweite Möglichkeit ist die Generierung von Syntaxelementen aus
Zeichenketten. Dabei werden die Syntaxelemente direkt durch, von der Compiler API gepars-
te, Zeichenketten erzeugt. Besonders bei der Erstellung verschachtelter Anweisungen erweist
sich diese Vorgehensweise als vorteilhaft. Mit ihr können vollständige Teilbäume direkt und
unter Berücksichtigung verschiedener Faktoren, wie z.B. der Zielversion des Frameworks,
erzeugt werden.

Da der Syntaxbaum in der Lage ist, nicht kompilierbaren Quellcode zu verwalten, muss bei der
Erstellung und Manipulation von Syntaxelementen auf die semantische Korrektheit des Quell-
codes geachtet werden. Weder bei der Erstellung einzelner Elemente noch beim Erstellen des
editierten Syntaxbaumes findet eine semantische Verifizierung statt. Es ist also unter anderem
möglich ungültige Typbindungen zu erzeugen, ungültige Methodenaufrufe zu generieren oder
nicht kompilierbare Anweisungen in den Editor zu schreiben.

Da alle Syntaxelemente und Syntaxbäume in Roslyn grundsätzlich unveränderlich sind, kann
ein Editieren eines bestehenden Elementes nur über die Erzeugung eines neuen Elementes
erfolgen. Um die manipulierten Elemente in den Syntaxbaum einzufügen muss dieser folglich
ebenfalls neu erstellt werden.

2.5. Microsoft Visual Studio

Visual Studio ist eine von Microsoft entwickelte, integrierte Entwicklungsumgebung (IDE -
integrated development environment). Sie unterstützt die Entwicklung von Programmen mit

                                             31
2. Grundlagen

verschiedensten verwalteten und nicht verwalteten Programmiersprachen. Darunter beispiels-
weise C#, Visual Basic .NET, C++ und HTML.

Visual Studio beinhaltet des Weiteren eine Vielzahl von Werkzeugen zur Steigerung der Pro-
duktivität von Softwareentwicklern. So beinhaltet es unter anderem eine IntelliSense, zusätz-
liche grafische Editoren zum Erstellen von Windows Forms oder Windows Presentation Foun-
dation (WPF) Benutzungsschnittstellen und einen integrierten Debugger.

Im Rahmen dieser Arbeit wird Visual Studio vor allem als Basis für die zu entwickelnde
Erweiterung verwendet. Aus diesem Grund soll im Folgenden kurz auf die Möglichkeiten der
Erweiterung von Visual Studio eingegangen werden.

2.5.1. Erweiterbarkeit von Visual Studio

Grundsätzlich bietet Visual Studio eine Vielzahl von Möglichkeiten Erweiterungen zu inte-
grieren, welche unter dem Begriff Visual Studio Extensibility (VSX) zusammengefasst wer-
den.

Die Möglichkeiten zur Erweiterung reichen von einfachen Makros zur Vereinfachung oder
Automatisierung von häufig auftretenden Aufgaben über das Hinzufügen von Code Snippets
bis hin zu Visual Studio Add-Ins und Integrated Packages (VSPackages).

Makros stellen dabei die einfachste Form der Erweiterung dar. Sie ermöglichen die Erstellung
einer Abfolge von Anweisungen zum Erfüllen verschiedenster Aufgaben. Makros können
entweder durch den, direkt in die IDE eingebetteten Service aufgenommen und gespeichert
werden oder in einer, an Visual Basic angelehnten, Skriptsprache händisch erstellt werden.
Da Makros weder vollen Zugriff auf die IDE haben, noch die Möglichkeit beinhalten neue
Funktionalität wie beispielsweise Kontextmenü-Einträge zu integrieren, sind sie für die zu
realisierende Aufgabe nicht ausreichend.

Visual Studio Add-Ins stellen eine weitaus mächtigere Variante der Erweiterung von Visual
Studio dar. Ihnen steht die Möglichkeit offen auf das Visual Studio automation model zuzu-
greifen. Dieses ermöglicht einen breiten Zugriff auf die Funktionalitäten der IDE. So können
beispielsweise die Funktionalitäten anderer Add-Ins genutzt werden, Funktionen zu bestehen-
den Editoren wie dem Text Editor hinzugefügt werden oder vollständig eigenständige Werk-

                                             32
2. Grundlagen

zeuge integriert werden. Im Gegensatz zu Makros werden Add-Ins über die Implementierung
einer COM (Component objekt model - eine Vorgänger-Technologie von .NET) Schnittstelle
eingebunden. Sie werden als kompilierte DLL eingebunden. Mit dem Erscheinen von Visu-
al Studio 2013 wurden Visual Studio Add-Ins von Microsoft als veraltet gekennzeichnet, die
empfohlene Alternative sind die Visual Studio Packages.

Unter Visual Studio Packages versteht man die wohl mächtigsten Erweiterungsmöglichkeiten
der Visual Studio IDE. Sie bieten neben dem Zugriff auf das automation model zusätzlich
eine Vielzahl von Funktionalitäten der IDE an. Die Möglichkeiten der Packages sind so weit-
reichend, dass selbst Teile der Kernkomponenten des Visual Studio selbst, als Packages im-
plementiert wurden. Darunter fallen beispielsweise der Texteditor oder der Debugger. (Novak,
2008) Um die Funktionalität des zu erstellenden Plugins auch in Visual Studio 2013 prinzipiell
zu ermöglichen, wurde es als Visual Studio Package realisiert.

                                             33
3. Lösungsansatz

Das folgende Kapitel soll den grundsätzlichen Lösungsansatz darlegen, welcher zur Realisie-
rung der Refaktorisierung Extract Closure verwendet wurde. Dabei sollen zuerst die Rahmen-
bedingungen skizziert werden, innerhalb welcher diese Arbeit durchgeführt wurde. Nachfol-
gend sollen die Anforderungen an das Werkzeug selbst sowie an die Konfigurierbarkeit der
Lösung vollständig dargelegt werden. Außerdem wird die zur Extraktion des Closures ver-
wendete Vorgehensweise vorgestellt. Abschließend werden zusätzliche Einflüsse auf die ent-
standene Lösung betrachtet, welche außerdem in Kapitel 5 bewertet werden sollen.

3.1. Rahmenbedingungen

Im Folgenden sollen, in aller Kürze, die zur Realisierung von Extract Closure verwendeten
Rahmenbedingungen vorgestellt werden. Wie bereits erwähnt soll das Plugin in der Program-
miersprache C# entwickelt werden. Zur Realisierung der Datenflussanalyse und zur Mani-
pulation des Syntaxbaumes wird das Roslyn Framework verwendet. Das Plugin wird für die
Entwicklungsumgebung Visual Studio 2012 entwickelt.

Eine weitere Rahmenbedingung bezieht sich auf den Quellcode, aus welchem heraus die Re-
faktorisierung gestartet werden soll. Da der von Roslyn verwaltete Syntaxbaum in der Lage ist,
nicht kompilierbaren Quellcode darzustellen, kann die Refaktorisierung prinzipiell auch auf
Quellcode ausgeführt werden, welcher syntaktische Fehler beinhaltet und somit nicht kompi-
liert.

Da die reine Erstellung eines Closures bzw. einer anonymen Methode diese nicht ausführt,
muss die neu erstellte Funktion anstelle des früheren Quellcodes ausgeführt werden.

                                             34
3. Lösungsansatz

3.2. Grundsätzlicher Ablauf der Refaktorisierung

Der grundsätzliche Ablauf der Extract Closure Refaktorisierung lehnt sich an die bereits in
Visual Studio integrierten Refaktorisierungen an. So wird nach der Selektion des Quellcodes
über das Kontextmenü die betreffende Refaktorisierung gestartet. Dabei wird überprüft ob
alle Vorbedingungen zur Ausführung der gewählten Refaktorisierung erfüllt sind. Die zusätz-
lich anzugebenden Benutzerinformationen werden in einem Benutzungsdialog abgefragt und
stellen die Konfiguration des zu erstellenden Closures dar. Sind alle notwendigen Informatio-
nen vorhanden, kann die Refaktorisierung ausgeführt werden. Der stichpunktartige Ablauf der
Refaktorisierung lässt sich wie folgt beschreiben:

   1. Der zu refaktorisierende Quellcode wird vom Benutzer selektiert

   2. Eine erste Überprüfung stellt fest, ob die Refaktorisierung auf dem selektierten Quell-
      code ausführbar ist

   3. Die auszuführende Refaktorisierung wird vom Benutzer gestartet

   4. Vom Benutzer anzugebende, zusätzliche Konfigurationsmöglichkeiten werden behan-
      delt

   5. Die angestrebten Änderungen im Quellcode werden berechnet

   6. Falls die Änderungen durchführbar waren, wird das Ergebnis in den Texteditor zurück-
      geschrieben

Dabei kann nach der Ausführung der Refaktorisierung der neue erstellte Quellcode mit den
Undo-Funktionen der Entwicklungsumgebung rückgängig gemacht werden. Sollte die Refak-
torisierung auf den ausgewählten Quellcode nicht ausführbar sein, wird die entsprechende
Option im Kontextmenü ausgeblendet und kann somit nicht ausgeführt werden.

                                             35
3. Lösungsansatz

3.3. Vorbedingungen

Um die vom Benutzer selektierten Anweisungen zu extrahieren müssen eine Reihe von Vor-
bedingungen erfüllt sein, die eine gültige Auswahl von Anweisungen eruieren. Diese sollen
sicherstellen, dass die Refaktorisierung korrekt durchgeführt werden kann und sich innerhalb
zulässiger Grenzen bewegt. Die im Rahmen dieser Arbeit verwendeten Vorbedingungen für
die Refaktorisierung Extract Closure sind dabei:

  1. Es muss mindestens eine Anweisung selektiert sein

  2. Alle selektierten Anweisungen müssen sich innerhalb derselben Methode befinden

  3. Die erste und die letzte selektierte Anweisung müssen sich zusätzlich innerhalb des
     gleichen Gültigkeitsbereiches befinden

  4. Die Selektion darf keine Variablendeklaration beinhalten

  5. Die selektierten Anweisungen dürfen eine Reihe von Ausdrücken nicht enthalten

  6. Mindestens eine der selektierten Anweisungen muss eine lokale Variable beinhalten

Die aufgeführten Vorbedingungen sind Ausschlusskriterien für die Durchführung der Refak-
torisierung und werden noch vor der Freigabe des Anwenderdialoges zur Konfiguration der
Refaktorisierung überprüft. Um die Refaktorisierung durchführen zu können, muss mindes-
tens eine gültige Anweisung selektiert sein. Dabei muss die Auswahl die Anweisung nicht
vollständig umschließen. Wird nur ein Teil einer gültigen Anweisung selektiert, so wird die
Auswahl auf die vollständige, berührte Anweisung erweitert. Dies soll einer Extraktion un-
vollständiger und damit fehlerhafter Anweisungen entgegenwirken.

Bei allen Anweisungen, die selbst einen eigenen Gültigkeitsbereich erstellen, wird explizit
unterschieden ob die Selektion die beinhalteten Anweisungen betrifft oder die Anweisung
(inklusive aller beinhalteten Anweisungen) selbst. Somit ist es beispielsweise möglich An-
weisung innerhalb von Schleifen oder die gesamte Schleifenanweisung zu extrahieren.

Für die semantische Analyse der Anweisungen und für die Erstellung einer anonymen Funk-
tion ist es zwingend notwendig, dass alle selektierten Anweisung innerhalb des gleichen Gül-

                                            36
3. Lösungsansatz

tigkeitsbereiches liegen. So ist es unter anderem nicht möglich über Methodengrenzen hinweg
Anweisungen zu selektieren oder Teile von geschachtelten Gültigkeitsbereichen zu extrahie-
ren. Wäre dies möglich, würde die Schachtelung der Gültigkeitsbereiche durch den Abschluss
der anonymen Funktion (oder der Anweisungen innerhalb des Anweisungslambdas) die be-
stehende Strukturierung des Quellcodes verändert. Dies würde im besten Fall zu nicht kompi-
lierbarem Code führen und somit den Fehler offenlegen, im schlimmsten Fall unbemerkt das
Verhalten ändern.

Eine weitere Vorbedingung ist, dass sich innerhalb der zu extrahierenden Anweisungen keine
Variablendeklaration befinden darf. Da durch die anonyme Funktion ein neuer Gültigkeits-
bereich erstellt wird, wäre die vorher im umgebenden Gültigkeitsbereich verfügbare Varia-
ble, nur noch lokal innerhalb der anonymen Funktion verfügbar. Grundsätzlich bestünde die
Möglichkeit die Variablendeklaration vor die Erstellung der anonymen Funktion zu stellen.
Somit könnte die Bindung der Variablen aufrecht erhalten werden und zusätzlich die Extrak-
tion ausgeführt werden. Da die Verschiebung der Variablendeklaration selbst jedoch an einige
Vorbedingungen geknüpft ist, soll im Rahmen dieser Arbeit auf diese Möglichkeit verzichtet
werden.

Zusätzlich sind eine Reihe von Anweisungen innerhalb anonymer Funktionen nicht oder nur
unter Einschränkungen verfügbar. Bei nicht erlaubten Anweisungen handelt es sich zumeist
um Sprunganweisungen 1 (Microsoft, 2012). Diese stellen eine Übergabe der Programmsteue-
rung dar. Durch die Sprunganweisungen goto, break und continue kann eine anonyme Metho-
de nicht verlassen werden. Sie verhindern somit eine Ausführung von Extract Closure auf den
ausgewählten Anweisungen.

Ein elementarer Bestandteil eines Closures ist der Bezug auf dessen Erstellungskontext (siehe
Kapitel 2.3). Daher kann ein Closure nur extrahiert werden, wenn innerhalb der selektierten
Anweisungen entweder schreibend oder lesend auf mindestens eine lokale Variable oder einen
lokalen Parameter zugegriffen wird. In allen anderen Fällen wird die Refaktorisierung nicht
ausgeführt, da eine resultierende anonyme Funktion kein Closure darstellen könnte.

 1
     http://msdn.microsoft.com/de-de/library/d96yfwee.aspx

                                             37
Sie können auch lesen