Softwareproduktlinien - Refactoring von SPLs und ihren Produkten Sven Apel (Universität Passau) Christian Kästner (CMU) Martin Kuhlemann, Gunter Saake (Universität Magdeburg)
Agenda Grundlagen Refaktorisierung Feature-Refaktorisierung Refaktorisierung zum Erstellen von Produktlinien Einordnung, Motivation Herausforderungen, Granularität Refaktorisierung zum Ändern von Produktlinien Features aufspalten und zusammenlegen Reihenfolge vertauschen
Refaktorisierung – Definition „Refactoring is the process of changing a software system in such a way that it does not alter the external behaviour of the code yet improves its internal structure“ Ziele sind Verbesserung der Lesbarkeit Verbesserung der Verständlichkeit Verbesserung der Wartbarkeit Verbesserung der Erweiterbarkeit Kurz: Nachträgliche Verbesserung der Struktur von existierendem Quelltext Refactorings existieren fast für jede Programmiersprache [Fowler 1999]
Refaktorisierung – Ein Widerspruch? Widerspricht eigentlich traditionellen Top-Down Softwaretechnikansätzen, z. B. Wasserfallmodell In der Praxis häufig nötig, besonders wenn sich Software weiterentwickelt Grundlage moderner Softwaretechnikansätze wie „Agile Softwareentwicklung“
Refaktorisierung – „Code Smells“ Anlass zu Refaktorisierung ist ein „Code Smell“ „A code smell is a perceivable property of source code that is an indicator of an undesired code property.“ Code Smell-Beispiele in OO-Code Kopierter Quelltext (code clones) Sehr lange Methoden, sehr große Klassen, lange Parameterlisten Nutzlose Klassen Zu starke Kopplung u.v.m.
Katalog zur Refaktorisierung Refaktorisierung als Vorgehensvorschrift Je mit Name, Beschreibung, Motivation, Vorbedingungen, Algorithmus, und Beispiel Validität… teils formal bewiesen, dass keine Verhaltensänderungen auftreten können oder so klein und überschaubar dass offensichtlich keine Verhaltensänderungen auftreten Üblicherweise klein und überschaubar, leicht verständlich Zum Teil mit Werkzeugen automatisiert, z. B. Eclipse nur geringer Ausschnitt der vorgeschlagenen Refactorings von Fowler (M. Fowler. Refactoring: Improving the Design of Existing Code. Addison- Wesley, 2000.)
Bekannte OO-Refaktorisierungen Rename Class, Rename Method, ... Move Method, Move Field Extract Method Encapsulate Field Inline Class, Extract Class Push Down Method, Pull Up Method Replace Method with Method Object Replace Constructor with Factory Method u.v.m.
Beispiel: Extract Method Code Smell: lange Methode, Zweck von Codefragmenten darin nicht deutlich Extraktion von Statements in eigene Methode Automatisierbar in Eclipse class Foo { void printOwing() { printBanner(); printDetails(getOutstanding()); } void printDetails(double outst){ System.out.println("name " + _name); System.out.println("amount " + outst); class Foo { void printOwing() { printBanner(); //print details System.out.println("name " + _name); System.out.println("amount " + getOutstanding()); }
Domänenspezifische Refaktorisierungen Funktionale Programmiersprachen Algebraic or existential type Concrete to Abstract Data Type SQL Refactorings Benenne Tabelle/Spalte um Definiere Default-Wert Aspekt-Orientierte Programmierung Move Method from Class to Inter-Type Declaration Push down pointcut Model-getriebene Softwareentwicklung ...
Refaktorisierung bei Softwareproduktlinien
Refaktorisierung bei Softwareproduktlinien – Definition „A product-line refactoring is a refactoring step specific to feature-oriented product-line development that may affect the feature model and the domain artifacts of a software product line“ [Apel et al. 2013] Ziele sind Erstellung von Produktlinien Änderung von Produktlinien Lesbarkeit/Verbesserung der Verständlichkeit Verbesserung der Erweiterbarkeit Einfügen neuer Produkte
Arten von Refaktorisierungen Refaktorisierung zur Erstellung von Produktlinien Zerlegung einer bestehenden Anwendung in Features (Feature-Extraktion) Verbesserung der Struktur der Anwendung durch Explizit- machen der Features (Feature Traceability Problem) Fügt ggf. Konfigurationsoptionen hinzu Refaktorisierung zur Änderung von Produktlinien Verbesserung der Programmstruktur durch Umformung der Features, z. B. zusammenlegen oder aufspalten
Exkurs: Ansätze zur Einführung von SPLs Es gibt drei übliche Ansätze eine Software Produktlinie zu erstellen / einzuführen Proaktives Vorgehensmodell Extraktives Vorgehensmodell Reaktives Vorgehensmodell Auswahl anhand betrieblicher Gesichtspunkte Kosten Risiko Chancen ...
Exkurs: Proaktives Vorgehensmodell SPL neu entwerfen und implementieren; wie Wasserfallmodell Komplette Domänenanalyse zu Beginn Kann die normale Entwicklung für mehrere Monate unterbrechen, bis Produktlinie auf dem Stand der bestehenden Produkte ist Hohe Kosten und hohes Risiko Gut wenn Anforderungen wohl-definiert sind
Exkurs: Proaktives Vorgehensmodell II Implementation Proactive [Krueger 2001]
Exkurs: Extraktives Vorgehensmodell Nutzt eine oder mehrere bestehende Anwendungen (legacy application) als Basis Extrahiert daraus Features und erzeugt so Varianten Geeignet für den schnellen Wechsel von traditionellen Anwendungen zu Produktlinien Relativ geringer Ressourcenaufwand und Risiko Sehr anspruchsvoll für Werkzeuge und Sprachen, da Zerlegung einer Anwendung, die nicht als SPL entworfen wurde (vgl. Unbewusstheitsprinzip in AOP)
Exkurs: Extraktives Vorgehensmodell II Extract [Krueger 2001]
Exkurs: Reaktives Vorgehensmodell Analog zu Spiralmodell oder Agiler Softwareentwicklung Implementiert zuerst wenige Variationen; fügt inkrementell weitere hinzu Geeignet, wenn benötigte Varianten nicht komplett im voraus bekannt, und für unvorhergesehene Änderungen Kleinere Projektgröße, geringere initiale Kosten; weniger Ressourcenbindung, schnellere Ergebnisse später evtl. Umstrukturierungen nötig
Exkurs: Reaktives Vorgehensmodell II React Iterate [Krueger 2001]
Refaktorisierung bei Softwareproduktlinien Refaktorisierung zur Erstellung von Produktlinien
Refaktorisierung zur Erstellung von Produktlinien Refaktorisierung in Features wird im extraktiven Modell genutzt Code aus bestehender Anwendung in Feature verschieben ohne Verhaltensänderung ( mit aktiviertem Feature muss sich die Produktlinie wie die originale Anwendung verhalten) Typische Refaktorisierungen sind „Move Class/Method/Field/Statements to Feature“ Konkrete Umsetzung abhängig von verwendeten Implementierungsansatz (Präprozessor, FOP, AOP, Framework, ...)
Herausforderung: optionale Features Extraktion eines Features Neue Variante(n) Alle Varianten müssen korrekte Programme sein (die sich natürlich anders verhalten) d. h. Extraktion eines Features muss vollständig sein (nicht nur die einzelnen Refaktorisierungen von Code- Fragmenten müssen stimmen) Code tangling & scattering Extraktion nicht unmittelbar möglich Wie kann Feature-Code vollständig erkannt werden? Wie erkennt man korrekte Refaktorisierungen von Features? Was könnten Pre- und Post-Conditions sein?
Beispiel: Feature-Refaktorisierung Einstiegsbeispiel: Graph-Beispiel mit Farben; der Code für die Farben (blau markiert) soll in ein Feature verschoben werden class Node { int id = 0; Color color = new Color(); void print() { Color.setDisplayColor(color); System.out.print(id); } class Edge { Node a, b; Color color = new Color(); Edge(Node _a, Node _b) { a = _a; b = _b; } void print() { Color.setDisplayColor(color); a.print(); b.print(); class Color { static void setDisplayColor(Color c) { ... } }
Refaktorisierung mit Jak Die Klasse Color kann direkt in das Feature-Verzeichnis verschoben werden Die Felder color werden in Rollen (Klassenverfeinerungen) verschoben Die Aufrufe „setDisplayColor“ werden mittels Methodenverfeinerung in Rollen implementiert class Node { int id = 0; void print() { System.out.print(id); } class Node { int id = 0; Color color = new Color(); void print() { Color.setDisplayColor(color); System.out.print(id); } refines class Node { Color color = new Color(); void print() { Color.setDisplayColor(color); Super.print(); }
Refaktorisierung mit AspectJ Die Klasse Color wird als innere statische Klasse des Aspekts verschoben Die Felder color werden in Inter-Type Deklarationen verschoben Die Aufrufe „setDisplayColor“ werden mittels Advice implementiert class Node { int id = 0; void print() { System.out.print(id); } class Node { int id = 0; Color color = new Color(); void print() { Color.setDisplayColor(color); System.out.print(id); } aspect Colors { Color Node.color = new Color(); before(Node n) : execution(void Node.print()) && this(n) { Color.setDisplayColor(n.color); }
Schwierige Refaktorisierungen Anweisungen in der Mitte einer Methode Refaktorisierung von Parametern oder Exceptions class Edge { Node a, b; Color color = new Color(); Edge(Node _a, Node _b) { a = _a; b = _b; } void print() { Color.setDisplayColor (Color.WEIGHTCOLOR); weight.print(); Color.setDisplayColor(n.color); a.print(); b.print(); } class Graph { ... Edge add(Node n, Node m, Weight w) throws InvalidWeightException { Edge e = new Edge(n, m); nv.add(n); nv.add(m); ev.add(e); e.setWeight(w); return e; }
Anweisungen in einer Methode AspectJ Jak (mittels Hook-Methoden) aspect Colors { Color Edge.color = new Color(); before() : execution(void Edge.print()) { Color.setDisplayColor(Color.WEIGHTCOLOR); } after(Edge e) : call(void Weight.print()) && withincode(void Edge.print()) && this(e) { Color.setDisplayColor(e.color); }} class Edge { void print() { weight.print(); hook(); a.print(); b.print(); } void hook() {}; refines class Edge { Color color = new Color(); void print() { Color.setDisplayColor (Color.WEIGHTCOLOR); Super.print(); } void hook() { Color.setDisplayColor(n.color); }}
Anweisungen in einer Methode II Der AspectJ-Ansatz nutzt das fein-granulare Join-Point- Modell von AspectJ Gefährlich wenn als „Hack“ genutzt (Fragile Pointcut Problem) Nicht immer möglich (Code um Anweisungssequenz, Code vor Anweisungen, die keinen JP haben z. B. if) Hook-Methoden fügen neue Erweiterungspunkte (oder Join-Points) ein Können Code-Lesbarkeit erschweren Code Tangling
Parameter und Exceptions einfügen In fast allen Sprachen sind Signaturen fixiert, weil sie zum Adressieren verwendet werden (z. B. in einem Pointcut) Zu den Deklarationen müssen auch alle Aufrufe angepasst werden Einfügen von neuen Parametern ist nicht möglich Refaktorisierungen nur mit Hacks/Workarounds Duplizierte Methoden und Feldvariablen Parameter-Objekte Wormhole Pattern in AspectJ Ausnahme: Präprozessoren
Eine Frage der Granularität Dateien/Verzeichnisse einf. Module/Komponenten/Klassen einf. Explizite Erweiterungspunkte Methoden/Felder einfügen Methoden erweitern Statement in Methodenmitte Ausdrücke/Terme erweitern Signaturen von Methoden erweitern Token einfügen Zeichen einfügen Einfache SPL Tools Frameworks, Komp. FOP Fein – Granularität – Grob AOP Präprozessoren
Granularität bei der Refaktorisierung Ursprüngliche Anwendung nicht für Features vorbereitet Erweiterungen oft sehr fein-granular Viel mehr Erweiterungspunkte als bei proaktivem Vorgehen, wo häufig wenige geplante Punkte ausreichen Aufgrund vieler „Hacks“ wird Quelltext schnell unlesbar
Fallstudie: Refaktorisierung von Berkeley DB mittels AspectJ Zerlegung einer Datenbank (80 KLOC) in 38 Features; Refaktorisierung mit AspectJ Art und Granularität der Erweiterungen 1124 Erweiterungen in 151 Aspekten, davon 640 statisch (Methoden, Felder, Klassen einfügen) und 484 dynamisch 214 einfache Methodenerweiterungen 285 Erweiterungen in der Mitte einer Methode Davon 121 `call && within' 164 Erweiterungen mittels Hook-Methode
Fallstudie Berkeley DB II Parametererweiterungen traten nur in wenigen Features, dort aber sehr gehäuft auf Transaktion: 64 Methoden mit Txn Parameter Locking: Locker, LockMode oder LockType in über 200 Methoden erwartet; Feature aufgrund des enormen Aufwands nicht extrahiert Exceptions wurden von einem Feature Synchronisation in sehr vielen Methoden eingefügt
Fallstudie Berkeley DB - Fazit Insgesamt hat die zu grobe Granularität die Refaktorisierung sehr aufwendig gemacht (zudem fehlte Werkzeugunterstützung) Aufgrund der vielen Workarounds sind einige Feature sehr schwer lesbar AspectJ ist keine geeignete Sprache für Feature- Refaktorisierung, aber auch andere Sprachen haben Defizite hinsichtlich der Granularität Extraktion von Features aus bestehenden Anwendungen sehr anspruchsvoll
Werkzeug: CIDE Colored Integrated Development Environment Features werden durch Hintergrundfarben repräsentiert Feingranulare Zuweisungen Disziplinierte Annotationen: nur syntaktisch sinnvolle Elemente können annotiert werden (keine einzelnen Zeichen) Automatische Refaktorisierung von markiertem Code in Feature-Module z. Z. Jak, FeatureHouse und AspectJ Nutzt Workarounds wie Hook-Methoden für schwierige Refactorings Generierter Code kaum lesbar, Wartung auf gefärbtem Code
CIDE II
Refaktorisierung bei Softwareproduktlinien Refaktorisierung zur Änderung von Produktlinien
Änderung von Produktlinien – Untergliederung I 1. Variabilitätserhaltende Refaktorisierung: „A variability-preserving refactoring is a product-line refactoring that does not change the set of valid products and corresponding feature selections (modulo renaming) and preserves the observable behavior of all valid products (modulo binding time changes). “ 2. Variabilitätssteigernde Refaktorisierungen „A variability-enhancing refactoring is a product-line refactoring that does not remove products from the set of valid products and preserves the observable behavior of all previously valid products. A variability-enhancing refactoring may introduce additional valid products. “ [Apel et al. 2013] [Apel et al. 2013]
Änderung von Produktlinien – Untergliederung II 3. Produkterhaltende Refaktorisierung: „A product-preserving refactoring is a product-line refactoring that does not remove products from a given set of products and preserves the observable behavior of all those products. A product-preserving refactoring may add and remove products outside the given set.“ [Apel et al. 2013]
Refaktorisierung zur Änderung von Produktlinien Grund für Refaktorisierung: Evolution oder Wartung Ungeplante SPL-Erweiterungen Code-Replikation Übersichtlichkeit Verbesserung der Struktur / Lesbarkeit bei gleichem Verhalten Mögliche Refaktorisierungen sind u. a. Umbenennen von Features (rename) Zusammenfügen von Features (merge) Aufspalten von Features (split) Umsortieren von Features (reorder) Refaktorisierungen von Feature Modellen
Variability Smells I Ein optionales Feature wird in jeder Konfiguration benötigt Feature kann zum Basiscode hinzugefügt werden Oder: Feature Modell anpassen „A variability smell is a perceivable property of a product line that is an indicator of an undesired code property. It may be related to all kinds of artifacts in a product line, including feature models, domain artifacts, feature selections, and derived products.”
Variability Smells II Ein Feature wird immer zusammen mit einem anderen Feature ausgewählt Komplexität verringern durch Zusammenlegen (merge) Ein Feature wird in keiner Konfiguration benötigt Feature löschen Ein Feature wird häufig benötigt, nur weil es Code enthält, den ein anderes Feature braucht Feature aufspalten und den gemeinsamen Code herauslösen Ein Feature benötigt Funktionalität, die erst von einem späteren Feature zur Verfügung gestellt wird Reihenfolge ändern (reorder)
Herausforderungen – Refaktorisierung SPLs Semantik wird als Summe aller Produkte angesehen Komplexität der notwendigen Anpassungen und Auswirkungen Refaktorisierung erfordert Änderungen in verschiedenen „Sprachen“ Feature-Umbenennung: Anpassungen: Cross-Tree Constraints, Modul-Name, Konfigurationen… Pull-Up Method: Accidental Overriding in anderen Refinements
Umbenennen von Features Feature-Name wird in Feature-Modell und Feature-Modul (Verzeichnisname) verwendet Außerdem ggf. noch in Konfigurationen (Equation-Dateien) Jak-Dateien (Keyword layer) AspectJ-Dateien (Package-Deklaration) ... Simple Automatisierung in FeatureIDE: Bei Umbenennungen im Feature-Modell wird auch das Feature-Modul umbenannt
Zusammenfügen von Features (merge) „Benachbarte“ Features können i.d.R. kombiniert werden, indem die Erweiterungen zusammengefügt werden. Lediglich Erweiterungen des gleichen Codefragments müssen kombiniert werden Sind Features nicht benachbart, muss zuerst die Reihenfolge entsprechend verändert werden Formalisierung: Introduktionssumme und Modifikationssumme
Zusammenfügen von Features class Foo { void print() { System.out.print('<core/>'); } refines class Foo { int a; System.out.print('<inner>'); Super.print(); System.out.print('</inner>'); Object b; System.out.print('<outer>'); System.out.print('</outer>'); class Foo { void print() { System.out.print('<core/>'); } refines class Foo { int a; Object b; System.out.print('<outer>'); System.out.print('<inner>'); Super.print(); System.out.print('</inner>'); System.out.print('</outer>'); P = InnerOuter • Core P = Outer • Inner • Core
Aufspalten von Features Aufspalten von Features ist ähnlich der Refaktorisierung zur Erstellung von Produktlinien Der zu entsprechende Code muss ausgewählt werden und in ein neues Feature verschoben werden Das eigentliche Verschieben kann automatisiert werden
Umsortieren von Features Bei vielen Features ist die Reihenfolge nicht relevant, weil sie disjunkte Codefragmente erweitern und einander nicht referenzieren Erweitern beide Features das gleiche Code Fragment (die gleiche Methode, den gleichen Join Point), muss der Feature-Quelltext geändert werden Kann im allgemeinen Fall schwierig sein, aber z. B. durch Hook-Methoden möglich
Umsortieren von Features (gemeinsamer Join-Point) class Foo { void print() { System.out.print('<core/>'); } refines class Foo { System.out.print('<inner>'); Super.print(); System.out.print('</inner>'); System.out.print('<outer>'); System.out.print('</outer>'); class Foo { void print() { System.out.print('<core/>'); } refines class Foo { System.out.print('<outer>'); hook1(); Super.print(); hook2(); System.out.print('</outer>'); void hook1() {} void hook2() {} void hook1() { System.out.print('<inner>'); void hook2 () { System.out.print('</inner>'); P = Outer • Inner • Core P = Inner • Outer • Core
Refaktorisierung von Feature Modellen Veränderung im Code erfordern teilweise Veränderungen im Feature Modell A A Evolution B C B C X B C B C Nicht mehr benötigt
Zukünftige Herausforderungen Objekt-orientierte Refactorings nicht ohne weiteres auf SPLs anwendbar Refactorings in SPLs transformieren nicht nur ein Programm sondern viele Wie die Vorbedingungen prüfen? Was tun bei Mehrdeutigkeiten? Auch neue Refaktorisierungen denkbar: z.B. – Verschiebung von Code zwischen Features Änderungen im Code erfordern Änderungen in anderen Bereichen der SPL (Konfig, Module, …) Variability Smells
Zusammenfassung Refaktorisierung ist ein übliche Methode zur nachträglichen Verbesserung der Code-Struktur Refaktorisierung zur Erstellung von Produktlinien Zerlegung einer Anwendung in Features Probleme bei fein-granularen Erweiterungen Refaktorisierung zur Änderung von Produktlinien Verbesserung der SPL-Struktur durch das Zusammenfügen, Aufspalten oder Umsortieren von Features
Literatur I S. Apel, D. Batory, C. Kästner, and G. Saake. Feature- Oriented Software Product Lines - Concepts and Implementation. Springer, 2013. Chapter 8: Refactoring of Software Product Lines M. Fowler. Refaktorisierung. Improving the Design of Existing Code. Addison-Wesley, 1999. [Standardliteratur zu Refaktorisierung im Allgemeinen] P. Clements and C. Krueger. Point/Counterpoint: Being Proactive Pays Off/Eliminating the Adoption Barrier. IEEE Software, 19(4), 2002. [Vorgehensmodelle zur SPL-Entwicklung]
Literatur II C. Kästner, S. Apel, and M. Kuhlemann. Granularity in Software Product Lines. Proc. Int'l. Conf. Software Engineering, 2008. [Granularität und Überblick CIDE] S. Apel, C. Kästner and D. Batory. Program Refactoring using Functional Aspects. In Proc. of Int'l Conf. Generative Programming and Component Engineering, 2008. [Reihenfolge von Features (Aspekten) ändern] C. Kästner and S. Apel and D. Batory. A Case Study Implementing Features using AspectJ. In Proc. of Int'l Software Product Line Conference, 2007. [Fallstudie Berkeley DB]
Literatur III V. Alves, R. Gheyi, T. Massoni, U. Kulesza, P. Borba, and C. J. P. de Lucena. Refactoring Product Lines. In Proc. Int’l Conf. Generative Programming and Component Engineering, ACM, 2006. Hagen Schink, Martin Kuhlemann, Gunter Saake, and Ralf Lämmel. Hurdles in Multi-Language Refactoring of Hibernate Applications. In Proceedings of the International Conference on Software and Data Technologies (ICSOFT), SciTePress, 2011. Charles W. Krueger. Easing the Transition to Software Mass Customization. In Proceedings of the 4th International Workshop on Software Product-Family Engineering, Bilbao, Spain. 2001.
Literatur Ambler, S. Agile Database Techniques: Effective Strategies for the Agile Software Developer. JohnWiley & Sons, Inc., New York, NY, USA, 2003. Huiqing Li, REFACTORING HASKELL PROGRAMS, Dissertation at the University of Kent, September 2006 S. Trujillo, D. Batory, and O. Diaz. Feature refactoring a multi-representation program into a product line. In Proceedings of the Conference on Generative Programming and Component Engineering (GPCE), ACM Press, 2006