refactoring and retrofitting design patterns in java software...

11
Refactoring and Retrofitting Design Patterns in Java Software Product Lines ABSTRACT A centerpiece of modern software development is refactoring. Software Product Lines (SPLs), a major software develop- ment paradigm, lack tools to refactor Java SPL codebases. R4 is a new set of design guidelines, techniques, and lan- guage constructs to (1) express feature-based Java SPLs us- ing only Java custom annotations, (2) view programs of the SPL, (3) edit views, automatically propagating edits back to the SPL codebase, (4) verify refactoring preconditions are satisfied by the target program as well as all SPL pro- grams, (5) refactor programs, automatically applying a cor- responding refactoring to the SPL codebase, and (6) retrofit design patterns into the SPL codebase by executing refac- toring scripts. R4 implements a core theorem on refactoring feature-based Java SPLs. Case studies apply 2,316 refactor- ings to retrofit 64 pattern instances in 8 public Java SPLs and show that R4 is as efficient, expressive, and scalable as a state-of-the-art feature-unaware refactoring engine. 1. INTRODUCTION An SPL is a family of programs with commonalities [2,42, 44]. Amortizing the cost to design and maintain these com- monalities makes it economical to create SPLs. Programs of an SPL are distinguished by features – increments in pro- gram functionality. Each program in an SPL is defined by a unique of set of features called a configuration [2]. A common way to code SPLs is to use #if-#endif pre- processing: declarations and code blocks are labeled with feature presence conditions and are included when particu- lar feature(s) are present in a configuration; otherwise the declarations and blocks are erased [2, 46]. The Linux Ker- nel is a huge SPL, consisting of 16M LOC and over 6000 features [15, 34, 42]. It uses the C-preprocessor to remove declarations, code, and files to produce the C codebase for a configuration. The presence or absence of a feature in Java can be en- coded by a global static boolean variable; the Java compiler can evaluate feature predicates to remove unreachable code Permission to make digital or hard copies of all or part of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and the full cita- tion on the first page. Copyrights for components of this work owned by others than ACM must be honored. Abstracting with credit is permitted. To copy otherwise, or re- publish, to post on servers or to redistribute to lists, requires prior specific permission and/or a fee. Request permissions from [email protected]. c 2016 ACM. ISBN 978-1-4503-2138-9. DOI: 10.1145/1235 in if(feature expression) statements. But removing en- tire class, field and method declarations is not yet possible with existing Java constructs. So Java SPLs are hacked in some manner to achieve this additional and essential effect. One way is to preprocess a Java SPL codebase P for a given configuration C to produce its codebase PC; although Java does not officially have a preprocessor, there are unofficial ones [22, 40, 43]. Another way is to copy and assemble code fragments from P to produce PC [3, 5, 23, 30]. Either way, a separate codebase is created for PC, followed by run-edit- debug cycles to improve, tune, and repair PC. This is a com- mon way to develop SPL programs. It also exposes three key limitations in today’s SPL tooling. First, given an edited program PC, how are its changes back-propagated into P, the SPL codebase? Manually prop- agating changes does not scale. AHEAD [5] maintained links from PC to P and had the first tool to back-propagate changes. Gears [30], a commercial tool for SPL development, has a similar (but in our opinion more elegant) preprocessing solution. All solutions for back-propagation that are known to us rely on preprocessors. Second, refactoring is a centerpiece in modern software de- velopment [6, 35]. Programmers routinely rename program elements and move them using refactorings to improve pro- gram structure and clarity. Unfortunately, the same cannot be said for Java SPL development. Despite the significance of refactoring, existing prototype [3,5,23,41] and commercial tools [30] for Java SPL development offer no help to refactor SPLs; only recently has a refactoring engine for C-language SPLs appeared [32]. The main reason is that existing tools rely on preprocessors which lack type information needed for precondition checks and code transformations. Another reason is that some require extensions to the Java language, which is unlikely. Further, refactoring engines and SPL tools are notoriously difficult to build. Third, refactoring technology has been slow to evolve. Scripting is a key functionality that is missing in major IDEs [7, 20, 45], where scripts are programmatic sequences of refactorings. Most design patterns in the Gang of Four text [16] can be expressed as scripts [27, 28, 48]. R3 [29] is a Java refactoring engine that enables programmers to write and execute such scripts. R3, however, cannot refactor Java SPLs as it is feature ‘unaware’. In short, Java SPL development is difficult because of (1) the lack of tools to propagate program changes back into an SPL codebase, (2) the inability to refactor SPLs, and to a lesser extent (3) the inability to script refactorings to retrofit design patterns into SPL codebases automatically. 1

Upload: others

Post on 12-Aug-2020

19 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Refactoring and Retrofitting Design Patterns in Java Software …dig.cs.illinois.edu/papers/refactoringSPL_ASE16.pdf · Refactoring and Retrofitting Design Patterns in Java Software

Refactoring and Retrofitting Design Patterns inJava Software Product Lines

ABSTRACTA centerpiece of modern software development is refactoring.Software Product Lines (SPLs), a major software develop-ment paradigm, lack tools to refactor Java SPL codebases.R4 is a new set of design guidelines, techniques, and lan-

guage constructs to (1) express feature-based Java SPLs us-ing only Java custom annotations, (2) view programs of theSPL, (3) edit views, automatically propagating edits backto the SPL codebase, (4) verify refactoring preconditionsare satisfied by the target program as well as all SPL pro-grams, (5) refactor programs, automatically applying a cor-responding refactoring to the SPL codebase, and (6) retrofitdesign patterns into the SPL codebase by executing refac-toring scripts. R4 implements a core theorem on refactoringfeature-based Java SPLs. Case studies apply 2,316 refactor-ings to retrofit 64 pattern instances in 8 public Java SPLsand show that R4 is as efficient, expressive, and scalable asa state-of-the-art feature-unaware refactoring engine.

1. INTRODUCTIONAn SPL is a family of programs with commonalities [2,42,

44]. Amortizing the cost to design and maintain these com-monalities makes it economical to create SPLs. Programsof an SPL are distinguished by features – increments in pro-gram functionality. Each program in an SPL is defined bya unique of set of features called a configuration [2].

A common way to code SPLs is to use #if-#endif pre-processing: declarations and code blocks are labeled withfeature presence conditions and are included when particu-lar feature(s) are present in a configuration; otherwise thedeclarations and blocks are erased [2, 46]. The Linux Ker-nel is a huge SPL, consisting of 16M LOC and over 6000features [15, 34, 42]. It uses the C-preprocessor to removedeclarations, code, and files to produce the C codebase fora configuration.

The presence or absence of a feature in Java can be en-coded by a global static boolean variable; the Java compilercan evaluate feature predicates to remove unreachable code

Permission to make digital or hard copies of all or part of this work for personal orclassroom use is granted without fee provided that copies are not made or distributedfor profit or commercial advantage and that copies bear this notice and the full cita-tion on the first page. Copyrights for components of this work owned by others thanACM must be honored. Abstracting with credit is permitted. To copy otherwise, or re-publish, to post on servers or to redistribute to lists, requires prior specific permissionand/or a fee. Request permissions from [email protected].

c© 2016 ACM. ISBN 978-1-4503-2138-9.

DOI: 10.1145/1235

in if(feature expression) statements. But removing en-tire class, field and method declarations is not yet possiblewith existing Java constructs. So Java SPLs are hacked insome manner to achieve this additional and essential effect.One way is to preprocess a Java SPL codebase P for a givenconfiguration C to produce its codebase PC; although Javadoes not officially have a preprocessor, there are unofficialones [22, 40, 43]. Another way is to copy and assemble codefragments from P to produce PC [3, 5, 23, 30]. Either way,a separate codebase is created for PC, followed by run-edit-debug cycles to improve, tune, and repair PC. This is a com-mon way to develop SPL programs. It also exposes threekey limitations in today’s SPL tooling.

First, given an edited program PC, how are its changesback-propagated into P, the SPL codebase? Manually prop-agating changes does not scale. AHEAD [5] maintainedlinks from PC to P and had the first tool to back-propagatechanges. Gears [30], a commercial tool for SPL development,has a similar (but in our opinion more elegant) preprocessingsolution. All solutions for back-propagation that are knownto us rely on preprocessors.

Second, refactoring is a centerpiece in modern software de-velopment [6, 35]. Programmers routinely rename programelements and move them using refactorings to improve pro-gram structure and clarity. Unfortunately, the same cannotbe said for Java SPL development. Despite the significanceof refactoring, existing prototype [3,5,23,41] and commercialtools [30] for Java SPL development offer no help to refactorSPLs; only recently has a refactoring engine for C-languageSPLs appeared [32]. The main reason is that existing toolsrely on preprocessors which lack type information neededfor precondition checks and code transformations. Anotherreason is that some require extensions to the Java language,which is unlikely. Further, refactoring engines and SPL toolsare notoriously difficult to build.

Third, refactoring technology has been slow to evolve.Scripting is a key functionality that is missing in majorIDEs [7, 20, 45], where scripts are programmatic sequencesof refactorings. Most design patterns in the Gang of Fourtext [16] can be expressed as scripts [27, 28, 48]. R3 [29] is aJava refactoring engine that enables programmers to writeand execute such scripts. R3, however, cannot refactor JavaSPLs as it is feature ‘unaware’.

In short, Java SPL development is difficult because of (1)the lack of tools to propagate program changes back into anSPL codebase, (2) the inability to refactor SPLs, and to alesser extent (3) the inability to script refactorings to retrofitdesign patterns into SPL codebases automatically.

1

Page 2: Refactoring and Retrofitting Design Patterns in Java Software …dig.cs.illinois.edu/papers/refactoringSPL_ASE16.pdf · Refactoring and Retrofitting Design Patterns in Java Software

This paper presents R4, the first feature-aware refactoringengine for Java, that solves all three problems. We showhow a modification of standard IDE code folding allows usto project program PC with configuration C as a ‘view’ of anSPL codebase P. A programmer can edit and refactor viewPC; behind the curtains R4 applies corresponding edits andfeature-aware refactorings to P. R4 leverages scripts of R3 toallow programmers to automatically retrofit design patternsinto SPL codebases.

This paper makes several novel contributions:• New constraints (guidelines) to eliminate semantic

ambiguities that arise in annotated Java SPL designs,• A core theorem that equates a feature-unaware refac-

toring of an SPL program with a feature-aware refac-toring of the entire SPL codebase,• The R4 tool for editing, projecting, and refactoring

Java SPLs that implements this theorem,• The extensions R4 makes to R3 to support feature-

aware precondition checks in refactoring SPLs,• Case studies that apply 2,316 refactorings to retrofit

64 pattern instances in 8 Java SPLs and show R4 is asefficient, expressive, and scalable as a state-of-the-artfeature-unaware refactoring engine.

We begin with a gentle overview of standard SPL tools andconcepts. We then reveal the novelties of R4: how SPLs areencoded, how editable views are created, the theorem thatmakes refactoring SPLs possible, and how R4 extends R3.

2. STANDARD SPL TOOLS AND CONCEPTSEvery SPL has a Feature Model (FM) [2]. It is a hierar-

chy of features that define containment relationships (distin-guishing mandatory from optional features, and alternativefrom multi-choice features) and cross-tree constraints (suchas if feature X is selected, so to must feature Y).

An FM can be translated to a propositional formula [2]. ASPL configuration tool reads a FM and allows users to selectdesired features and deselect unwanted features to specify aprogram. The tool’s responsibility is to guarantee that theselection is legal w.r.t.the FM. To do so, the FM is mappedto formula φ, conjoined with the set of desired features andthe negation of undesired features. If features X and Y areselected and Z is deselected, the formula φ∧X∧Y∧¬Z is sub-mitted to a SAT solver. If satisfiable, at least one programin the SPL has this combination of features. Otherwise, theselection {X, Y,¬Z} is illegal. A configuration lists every fea-ture (as being selected or not) and this combination is legalw.r.t. the SPL’s FM. A configuration file, typically a CPP#include file, is produced by the configuration tool. Theprogram for this configuration file is produced by C prepro-cessing the SPL codebase.

Using the C-preprocessor causes all sorts of problems, suchas those listed in the Introduction. It also makes it consid-erably harder to analyze the SPL codebase [23, 24]. Suchan analysis is Safe Composition (SC), the verification thatevery program of an SPL is type safe, i.e., compiles withouterror [2, 26, 47]. Suppose statement “x=y;” is introduced byfeature F, variable x is defined by feature X, and y by Y. Thisrelationship is expressed by the constraint ψ := ( F⇒ X∧Y ).That is, if the “x=y;” statement appears in a program, sotoo must the definitions of x and y.

Again, let φ be the prop formula of the SPL’s FM. If φ∧¬ψis satisfiable, then there exists at least one program in theSPL that does not satisfy ψ and hence will not compile [11].

Similarly, dead code is source that appears in no SPL pro-gram. Let ψ be the presence condition of code fragment `.If φ ∧ ψ is unsatisfiable, then ` is dead code.

An SC tool culls an SPL codebase P for all distinct ψconstraints and verifies that no program in the SPL violateseach constraint. We say P satisfies SC or P is dead code freeif there are no violations. Others [2,23,24,47] provide moredetails.

We are now ready to present the novel ideas of R4.

3. R43.1 Encoding Java SPLs with R4

package constants;

interface spl {

static final boolean X = true;

static final boolean Y = true;

static final boolean Z = false;

}

@interface Feature {

boolean value();

}

(a)

(b)

@Feature(X)

int i, j, k;

@Feature(X)

int i, j;

@Feature(Y)

int k;

(b)

(c)

interface Graphics { ... }

@Feature(X)

class Line implements Graphics { ... }

@Feature(Y)

class Circle implements Graphics { ... }

(a)

@interface Feature {

static final boolean X = true;

static final boolean Y = true;

static final boolean Z = false;

boolean value();

}

Figure 1: The Feature Annota-tion Type.

R4 requires everyJava SPL to usethe custom annota-tion type Feature,which defines an SPLconfiguration. Ev-ery feature F of anSPL has a static

boolean variable F

declared inside Featurewhose value indicates whether F is selected (true) or not(false). Fig. 1 shows a Feature declaration with three fea-tures {X, Y, Z} where X and Y are selected and Z is not.The specified configuration is {X, Y, ¬Z}. Feature.java isgenerated by a SPL configuration tool, mentioned earlier.

Every Java declaration (class, method, field, etc.) andpackage in P has an optional Feature annotation with aboolean expression of Feature variables.1 If the expressionis true for a configuration, the declaration (or entire pack-age) is present in the program; otherwise it is removed. Ifa declaration has no Feature annotation, it is included inevery program of the SPL.

Fig. 2a illustrates three declarations: Graphics, Square,and Picture. Graphics belongs to every program of theSPL as it has no Feature annotation. Square is added byfeature X. Picture is added by feature Y.2

@Feature(X)

int i, j;

@Feature(Y)

int k;

(c)

interface Graphics {...}

@Feature(X)

class Square implements Graphics {...}

@Feature(Y)

class Picture implements Graphics {...}

(a) (b) @Feature(X)

int i, j, k;

2

Figure 2: Feature Annotations.

Methods and fields are annotated similarly. Fig. 2b showsa declaration of three integer variables (i, j, k), all belongingto feature X; the Feature annotation is for the entire line. Ifwe wanted variables i and j to be introduced by feature X,and k by feature Z, we would use Fig. 2c.

Feature variations in executable code are written usingif(feature expression) statements. For example, it is notuncommon in SPLs to have different bodies for a singlemethod. Suppose features X and Y are never both selected.In preprocessor-based tools, one might use Fig. 4a, where#if-#endif introduces at most one declaration of method m

1In Java, each package-level annotation is placed in apackage-info.java file.2To write @Feature(X) instead of @Feature(Feature.X), includeimport static Feature.*; in all files.

2

Page 3: Refactoring and Retrofitting Design Patterns in Java Software …dig.cs.illinois.edu/papers/refactoringSPL_ASE16.pdf · Refactoring and Retrofitting Design Patterns in Java Software

(a) SPL codebase (b) GREEN=false (folded) (c) GREEN=false (expanded)

Figure 3: Code Folding in R4.

in any program; Fig. 4b shows the cascading if-else state-ments that we use to encode the same variability using onlyone declaration for m.

#if(X)

int m() { return 1; }

#elif(Y)

int m() { return 2; }

#else

int m() { return 0; }

#endif

(a)

int m() {

if(X)

return 1;

else if(Y)

return 2;

else

return 0;

}

(b)

#if(X)

int m() { return 1; }

#elif(Y)

int m() { return 2; }

#else

int m() { return 0; }

#endif

(a) int m() {

if(X)

return 1;

else if(Y)

return 2;

else

return 0;

}

(b)

Figure 4: Encoding Different Method Bodies.

3.2 SPL Codebase ProjectionsLet C be the configuration of program PC. Let P be the

SPL codebase and ΠC(P) be its C-projection that yields PC:

ΠC(P) = PC (1)

Think of ΠC as an operation that alters the text of P toproduce the text of PC.R4 uses two projection operations, Π^

C and Π]C, that both

satisfy (1). The first, Π^C , folds lines of code in P, exposing

only the source of PC [10]. This is the source of P that R4 al-lows programmers to modify. Further, code folding providesthe important functionality that (a) shows where variationpoints in PC exist and (b) allows programmers to inspect, notedit, their folded contents. Fig. 3a shows P, Fig. 3b showsPC with folded code when GREEN = false, and Fig. 3c showsPC with unfolded code when GREEN = false.

Note: Variation points (VP) are locations where dif-ferent SPL programs may differ in source. Knowing theplacement of VPs is vital to guarantee a consistent be-havior of all variants of packages, classes, and methods;they must all be designed consistently, sharing the sameVP structure [2].

The second, Π]C, does something similar: instead of code

folding regions, it simply comments them out. Π]C(P) is fed

to the Java compiler to produce bytecode for PC; it is thiscompiled version that allows programmers to execute, de-bug, and step-through a code folded version of PC.

In summary, R4 uses Π^C to provide an editable view of PC,

and Π]C to provide the source of PC necessary for compilation,

execution, and debugging.

3.3 Improved SPL Design TechniquesWe have authored many Java SPLs. In doing so, we came

to the realization that an SPL design is a “master plan” thatall SPL programs must conform. Henceforth when we say“program” we mean a product of an SPL.

We realized that an SPL design should follow an elementnaming convention: All programs of an SPL use the samename for the same element. Here is why: Let d be a decla-ration that appears in many programs of an SPL. Supposed is given the name “dd” in some SPL programs and “d” inothers. This doubles the information a programmer needs toremember: s/he has to know when to use “dd” and when touse “d”. A decent-sized SPL can have thousands or tens ofthousands of declarations. To remember all type, method,and variable names is difficult enough, but complicating thisknowledge with name variability is untenable. Eliminatingname variability was a key requirement for (our) sanity inSPL designs.3

An ugly relative of name variability is semantic inconsis-tency : we do not want d to mean one thing in some programsand something radically different (e.g., have a fundamentallydifferent type) in others. Every declaration should have aconsistent meaning across programs of an SPL, otherwisethe scalability problems similar to name variability arise.More on this in Section 4.4.

An important consequence of eliminating name variabil-ity and semantic inconsistency is that the codebase P of anSPL always compiles. (In our case, P compiles by ignoringFeature annotations, discussed in the next section). Thecompiled version of P need not correspond to an SPL pro-gram – it is only a check that every reference can be bound tosome declaration in P [47]. If this is not the case, there maybe one program of the SPL that does not compile. The com-pilability of P is a precondition for safe composition [2, 47],discussed earlier.

Henceforth, we require the following SPL sanity constraints:S1. Absence of name variability,S2. Absence of semantic inconsistencies, andS3. Compilability of the Java SPL codebase P.

These constraints not only simplify SPL designs by elimi-nating“artificial complexity” [8], they also provide an unam-biguous way to understand the result of refactoring a JavaSPL codebase, a topic which we explore in Section 4.4.

3.4 PerspectiveReaders who are familiar with today’s SPL tools may rec-

ognize R4, as described so far, is not incremental. As said inthe Introduction, existing tools rely on preprocessing so thata program PC projected from P is literally a different code-base [3,5,23,30]. Special (preprocessing) tools are needed topropagate edits of PC back to P. R4 is different: it relies on

3There are many SPLs where products are customized by cloningand then have their own edit histories that give rise to namevariability [12]. To us, this is a bad smell or bad SPL design.

3

Page 4: Refactoring and Retrofitting Design Patterns in Java Software …dig.cs.illinois.edu/papers/refactoringSPL_ASE16.pdf · Refactoring and Retrofitting Design Patterns in Java Software

Java custom annotations and an extension of standard code-folding tools to provide editable views of SPL programs.

What existing tools lack is the ability to refactor SPLcodebases. This is the next contribution of R4 that we dis-cuss, starting with a core theorem.

4. THEOREM FOR REFACTORING SPLSFeature modules are abstractions that exist in the SPL

problem space [2]; theorems derived in the problem spacecan be mapped to an SPL solution space in many ways (e.g.,preprocessor or annotation-based implementations) that pre-serve theorem validity. This is how we developed R4: weproved a theorem about refactoring feature modules andmapped it to R4’s Java-annotation-based implementation.

ℙ 𝑃𝐶

𝑃𝐶ℰℙℰ′

Π𝐶

Π𝐶

ℰ′ ℰ

(a)

𝑃𝐶ℰ = Π𝐶 ℰ′ ℙ

= ℰ Π𝐶 ℙ

(b)

Π𝐶ℙ 𝑃𝐶

𝑃𝐶ℛℙℛ′

Π𝐶

ℛ′ ℛ

Figure 5: CoreTheorem of SPLRefactoring.

Let R be a feature-unaware refac-toring. A programmer applies R toan SPL program PC to produce PRC :

R(PC) = PRC (2)

Equation (2) does not say how theSPL codebase P should be modifiedto project PRC . Fig. 5 depicts thecore theorem of SPL refactoring as acommuting diagram [38]. Namely: C-projecting P to produce PC and thenapplying R is equivalent to a feature-aware R′-refactoring

of P, yielding PR′, and C-projecting it to produce PRC . R and

R′ are identical in terms of their code transformations, butdiffer in their preconditions. In the following, we prove thetheorem of Fig. 5 and explain how R and R′ differ.

4.1 Feature Modules and Their SumsLet F denote the set of all features of an SPL and let Fi

be the feature module for feature i. Feature modules arecomposed by the + operation [3,5]. The SPL codebase P isthe sum of all feature modules:4

P =∑i∈F

Fi (3)

And the C-projection of P, where C ⊆ F , yields PC which isthe sum of all feature modules in C:

ΠC(P) =∑

i∈C∩F

Fi =∑i∈C

Fi = PC (4)

4.2 Code Transformation of Feature ModulesFrom our experience in developing SPLs, the following

distributivity identity suggested itself: with respect to codetransformations and not their preconditions, anR-refactoringof a sum of feature modules A and B equals the sum of theeach R-refactored feature module:

R(A + B) = R(A) +R(B) (5)

The insight behind (4) is simple: provided that one observesthe sanity rules S1-S3 of Section 3.3 – common refactoringsare largely oblivious to feature module boundaries. That is,when a program P = A+B is R-refactored, one expects bothA and B to be modified by R, i.e., PR = AR+ BR.

Example: Method m in Fig. 6 is defined in class/featureA. Class/feature B has a call to m. When m is renamedto n, both features A and B are modified to AR and BR.

4Without loss of generality, feature interactions can be treated asseparate features that are summed like other features [5].

(a) before rename

class A {

void m() {...}

}

class B {

void foo(A a) {

a.m();

}

}

(b) after rename

class A {

void n() {...}

}

class B {

void foo(A a) {

a.n();

}

}

rename m→n

Figure 6: Method Rename Refactoring.

Four identities follow from (5). First, an R-refactoring of Pis the sum of all R-refactored feature modules:

R(P) = R(∑i∈F

Fi) // by (3)

=∑i∈F

R(Fi) // by (5) (6)

Second, R-refactoring a C-projection of P equals PRC :

R(ΠC(P)) = R(PC) // by (4)

= PRC // by (2) (7)

Third, PRC is the sum of its R-refactored feature modules:

PRC = R(PC) // by (2)

= R(∑i∈C

Fi) // by (4)

=∑i∈C

R(Fi) // by (5) (8)

Fourth, C-projecting an R-refactored codebase P equals PRC :

ΠC(R(P)) = ΠC(∑i∈F

R(Fi)) // by (6)

=∑i∈C

R(Fi) // by (4)

= PRC // by (8) (9)

Equations (7) and (9) prove the theorem of Fig. 5 wherethe code transformations of R and R′ are identical. We willsee in the next section that the preconditions for R and R′

are not the same, which is their distinction.The theorem is important: it tells us how to “back project”

code changes on views (namely PC) to P – whether they aretext edits or modifications made by refactorings. That is, ifR4 presents view PC of P, a programmer can invoke a refac-toring R to get R(PC) = PRC . But behind the curtains, R4is really applying R′ to P, and taking its C-projection topresent PRC to the programmer.

Note: There are situations where to correctly editPC, programmers must edit P. Example: a program-mer wants to provide a new body to an existing method.To do so, s/he must edit the P definition to achieve thedesired projection. R4 offers a GUI button for users totoggle between editing P and PC, should the need arise.

4.3 Refactoring PreconditionsEquation (9) does not take into account preconditions for

refactorings, which determines whether a refactoring can beapplied to all SPL products. We start with the rule of Liebig,et al. [32]: A refactoring R of a product line fails if R failson any program of that product line. We explain in the nextsection why we must qualify this rule.

4

Page 5: Refactoring and Retrofitting Design Patterns in Java Software …dig.cs.illinois.edu/papers/refactoringSPL_ASE16.pdf · Refactoring and Retrofitting Design Patterns in Java Software

Example: A programmer wants to edit the base pro-gram Pbase of an SPL whose codebase P is Fig. 7. Methodbar is invisible to the programmer as it belongs to fea-ture X which is unselected. If the programmer tries torename foo to bar, the rename fails. Reason: there is atleast one program in the SPL (any configuration with X)where the rename refactoring fails, even though renam-ing foo to bar in Pbase is legal.

class A {

void foo() {...}

@Feature(X)

void bar() {...}

}

@Feature(A)

class foo {

@Feature(A)

int a;

@Feature(A)

int m() {

int result = 4;

■xreturn result;

}

}

(a) Module A

■x{ if (B)

result = result*4;

}

@Feature(B)

class bar extends foo {

...

}

(b) Module B (c) Module A+B

@Feature(A)

class foo {

@Feature(A)

int a;

@Feature(B)

int m() {

int result = 4;

if (B)

result = result*4;

return result;

}

}

@Feature(B)

class bar extends foo {

...

}

Figure 7: Renamefoo to bar Fails.

SPL programmers must realizethat refactoring an SPL codebasehas more constraints than justrefactoring a single program PC. Wereport precondition failures of arefactoring R by citing a conditionor SPL configuration where it fails.This is done by ‘lifting ’ a refactor-ing precondition to a SC ψ con-straint and verifying all SPL programs satisfy ψ. Examplesof such constraints are given in Section 5.5.

So the difference between a refactoring R on a single pro-gram PC and the corresponding refactoring R′ on P is liftingpreconditions ρ of R to determine if there exists any pro-gram in the SPL that fails to satisfy ρ.

4.4 Eliminating Ambiguous DesignsWe said earlier that refactorings are largely oblivious to

feature module boundaries. Here is a case where they are notand ‘accidental’ or ad hoc polymorphism and subtype poly-morphism collide [39]. Ad hoc polymorphism arises whenmethods can be applied to arguments of different types, butbehave differently. Example: classes HandSketch and Chess

both have a draw() method with very different semantics.

+m(in d : D)

A

+m(in d : D)

B D

b.m(d)

C

b.m(d)

+m(in d : D)

A

B

+m(in a : B)

D

d.m(b)

C

b.m(d)

B.m(D) à D.m(B)

+m(in d : D)

A

+m(in d : D)

B

D

b.m(d)

b.m(d)

Figure 8: PolymorphicAmbiguity.

Consider the class hier-archy of Fig. 8. Twomutually-exclusive features,blue-hatched and red-solid,both introduce a methodm(D). These methods neverappear in the same SPLprogram, so they need nothave the same semantics– this is ad hoc polymor-phism. But any Java pro-grammer who reads theSPL codebase would instinctively expect these methods tobe related via subtype polymorphism.

What happens when method B.m(D) is moved to class D?With an ad hoc interpretation, the method is moved and thered call is updated to d.m(b). With a subtype interpretation,the method can be moved only if it leaves a delegate behindand all calls remains as b.m(d).

Our sanity constraints S1-S3 of Section 3.3 eliminate thisambiguity about which interpretation to use. If the m meth-ods are semantically different, the constraints say they mustbe given different names, and the result of a move is con-sistent with that of an ad hoc interpretation. If they aresemantically related, the constraints say they have the samename, and the result of a move is consistent with a subtypinginterpretation. Either way, R4 handles both interpretationsby asking users to follow the sanity constraints listed earlier.

In effect, we qualify Liebig’s rule [32]: Given the sanityconstraints S1-S3 of Section 3.3 are satisfied and theSPL codebase satisfies SC, a refactoring R of an SPL

fails if R fails on any program of the SPL.

5. IMPLEMENTATION5.1 R3R3 [29] is a Java refactoring engine that refactors programs

by pretty-printing Abstract Syntax Trees (ASTs). Unlikestandard engines that modify ASTs, pretty-printing neverchanges ASTs; it only displays a view of ASTs. We brieflyexplain how R3 works in this section.R3 is a Java package that presents Java declarations of

a target program as objects which a user can retrieve andmanipulate. Methods of R3 objects are (a) refactorings suchas rename and move, (b) retrievals of other R3 objects suchas get the member methods of a given class, and (c) cre-ations of other R3 objects such as add a new field to a givenclass. R3 objects are tuples in a non-persistent main-memorydatabase and are harvested from the ASTs of the program.

A refactoring script , a programmatic invocation of R3

methods, does not modify the target program’s ASTs, butinstead modifies the R3 database. For example, the rename-method refactoring updates the name field of that method’stuple. The move-method refactoring updates the “owner”field of that method’s tuple to point to R3 tuple of the new“owner” type declaration. In general, a refactoring script toinstall design patterns is a database transaction – it altersthe R3 database.

a_ref c_refb_ref

+

+

source “a + b + c”

fieldID name

a_ref a

b_ref b

c_ref c

fieldID name

a_ref x

b_ref y

c_ref z

renam

e

a_ref c_refb_ref

+

+

refactored source “x + y + z”

parse

disp

lay

Figure 9: How R3 Works.

R3 integrates thedatabase and ASTpretty-printing toproduce a refac-tored program. Fig. 9illustrates the R3

lifecycle: Sourceis parsed into anAST that refer-ences tuples inthe R3 database.A refactoring scriptrenames variablesa,b,c to x,y,z

via tuple modifi-cations. A prettyprint of the AST retrieves database tuples to display the newvariable names yielding the refactored source.

A consequence of the above and other improvements haslead to a 10× increase in R3’s performance over the Eclipserefactoring engine in terms of refactoring execution speed.

5.2 Refactoring ScriptsAlthough refactoring preconditions are altered, R4 scripts

to retrofit design patterns into programs are identical tothose of R3. In effect, refactoring scripts are feature-unaware;R4 makes R3 refactorings feature-aware with its precondi-tion checks. Fig. 10 is a makeVisitor script that retrofits aVisitor pattern into an existing program. makeVisitor in-vokes non-trivial refactorings such as change-method-signa-ture (Line 10) and move-instance-method (Line 11) as wellas a Singleton design pattern script (Line 5).

To illustrate, Fig. 11a shows a class hierarchy, rooted bythe abstract class Graphic, that consists of four classeseach with a distinct draw() method. (For now, ignore thecoloring/shading of classes). Fig. 11b sketches the Draw-

5

Page 6: Refactoring and Retrofitting Design Patterns in Java Software …dig.cs.illinois.edu/papers/refactoringSPL_ASE16.pdf · Refactoring and Retrofitting Design Patterns in Java Software

1 // member of RMethod class2 RClass makeVisitor(String N) {3 RPackage pkg = this.getPackage ();4 RClass v = pkg.newClass(N);5 RField singleton = v.addSingleton ();67 RRelativeList relatives = this.getRelatives ();8 relatives.rename("accept");9

10 int index = relatives.addparameter(singleton);11 relatives.moveAndDelegate(index);1213 v.getAllMethods ().rename("visit");1415 return v;16 }

Figure 10: R4 makeVisitor Method.

Visitor class that is produced by invoking makeVisitor onany draw() method. The method on which makeVisitor isinvoked is called a seed . Another script, undoVisitor, re-moves an existing Visitor by moving Visitor methods back totheir original classes, invoking a different set of refactorings.

+add(in Graphic)+draw()

Picture

+draw()

Triangle

+draw()

Graphic

0..

1

-contains

1..*

(a) (b)

+draw()

Square

+visit(in Picture)+visit(in Square)+visit(in Triangle)

+instance : DrawVisitor = new DrawVisitor();

DrawVisitor

Figure 11: makeVisitor Pattern.

In R4, each class has a Feature assignment. The shadingin Fig. 11a indicates that Graphic belongs to Base and eachother class belongs to a different feature. When R4 movesa declaration, it also moves its Feature annotation. In thecase of the move-and-delegate refactoring, which makeVisi-

tor uses, the created delegate has the same Feature anno-tation as its delegated method. Fig. 11b shows the preser-vation of Feature assignments of moved methods by theircoloring/shading.

5.3 R4 PipelineFig. 12 shows the execution pipeline of R4. The only dif-

ferences with R3 are the addition of steps α, β, and γ.

SPLcodebase

Eclipse ASTs +

Symbol table

AHEADASTs

Java SPL’

RefactoredSPL

codebase

(A)

(C)

(D)

(E)(F1)+(F2)+(𝛄)

(G)

R4 DB R4 DB with links to AHEAD ASTs

R4 DB with

refactoring updates

(β)

Code-folded SPL

Figure 12: R4 Pipeline.

(A) Eclipse parses a Java SPL codebase P;(B) R4 populates its database by traversing Eclipse ASTs;(α) R4 constructs feature predicates for all identifiers and

stores them in the database;(β) At a user’s request, R4 checks for dead code, validates

SC, and performs code folding for viewing, editing, anddebugging SPL programs;

(C) As Eclipse does not provide AST pretty-printers, R4

transforms the original program and(D) produces AHEAD parse trees;(E) R4 links database tuples with AHEAD ASTs;(F) A design pattern script executes (F1) precondition checks

and (F2) database updates;(γ) R4 performs replacement precondition checks needed

only for SPLs (see Section 5.5);(G) The refactored or pattern-retrofitted codebase is pro-

duced by AHEAD pretty-printing.The use of AHEAD ASTs is an artifact of R3’s imple-

mentation. Eclipse does not have the requisite AST prettyprinters; AHEAD does. In a non-prototype implementation,steps (C) and (D) would be eliminated entirely.

5.4 Dead Code and Safe Composition ChecksFeature models of SPLs are rather static; they do change

but slowly. As said in Section 2, R4 culls P for constraintsand collects a large set of theorems to prove. From the tablesof Section 6, a crude estimate is about 1 theorem for every 2lines of source. A saving grace is that the number of uniquetheorems can be orders of magnitude smaller [47].R4 leverages the stability of an SPL’s feature model by

caching the results of a theorem. When a feature-awarecondition is checked, R4 identifies the unique theorems toprove and looks in its theorem cache. Only when a previ-ously unseen theorem is encountered will a SAT solver beinvoked, and of course, its result is henceforth cached. Thecache is cleared whenever the feature model is updated. Theperformance of R4 and SC are detailed in Section 6.

5.5 Preconditions for SPL RefactoringsR3 supports 32 different primitive refactorings and uses 39

distinct primitive precondition checks, where each R3 refac-toring uses a subset of these 39 checks. R4 supports all ofR3’s primitive refactorings and preconditions.

We expected most preconditions of R3 would become feature-aware. Interestingly, only 5 of the 39 required lifting. Thereare three reasons: (1) Java annotations cannot be attachedto arbitrary code fragments, such as a Java modifier. (2) Oursanity guidelines S1-S3 eliminate ambiguities that wouldotherwise complicate precondition checks and make themfeature-aware. And (3) some preconditions are agnosticw.r.t. features (like Declaring Type and Constructorbelow). Overall, this is good: it tells us that refactoringengines for SPLs can approach the efficiency of feature-unaware refactoring engines. Our experiments in Section 6explore this conjecture.

Examples of primitive preconditions for the move-instance-method refactoring that are unaffected by features are:• Method Modifier – A method cannot be moved if it

has an abstract, native, or synchronized modifier.• Declaring Type – A method cannot be moved if its

enclosing type is an annotation or interface.• Constructor – A constructor cannot be moved.• Destination Parameter – A method with a param-

eter of class C cannot be moved to class C if one of itscalls has null as its C argument.

The last example is instructive. Assuming the offendingcall(s)-with-null are not in dead code, if such a call exists,we know at least one SPL program violates this constraint.This can be checked by a feature-unaware approach.5

5The create class refactoring has the precondition that no other class

6

Page 7: Refactoring and Retrofitting Design Patterns in Java Software …dig.cs.illinois.edu/papers/refactoringSPL_ASE16.pdf · Refactoring and Retrofitting Design Patterns in Java Software

Here are some feature-aware preconditions:• Execution Flow – Fig. 13a shows a feature-unaware

class C. It is illegal to inline method n due to thereturn statement inside n, as the i++ statement ofmethod m would never be executed. However, in thefeature-aware C of Fig. 13b inlining is allowed if fea-tures X and Y are mutually exclusive [32].• Binding Resolution – After a method is moved, SC

checks for the method must be re-verified. That is, themoved method should still be present in all programsin which it appeared before the move and all declara-tions referenced in its body are still present and visible.Fig. 13c and 13d show the before and after result ofmoving method A.m to class C. One SC check for pa-rameter type B prior to the move is green ∧ blue ⇒yellow6 and after the move (see Fig. 13d) the checkbecomes red ∧ blue⇒ yellow.

class C {

int i = 0;

void m() {

n();

i++;

}

void n() {

i = 1;

return;

}

} (a)

class C {

int i = 0;

void m() {

n();

if(red) i++;

}

@Feature(blue)

void n() {

i = 1;

return;

}

} (b)

@Feature(green)

class A {

@Feature(blue)

void m(B b, C c)

{...}

}

@Feature(yellow)

class B { }

@Feature(red)

class C { } (c)

@Feature(green)

class A { }

@Feature(yellow)

class B { }

@Feature(red)

class C {

@Feature(blue)

void m(B b, A a)

{...}

} (d)

40

Figure 13: Inline and After Move Constraint.

6. EVALUATIONWe evaluated R4 by answering three research questions:

• RQ1: Can R4 refactor Java SPLs?• RQ2: Does R4 refactor at interactive speeds?• RQ3: Is there a benefit to theorem caching?

6.1 Experimental Set-UpWe evaluated R4 by demonstrating that its scripts could

refactor SPL codebases to retrofit design patterns. Our cri-teria for determining which patterns to apply were thosethat stressed the capabilities of R4 the most. We chose themakeVisitor and undoVisitor scripts, as they invoke thelargest number of R4 refactorings, each using different setsof refactorings (e.g., undoVisitor uses inline and delete-classwhereas makeVisitor uses Singleton and create-class). R3,and thus R4, supports 18 of the 23 design patterns in [16]and the remaining 5 patterns do not benefit from automa-tion. We executed R4 scripts that invoked 2,316 primitiverefactorings to retrofit 32 makeVisitor and 32 undoVisitor

pattern instances, respectively.There are plenty of public SPLs written in the C lan-

guage [24, 32, 42]. To our astonishment, our biggest chal-lenge was finding Java SPLs. Surprisingly, there are veryfew public Java SPLs;7 most do not include regression tests,

in the same package has the same name. This precondition could in-deed be made feature-aware, but just like Destination Parameter,there is no need to do so. If such a class exists, then we know atleast one SPL program violates this constraint. A feature-unawareapproach suffices.6The presence of method m implies the presence for class C.

7In 2004, AHEAD [5] had a larger codebase size than any existing

Java SPL, but (a) it was not in a form that we could easily use – wedo use one program (Mixin) from the AHEAD Tool Suite and (b) wewanted to use as many Java SPLs as we could that we did not author.

which we need to verify that the original program behavioris preserved by R4 refactorings.

We eventually found 8 Java SPLs. Three (AHEAD, Calcu-ator, and Elevator) had regression tests that we could use.Two (Notepad and Sodoku) lacked regression tests but couldbe checked by manually invoking their GUIs before and afterrunning R4 scripts to verify behavior preservation. The re-maining three (Lampiro, MobileMedia, and Prevayler) alsolacked regression tests. We did not know how to executethem, so we could only verify that they compiled withouterrors before and after refactoring.

We selected “seed” methods for makeVisitor that createdthe largest Visitors in terms of the number of“visit”methodscollected in the Visitor class. Since the number of refactor-ings needed to make a Visitor is proportional to the size ofVisitor (the number of “visit” methods), large-sized Visitorswere appropriate for R4 evaluations.

We used an Intel CPU i7-2600 3.40GHz, 16 GB mainmemory, Windows 7 64-bit OS, and Eclipse JDT 4.4.2 (Luna)in our work.

6.2 Results6.2.1 Table Organization

Table 1 shows the results of makeVisitor. The first col-umn lists the eight target programs along with their linesof code, number of regression tests, and number of features.Each row is an experiment that corresponds to makeVisitor

applied to a distinct “seed” method in the Seed ID column.The third column, # of Refs, is the total number of refac-torings executed to make a Visitor for the “seed” method.

Each of our SPLs has a ‘max’ configuration – all featuresare selected. We let R3 execute the same refactoring scripton the ‘max’ configuration program of each SPL to estimatethe overhead of R4 w.r.t.R3. The next six columns show thetimes spent on each R3 pipeline step of Section 5.3:8

• Bld DB (B): time to build the R3 database by harvest-ing type information from Eclipse ASTs and symboltables.• Link AST (E): time to link AHEAD AST nodes withR3 database tuples.• Pre Chk (F1): time to check feature-unaware precon-

ditions.• DB Upd (F2): time to execute an R3 script which

updates the R3 database.• Proj (G): time to write the refactored code to files.• Tot (R3T): total time in pipeline stages (B), (E), (F1),

(F2), and (G).The next three columns list the extra computations neededfor feature-aware refactorings in R4:• Pred Coll (α): time to collect presence conditions on

all declarations and references.• Ext Prec (γ): time spent on SAT invocations to check

feature-aware preconditions and the time when cachingSAT solutions.• Tot: total time of (R3T) + (α) + (γ) with/without

caching.By comparing the total times using R3 and R4, we estimatethe overhead of feature-aware refactorings in our experi-ments, the subject of the last column:

8We used profiling tool VisualVM (ver. 1.3.8) [49] to measure CPU

times in running the R4 scripts. We repeated each experiment fivetimes and report the average execution time.

7

Page 8: Refactoring and Retrofitting Design Patterns in Java Software …dig.cs.illinois.edu/papers/refactoringSPL_ASE16.pdf · Refactoring and Retrofitting Design Patterns in Java Software

• Overhead: the overhead percentage in terms of exe-cution time with/without caching.

Table 2 lists the results of undoVisitor which has an iden-tical tabular structure.

6.2.2 Answers to Research Questions

RQ1: Can R4 refactor Java SPLs? R4 successfully ap-plied 64 design pattern instances on our SPLs using a totalof 2,316 R4 refactorings; 32 added a visitor pattern and 32removed a visitor. The most challenging experiments, A5 inTables 1 and 2, executed 552 primitive refactorings, respec-tively. Most other experiments required fewer as they havefewer ‘visit’ methods (see Fig. 11).

Our conclusion: R4 can indeed refactor SPL codebases.

RQ2: Does R4 refactor at interactive speeds? Toevaluate R4 refactoring speed, we used three measures:

1. Consider the execution times for R4 for all makeVisi-tor and undoVisitor experiments. The largest R4 ex-periment, Row A5, took a mere 4.8 seconds. The com-parable experiment using R3 took 3.7 seconds. (Fora perspective on R3’s improvement over the Eclipserefactoring engine, a comparable refactoring to A5 tookEclipse 298 seconds to execute [28].)Row L5 also took 4.8 seconds; the comparable experi-ment using R3 took 3.7 seconds. The numbers for un-

doVisitor in Table 2 are similar. For less demandingscripts – remember: not individual refactorings – (pickany non-A or non-L row), all R4 executions completein under 1.4 seconds; the corresponding R3 executionsfinish in under 1 second. On average across all experi-ments, R4 was 36% slower (i.e., .5 seconds slower) thanR3 per experiment.

2. Database creation time is small for R3; the largest ex-periments (A and L rows) take less than 2 seconds.R4 additionally collects feature presence predicates col-umn (γ); this adds one more second of execution timefor the largest SPLs. For smaller SPLs, R4 and R3

database build times are indistinguishable. For a per-spective, between the time a user clicks the EclipseGUI and the list of available scripts is displayed, an R4

database can be created with time to spare.3. Over 80% of Eclipse refactoring execution time is con-

sumed by checking preconditions [29]. In contrast,R3 precondition checking is almost nothing (see col-umn (F1) and [29]). R4 takes advantage of R3’s speed,but spends extra time for feature-aware preconditionchecks. They do indeed incur additional overhead (seecolumn (γ)). In the largest SPLs, this adds another1.2 seconds without theorem caching. As before, forsmaller SPLs, the additional time is unnoticeable.

Our conclusion: R4 refactors SPLs at interactive speeds.

RQ3: Is there a benefit to theorem caching? Toanswer this question, we used two measures:

1. The average overhead for checking feature-aware pre-conditions in the makeVisitor experiment was 41%without caching SAT solutions. With caching, the av-erage overhead dropped to 32%. For a perspective,experiment L5 spent 1.2 seconds proving 1,294 theo-rems, a vast majority of which were duplicates. Withcaching, only one extra theorem required a SAT proof,taking 0.07 seconds.

2. Table 3 shows the time and number of SAT problems

for dead code and SC checks on the SPLs in Table 1.P satisfying SC and being dead code free is a precondi-tion for R4 refactorings and it is meaningless to performrefactorings on uncompilable source. Again, we tooktwo different approaches (non-caching and caching) tomeasure how much time R4 can save by reusing SATsolutions. On average for our experiments, caching in-creased the speed of dead code checks by 1.03× andSC by 15×.

Table 2 shows the result of our undoVisitor experiment.Although the total # of refactorings needed for undoVis-

itor is equal to that of makeVisitor, the set of refactor-ing types and corresponding R4 scripts are different and thenumber of SAT problems to solve for undoVisitor is slightlylarger than that of makeVisitor. On average, the overheadfor feature-awareness in undoVisitor refactorings was 38%without caching and 32% with caching.

Readers may be surprised at the response time of our SATinvocations. This is due to the fact that the feature modelsof our SPLs are uncomplicated. And the theorems to beproven are also simple. Having said this, our observationsare consistent with prior work that SAT problems for featuremodels are ‘easy’ [36].

Our conclusion: theorem caching is beneficial.

6.3 Threats to ValidityWe would have preferred all SPLs to have regression-tests,

with larger codebases, and with feature models with hugesets of products – characteristics of large SPLs. Such SPLswere simply unavailable to us.

#ifdef X

package p;

import q.B;

class A {

B b;

}

#endif (a)

package p;

import q.B;

@Feature(X)

class A {

B b;

}

package p;

@Feature(X)

class A {

q.B b;

}(b) (c)

34

Figure 14: Translation javapp to@Feature Annotations.

The applicationsin Table 1 usejavapp to spec-ify features [22].In order to usethem, we had toreformat javapp

to Java customannotations by hand.We did our bestto keep the origi-nal feature specification but there were some code fragmentsthat required special care. Fig. 14a shows a compilation unitbelonging to feature X using javapp. As imports cannot beannotated in Java, we assigned feature X to the class decla-ration A in Fig. 14b. However, in case that class B belongsto X, Fig. 14b violates SC: it is an error in Java to import anon-existent class. Our solution was to use the fully qualifiedname instead as shown in Fig. 14c.

7. RELATED WORKKim et al. [29] report a user study that showed undergrad-

uate students could write R3 scripts (R4 scripts are no differ-ent) and writing scripts significantly improved the successrate over that of manual refactoring. As our work leveragesR3, R4 inherits its benefits.

Conditional compilation in Java has taken at least twoforms: preprocessors, such as [22,40,43], and OO language-extensions to support type safe variability, such as [4,13,21].These latter papers are elegant proposals to extend OO lan-guages with conditionals to enable static variability and typesafety using generics. Although this remains an active andbasic area, work on variability-aware compilers seems more

8

Page 9: Refactoring and Retrofitting Design Patterns in Java Software …dig.cs.illinois.edu/papers/refactoringSPL_ASE16.pdf · Refactoring and Retrofitting Design Patterns in Java Software

Application(LOC, #Tests,#Features)

SeedID

#ofRefs

R3 Time (seconds) R4 Time (seconds) OverheadBld Link Pre DB

Proj TotPred SAT Caching

DB AST Chk Upd Coll No Yes No Yes No Yes(B) (E) (F1) (F2) (G) (R3T) (α) Ext Prec (γ) Tot=(R3T)+(α)+(γ)

AHEADmixin(26K, 56, 120)

A1 54 0.000 0.026 0.191 2.016 0.092 [104] 0.035 [2] 3.074 3.017 52% 50%A2 56 0.000 0.020 0.100 1.919 0.075 [56] 0.030 [2] 2.960 2.915 54% 52%A3 58 1.712 0.087 0.000 0.030 0.460 2.289 0.966 0.110 [133] 0.040 [9] 3.365 3.295 47% 44%A4 124 0.000 0.030 0.250 2.079 0.087 [128] 0.031 [3] 3.132 3.076 51% 48%A5 552 0.020 0.221 1.601 3.641 0.170 [287] 0.040 [3] 4.777 4.647 31% 28%

Calculator(312, 17, 6)

C1 4 0.000 0.006 0.046 0.235 0.036 [17] 0.030 [3] 0.316 0.310 34% 32%C2 4 0.176 0.007 0.000 0.003 0.070 0.256 0.045 0.030 [4] 0.030 [4] 0.331 0.331 29% 29%C3 4 0.000 0.003 0.060 0.246 0.026 [3] 0.030 [3] 0.317 0.321 29% 30%

Elevator(973, 6, 6)

E1 4 0.000 0.003 0.071 0.360 0.020 [3] 0.030 [2] 0.466 0.476 29% 32%E2 4 0.263 0.023 0.000 0.003 0.067 0.356 0.086 0.026 [4] 0.030 [2] 0.468 0.472 31% 33%E3 4 0.000 0.003 0.072 0.361 0.026 [3] 0.020 [2] 0.473 0.467 31% 29%

Notepad(1192, 21*, 27)

N1 4 0.000 0.003 0.250 0.595 0.060 [28] 0.033 [3] 0.785 0.758 32% 27%N2 4 0.325 0.017 0.000 0.001 0.350 0.693 0.130 0.030 [2] 0.030 [2] 0.853 0.853 23% 23%N3 4 0.000 0.003 0.333 0.678 0.030 [5] 0.033 [3] 0.838 0.841 24% 24%

Sudoku(1975, 22*, 6)

S1 4 0.000 0.004 0.055 0.383 0.035 [8] 0.030 [1] 0.574 0.569 50% 49%S2 4 0.307 0.017 0.000 0.004 0.053 0.381 0.156 0.038 [18] 0.040 [1] 0.575 0.577 51% 51%S3 6 0.000 0.006 0.063 0.393 0.048 [47] 0.033 [2] 0.597 0.582 52% 48%

Lampiro(44K, 0, 16)

L1 16 0.000 0.010 1.430 3.284 0.099 [147] 0.030 [1] 4.043 3.974 23% 21%L2 26 0.001 0.020 1.002 2.867 1.164 [937] 0.050 [3] 4.691 3.577 63% 25%L3 26 1.743 0.101 0.000 0.020 1.470 3.334 0.660 0.106 [207] 0.030 [1] 4.100 4.024 23% 21%L4 32 0.003 0.019 1.033 2.899 0.874 [723] 0.050 [1] 4.433 3.609 53% 24%L5 42 0.001 0.029 1.748 3.622 1.160 [1294] 0.066 [2] 5.442 4.348 50% 20%

MobileMedia(4653, 0, 7)

M1 6 0.000 0.005 0.139 0.657 0.077 [79] 0.030 [2] 0.874 0.827 33% 26%M2 6 0.000 0.005 0.115 0.633 0.043 [16] 0.033 [3] 0.816 0.806 29% 27%M3 6 0.486 0.027 0.000 0.005 0.117 0.635 0.140 0.041 [14] 0.033 [3] 0.816 0.808 29% 27%M4 8 0.000 0.005 0.132 0.650 0.073 [76] 0.026 [2] 0.863 0.816 33% 26%M5 34 0.000 0.015 0.222 0.750 0.194 [432] 0.061 [18] 1.084 0.951 45% 27%

Prevayler(8009, 0, 5)

P1 10 0.000 0.008 0.042 0.920 0.041 [26] 0.030 [1] 1.284 1.273 40% 38%P2 10 0.000 0.007 0.044 0.921 0.039 [26] 0.030 [1] 1.283 1.274 39% 38%P3 10 0.831 0.039 0.000 0.007 0.079 0.956 0.323 0.039 [22] 0.040 [3] 1.318 1.319 38% 38%P4 16 0.000 0.007 0.076 0.953 0.036 [17] 0.026 [1] 1.312 1.302 38% 37%P5 16 0.000 0.007 0.073 0.950 0.038 [17] 0.030 [1] 1.311 1.303 38% 37%

– * indicates the regression tests done by invoking user interface operations manually.– N of [N] indicates the number of SAT problems solved for extra precondition checks.

Table 1: makeVisitor Results

SeedID

#ofRefs

R3 Time (seconds) R4 Time (seconds) OverheadBld Link Prec DB

Proj TotPred SAT Caching

DB AST Chk Upd Coll No Yes No Yes No Yes(B) (E) (F1) (F2) (G) (R3T) (α) Ext Prec (γ) Tot=(R3T)+(α)+(γ)

A1V 54 1.714 0.091 0.000 0.010 0.180 1.995 0.912 0.142 [208] 0.026 [2] 3.049 2.933 53% 47%A2V 56 1.736 0.093 0.000 0.010 0.086 1.925 0.924 0.097 [112] 0.030 [2] 2.946 2.879 53% 50%A3V 58 1.700 0.090 0.000 0.010 0.447 2.247 0.910 0.129 [191] 0.040 [7] 3.286 3.197 46% 42%A4V 124 1.613 0.106 0.000 0.010 0.226 1.955 0.874 0.160 [254] 0.030 [3] 2.989 2.859 53% 46%A5V 552 1.478 0.101 0.010 0.010 1.490 3.089 0.693 0.350 [841] 0.058 [3] 4.132 3.840 34% 24%

C1V 4 0.155 0.007 0.000 0.001 0.050 0.213 0.026 0.040 [19] 0.030 [3] 0.279 0.269 31% 26%C2V 4 0.150 0.010 0.000 0.001 0.070 0.231 0.023 0.046 [9] 0.033 [4] 0.300 0.287 30% 24%C3V 4 0.158 0.009 0.000 0.001 0.060 0.228 0.046 0.035 [4] 0.033 [3] 0.309 0.307 36% 35%

E1V 4 0.255 0.013 0.000 0.001 0.060 0.330 0.073 0.030 [6] 0.026 [2] 0.433 0.429 31% 30%E2V 4 0.250 0.010 0.000 0.001 0.070 0.332 0.070 0.030 [6] 0.030 [2] 0.432 0.432 30% 30%E3V 4 0.260 0.011 0.000 0.001 0.065 0.338 0.059 0.030 [6] 0.023 [2] 0.427 0.420 26% 24%

N1V 4 0.280 0.015 0.000 0.001 0.250 0.546 0.125 0.060 [28] 0.040 [4] 0.731 0.711 34% 30%N2V 4 0.263 0.020 0.000 0.001 0.020 0.304 0.126 0.030 [3] 0.036 [3] 0.460 0.466 51% 53%N3V 4 0.273 0.020 0.000 0.001 0.043 0.337 0.120 0.040 [6] 0.040 [3] 0.497 0.497 47% 47%

S1V 4 0.325 0.020 0.000 0.001 0.050 0.396 0.170 0.030 [9] 0.030 [1] 0.596 0.596 51% 51%S2V 4 0.320 0.025 0.000 0.001 0.050 0.396 0.175 0.030 [9] 0.035 [1] 0.601 0.606 52% 53%S3V 6 0.340 0.020 0.000 0.001 0.070 0.431 0.173 0.050 [65] 0.036 [3] 0.654 0.640 52% 48%

L1V 16 1.701 0.155 0.000 0.010 1.415 3.281 0.632 0.080 [90] 0.028 [1] 3.993 3.941 22% 20%L2V 26 1.690 0.170 0.001 0.009 1.003 2.873 0.639 0.246 [552] 0.050 [3] 3.758 3.562 31% 24%L3V 26 1.750 0.090 0.000 0.010 1.476 3.326 0.637 0.105 [165] 0.036 [1] 4.068 3.999 22% 20%L4V 32 1.765 0.106 0.002 0.008 1.055 2.936 0.630 0.301 [780] 0.050 [1] 3.867 3.616 32% 23%L5V 42 1.849 0.113 0.003 0.007 1.766 3.738 0.661 0.395 [1020] 0.053 [1] 4.794 4.452 28% 19%

M1V 6 0.480 0.025 0.000 0.002 0.135 0.642 0.150 0.073 [92] 0.032 [6] 0.865 0.824 35% 28%M2V 6 0.470 0.026 0.000 0.001 0.110 0.607 0.165 0.050 [18] 0.030 [6] 0.822 0.802 35% 32%M3V 6 0.490 0.023 0.000 0.003 0.106 0.622 0.150 0.040 [18] 0.030 [6] 0.812 0.802 31% 29%M4V 8 0.510 0.021 0.000 0.002 0.130 0.663 0.166 0.090 [93] 0.040 [2] 0.919 0.869 39% 31%M5V 34 0.525 0.020 0.000 0.006 0.273 0.824 0.150 0.266 [549] 0.064 [20] 1.240 1.038 50% 26%

P1V 10 0.745 0.026 0.000 0.002 0.030 0.803 0.370 0.040 [37] 0.025 [1] 1.213 1.198 51% 49%P2V 10 0.740 0.033 0.000 0.001 0.030 0.804 0.365 0.040 [37] 0.030 [1] 1.209 1.199 50% 49%P3V 10 0.760 0.030 0.000 0.001 0.070 0.861 0.390 0.035 [27] 0.033 [3] 1.286 1.284 49% 49%P4V 16 0.790 0.028 0.000 0.002 0.070 0.890 0.356 0.040 [27] 0.033 [1] 1.286 1.279 44% 44%P5V 16 0.765 0.030 0.000 0.001 0.065 0.861 0.366 0.040 [27] 0.030 [1] 1.267 1.257 47% 46%

– N of [N] indicates the number of SAT problems solved for extra precondition checks.– X#V indicates the application where a Visitor is retrofitted into Seed ID X# of Table 1.

Table 2: undoVisitor Results

AppNon-caching Caching Speed Up

Dead Safe Dead Safe Dead SafeCode Composition Code Composition Code Composition

A 1.179 [182] 94.675 [19,811] 1.130 [176] 4.210 [62] 1.04 (-6) 22.46 (-19,749)

C 0.110 [42] 0.140 [108] 0.100 [39] 0.080 [9] 1.10 (-3) 1.75 (-99)

E 0.230 [158] 0.256 [676] 0.229 [155] 0.130 [16] 1.00 (-3) 1.97 (-660)

N 0.380 [188] 0.497 [635] 0.470 [188] 0.240 [86] 0.81 (0) 2.07 (-549)

S 0.285 [79] 0.426 [854] 0.300 [64] 0.250 [14] 0.95 (-15) 1.70 (-840)

L 0.780 [138] 6.741 [29,501] 0.680 [62] 1.260 [11] 1.15 (-76) 5.35 (-29,490)

M 0.362 [125] 0.869 [1,976] 0.270 [87] 0.220 [25] 1.34 (-38) 3.95 (-1,951)

P 0.445 [94] 1.244 [3,329] 0.470 [88] 0.470 [12] 0.95 (-6) 2.65 (-3,317)

– N of [N] indicates the number of SAT problems solved for extra precondition checks.– N of (N) indicates the number of SAT problems which do not need to solve againfor the sake of caching.

Table 3: Dead Code and Safe Composition Check Results

9

Page 10: Refactoring and Retrofitting Design Patterns in Java Software …dig.cs.illinois.edu/papers/refactoringSPL_ASE16.pdf · Refactoring and Retrofitting Design Patterns in Java Software

active today [25,32,50]. In contrast, R4 requires no changesto Java and directly supports feature-variability for viewediting, view compilation, and view refactoring, a combina-tion of capabilities that elude preprocessor-based tools.

Among the capabilities of projection that R4 does not sup-port is the removal of method parameters that are Feature-annotated [23,32]. Consider Fig. 15a. Parameter a is Feature-annotated, meaning that it is removed if X is not a featureof the target configuration. Fig. 15b shows the projectedresult when ¬X holds.

void m( @Feature(X) A a ) {...}

void m( ) {...}

(a)

(b)

42

Figure 15: Parameter Removal by Projection.

R4 does not support method parameter projection; it simplyignores any Feature annotations on parameters of methodsor generics. We are unconvinced that parameter projectionis a good idea. It is a ugly variant of our sanity checks S1-S3: if method m has 2 parameters in some SPL programs,3 in others, and 4 in the remainder, it quickly becomes un-tenable to know which version to use and when – especiallyif there are many methods like m. There is nothing in R4

that precludes parameter projection other than increasedcomplexity; we leave its necessity for others to decide.

In Section 6 we said there are few large public SPL code-bases. Those that are available are written in C with CPPdirectives. Developing tools to parse C-with-CPP sourceto analyze the impact of feature variability is extraordinar-ily difficult and beyond the capabilities of most researchers[9,24], but are unavoidable if these codebases are to be ana-lyzed. Most of the effort in parsing C-with-CPP deals withthe artificial complexity that CPP brings to C [17,18]. Andusing these tools is not without effort – the codebase mustuse disciplined annotations [33]. Further, as OO languagesexpose more program structure than C, the number of OOrefactorings [14] is considerably larger than that for C [17].Morpheus [32], the first refactoring tool for C-with-CPP, of-fers three refactorings (rename, lift, and inline).

#ifdef X

int global;

#else

bool global;

#endif (a)

#ifdef X

int global = 1

#else

int global = 0;

#endif (b)

int global;

{ if(X)

global = 1;

else

global = 0;

} (c)

22

Figure 16: C-Preprocessor vs Java SPL Idioms.

Another important source of complexity in C-based SPLsare violations of our sanity constraints S1-S3. Example:Fig. 16a shows a common CPP idiom that violates S2: fieldglobal has type int when feature X is defined, otherwiseit is a bool. Our solution is either to use different variablenames or give global a single type. Fig. 16b shows anothercommon CPP idiom for initializing a variable; we expresssame idea in a slightly more verbose way in Fig. 16c. Wehope/believe that future SPLs will be developed with mod-ern OO languages that eschew such artificial complexities.

Kuhlemann et al. [31] proposed Refactoring Feature Mod-ules (RFMs). Just as we use the term feature modules tomean building-blocks of SPL programs, an RFM is a featuremodule or a single program refactoring (e.g., not a refactor-ing script). An RFM refactoring is feature-unaware and isapplied to a feature-unaware program to it to the interfaceneeded by a legacy application. Although RFMs have aname that is suggestive of our work, it does not deal withfeature-aware refactorings.

Aspect-aware refactorings [1, 19, 37, 51] are a counterpartto feature-aware refactorings in this paper. The technicalissues and solutions explored were specific to AspectJ (e.g.,pointcuts and wild-cards), and are not the topics of ourwork: refactoring feature-based Java SPLs, back-propagationof Java program edits, and refactoring scripts.

8. CONCLUSIONSRefactoring is a staple of Java program development. It

should be a staple of Java SPL development too. R4 is atool that not only brings critical refactoring support to JavaSPLs, it also solves four other vexing problems: (1) propaga-tion of edits and refactorings of SPL programs back to theSPL codebase, (2) scripting refactorings to automaticallyretrofit SPL codebases with design patterns, (3) not requir-ing language extensions to Java or a special variability-awarecompiler; the standard Java compiler will suffice, and (4) ef-ficiency: although R4 is between 20%-50% slower than R3,a state-of-the-art feature-unaware refactoring engine (whichis 10× faster than the Eclipse refactoring engine), our ex-periments showed that a factor of 50% slower is barely morethan a second for large refactoring tasks.R4 leverages practical experiences in years of Java SPL

construction (which we called ‘sanity constraints’ that mightotherwise be ‘best practice’ techniques) to eliminate artificialcomplexities in SPL design. It also leverages a theorem forrefactoring feature-based SPLs that reveals a fundamentaldistributivity property (refactorings distribute over featuremodule compositions) that makes R4 concepts and imple-mentation clean. R4 is a mere 10K Java LOC.R4 may remedy the awful situation where there are few

examples of public Java SPLs to analyze. R4 is an advancein Java SPL tooling and theory, and should encourage thedeployment of Java-based SPLs in the future.

9. REFERENCES[1] P. Anbalagan and T. Xie. Automated Inference of

Pointcuts in Aspect-Oriented Refactoring. In ICSE,2007.

[2] S. Apel, D. Batory, C. Kastner, and G. Saake.Feature-Oriented Software Product Lines. Springer,2013.

[3] S. Apel, C. Kastner, and C. Lengauer. Featurehouse:Language-independent, automated softwarecomposition. In ICSE, 2009.

[4] J. A. Bank, A. C. Myers, and B. Liskov.Parameterized Types for Java. In POPL, 1997.

[5] D. Batory, J. Sarvela, and A. Rauschmayer. ScalingStep-Wise Refinement. IEEE TSE, June 2004.

[6] K. Beck and C. Andres. Extreme ProgrammingExplained: Embrace Change (2nd Ed.).Addison-Wesley, 2004.

[7] M. Boshernitsan and S. L. Graham. iXj: InteractiveSource-to-Source Transformations for Java. InOOPSLA Companion, 2004.

[8] F. P. Brooks. The Mythical Man-Month: Essays onSoftware Engineering. Addison-Wesley, 1975.

[9] S. C. Charles Simonyi, Magnus Christerson.Intentional Software. In ONWARD! OOPSLA, 2006.

[10] Code Folding.https://en.wikipedia.org/wiki/Code folding.

10

Page 11: Refactoring and Retrofitting Design Patterns in Java Software …dig.cs.illinois.edu/papers/refactoringSPL_ASE16.pdf · Refactoring and Retrofitting Design Patterns in Java Software

[11] K. Czarnecki and K. Pietroszek. VerifyingFeature-based Model Templates AgainstWell-formedness OCL Constraints. In GPCE, 2006.

[12] Y. Dubinsky, J. Rubin, T. Berger, S. Duszynski,M. Becker, and K. Czarnecki. An Exploratory Studyof Cloning in Industrial Software Product Lines. InCSMR, 2013.

[13] B. Emir, A. Kennedy, C. Russo, and D. Yu. Varianceand Generalized Constraints for C# Generics. InECOOP, 2006.

[14] M. Fowler, K. Beck, J. Brant, W. Opdyke, andD. Roberts. Refactoring: Improving the Design ofExisting Code. Addison-Wesley, 2000.

[15] J. A. Galindo, D. Benavides, and S. Segura. DebianPackages Repositories as Software Product LineModels. Towards Automated Analysis. In ACoTA,2010.

[16] E. Gamma, R. Helm, R. Johnson, and J. Vlissides.Design patterns: Elements of ReusableObject-Oriented Software. Addison-Wesley, 1995.

[17] A. Garrido. Software Refactoring Applied to CProgramming Language. PhD thesis, University ofIllinois at Urbana-Champaign, 2000.

[18] A. Garrido and R. Johnson. Challenges of RefactoringC Programs. In IWPSE, 2002.

[19] J. Hannemann. Aspect-Oriented Refactoring:Classification and Challenges. In AOSD, 2006.

[20] M. Hills, P. Klint, and J. J. Vinju. Scripting aRefactoring with Rascal and Eclipse. In WRT, 2012.

[21] S. S. Huang, D. Zook, and Y. Smaragdakis. cJ:Enhancing Java with Safe Type Conditions. In AOSD,2007.

[22] Java Comment Preprocessor . https://github.com/raydac/java-comment-preprocessor.

[23] C. Kastner, S. Apel, and M. Kuhlemann. Granularityin software product lines. In ICSE, 2008.

[24] C. Kastner, P. G. Giarrusso, T. Rendel, S. Erdweg,K. Ostermann, and T. Berger. Variability-awareParsing in the Presence of Lexical Macros andConditional Compilation. In OOPSLA, 2011.

[25] C. Kastner, P. G. Giarrusso, T. Rendel, S. Erdweg,K. Ostermann, and T. Berger. Variability-awareParsing in the Presence of Lexical Macros andConditional Compilation. In OOPSLA, 2011.

[26] C. Kastner, K. Ostermann, and S. Erdweg. AVariability-aware Module System. In OOPSLA, 2012.

[27] J. Kerievsky. Refactoring to Patterns. Addison-Wesley,2006.

[28] J. Kim, D. Batory, and D. Dig. Scripting ParametricRefactorings in Java to Retrofit Design Patterns. InICSME, 2015.

[29] J. Kim, D. Batory, D. Dig, and M. Azanza. Improvingrefactoring speed by 10x. In ICSE, 2016.

[30] C. Krueger and et al. BigLever Gears Tool.http://www.biglever.com/solution/product.html.

[31] M. Kuhlemann, D. Batory, and S. Apel. RefactoringFeature Modules. In ICSR, 2009.

[32] J. Liebig, A. Janker, F. Garbe, S. Apel, andC. Lengauer. Morpheus: Variability-aware Refactoringin the Wild. In ICSE, 2015.

[33] J. Liebig, C. Kastner, and S. Apel. Analyzing the

Discipline of Preprocessor Annotations in 30 MillionLines of C Code. In AOSD, 2011.

[34] R. Lotufo, S. She, T. Berger, K. Czarnecki, andA. W ↪asowski. Evolution of the Linux KernelVariability Model. In SPLC, 2010.

[35] R. C. Martin. Agile Software Development: Principles,Patterns, and Practices. Prentice Hall, 2003.

[36] M. Mendonca, A. W ↪asowski, and K. Czarnecki.SAT-based Analysis of Feature Models is Easy. InSPLC, 2009.

[37] M. P. Monteiro and J. a. M. Fernandes. AnIllustrative Example of Refactoring Object-orientedSource Code with Aspect-oriented Mechanisms. Softw.Pract. Exper., April 2008.

[38] B. Pierce. Basic Category Theory for ComputerScientists. MIT Press, 1991.

[39] Polymorphism (Computer Science). https://en.wikipedia.org/wiki/Polymorphism (computer science).

[40] Prebop Preprocessor. http://prebop.sourceforge.net/.

[41] I. Schafer, L. Bettini, F. Damiani, and N. Tanzarella.Delta-oriented programming of software product lines.In SPLC, 2010.

[42] J. Sincero, H. Schirmeier, W. Schroder-Preikschat,and O. Spinczyk. Is The Linux Kernel a SoftwareProduct Line? In SPLC-OSSPL, 2007.

[43] SLASHDEV Preprocessor.http://www.slashdev.ca/javapp/.

[44] Splc product line hall of fame.http://splc.net/fame.html.

[45] F. Steimann and J. von Pilgrim. Constraint-BasedRefactoring with Foresight. In ECOOP, 2012.

[46] System Generation. https://en.wikipedia.org/wiki/System Generation (OS).

[47] S. Thaker, D. Batory, D. Kitchin, and W. Cook. SafeComposition of Product Lines. In GPCE, 2007.

[48] L. Tokuda and D. Batory. Evolving Object-OrientedDesigns with Refactorings. In ASE, 1999.

[49] VisualVM 1.3.8. http://visualvm.java.net/.

[50] L. Wanner and et al. NSF Expedition onVariability-Aware Software: Recent Results andContributions. Information Technology, 2015.

[51] J. Wloka, R. Hirschfeld, and J. Hansel.Tool-supported Refactoring of Aspect-orientedPrograms. In AOSD, 2008.

11