Refactorings 1. model refactoring 2. object oriented refactorings structural equalities in object oriented programs OBJECT ORIENTED REFACTORINGS Ref2-1 Introduction • A refactoring is a semantics-preserving transformation that maps an input program to an output program • “behavior” of the program is the same – at least, that is the goal… • structure should be cleaner or easier to understand or extend • Now known that refactorings apply to all program representations • like model refactorings of the last lecture Ref2-2 Fowler’s Text and Web Site • Fowler has collected and described a large number of common OO refactorings • Some covered in this lecture • Others, not part of Fowler’s collection, are also covered • See: refactoring.com/catalog/index.html Ref2-3 Big Picture • A refactoring is an endogenous transformation domain, codomain are the same • should be bi-directional – it has an inverse • some directions are harder to implement than others • each direction is usually given a distinct name • A refactoring should be a theorem • on the semantic equivalence of the input and output diagrams • not something to be “liked” or “disliked” • interestingly, correctness proofs for refactorings are scant • rely on programming language semantics, which makes proofs hard • you must experiment to understand the semantics of a refactoring yuck • consequently – expect to find refactoring engines that introduce errors Ref2-4 Refactoring Tools • IDEs offer some built-in refactorings • NetBeans IDE has at least these refactorings see figure to right • other IDEs may have more • Most refactorings are NOT supported by IDEs • Fowler’s text has > 70 • there are many, many more… Ref2-5 So What? • Programming methodologies based around refactorings • Extreme Programming (XP) • Agile Software Development • natural part of maintaining or beautifying OO programs • Refactorings automate common programming tasks that are tedious and error prone • “Design first, code later” is alternative • code evolves over time; connection of the code to the design slowly fades • software decay – “code slowly sinks from engineering to hacking” • Refactoring is the opposite of this practice • transforming bad code into better code… • can never transform bad code into great code…. Ref2-6 Extreme Programming • Idea to incrementally build your program using refactorings and extensions • Kerievsky’s text is a practical example of what we talk about in this course – how composing refactorings yield design patterns Ref2-7 Big Picture • Refactorings with • Refactorings with are supported by NetBeans are in Fowler’s catalog Ref2-8 how individual methods evolve structurally BASIC METHOD REFACTORINGS Ref2-9 Refactoring • This is a radically new way to think about refactorings • but I think it is also a fundamental way (to implement refactoring engines) • Think of a C function declaration – not Java but C! and a call to this function: int foo(B b, C c, D d) { /* body */ } foo(b1,c1,d1) Ref2-10 Move Method Refactoring int foo(B b, C c, D d) { return b.x+c.x+d.x; } foo(b1,c1,d1) • How would this function definition and call appear if foo was in class Java Declaration Java this Java call B int foo(C c, D d) { ret x+c.x+d.x } b b1.foo(c1,d1) D int foo(B b, C c) { ret b.x+c.x+x } d d1.foo(b1,c1) E unrelated static int foo(B b, C c, D d) { return b.x+c.x+d.x; } - E.foo(b1,c1,d1) • This is how to think about a method move refactoring • when you move a method B.foo(C,D) to class D, it becomes D.foo(B,C) • and all calls to foo are updated in the above way • Q: from the above, what constraint would preclude the above move? • Why is tool support necessary? Ref2-11 Move B.biff(A) A.biff(B) after move • Here is the Eclipse project from which this video is taken – you do it yourself Ref2-12 Change Method Signature Refactoring • Add a parameter (A a) to foo( … ) – must supply a default argument a1 yields int foo(B b, C c, D d) d, A a) { return b.x+c.x+d.x; } ...foo(b1,c1,d1,a1)... ...foo(b1,c1,d1)... • Remove an unused parameter (A a) from the above-modified foo( … ) yields int foo(B b, C c, D d) { return b.x+c.x+d.x; } ...foo(b1,c1,d1)... Ref2-13 Change Method Signature • Special case of remove unused parameter – A is unused int foo(B b, C c, D d) d, A a) { return b.x+c.x+d.x; } Ref2-14 Corner Case of Change Method Signature • Today’s IDE’s don’t allow you to do this, but …suppose foo is a method of class A class A { int foo(B b, C c, D d) { return b.x+c.x+d.x; } } • After removing parameter A, the definition of foo should become… class A { static int foo(B b, C c, D d) { return b.x+c.x+d.x; } } • Why? see my answers in slide notes Ref2-15 Another Refactoring: Move Via Field • Move B.biff(A) C.biff(A,B) via field B.c • Semantics of move via field is a sequence of refactorings • add parameter C to biff with argument this.c • move B.biff(A,C) C.biff(B,A) Ref2-16 Move via Field Refactoring • Goal I want you to think as precisely as you can in software design – it is not ad hoc • Think of a refactoring engine being similar to reflection – for each • class in a program – there is a RClass object • method in a program – there is an RMethod object • field in a program – there is a RField object • So suppose rbiff is the RMethod object for method “biff” class B { C c; we know: RType t = rbiff.getReturnType(); t.name.equals(“void”) void biff(...) { … } // is true Rclass b = rbiff.getClass(); b.name.equals(“B”) // is true .. biff().. } Ref2-17 Move via Field is a Composite Refactoring static void MoveViaField(RMethod m, RField f) { RType ft = f.getType(); if (ft.isPrimitive()) { throw new RefactoringError(“field can’t be primitive”); } m.addParameter(f); // adds param fname of type ft m.moveTo(ft); } 1. 2. 3. verify that field f is a class and not a primitive type add a new parameter to method m with name f.getName() with type f.getType() move m to class f.getType() Ref2-18 Rename Refactoring int bar(B foo(B b, C c, D d) { /* body1 */ } bar(b1,c1,d1) foo(b1,c1,d1) • Rename changes the name of a method: foobar • rename all method calls as well rename B.foo() to B.bar(A) Ref2-19 Rename Refactoring • There are constraints – just as there are constraints in UML class diagrams – for all refactorings • Suppose I want to rename B.biff(A) to B.zoo(A) is this legal? Eclipse produces… Ref2-20 Yes and No… • I think it is a bug! • Cute, but not funny… • It is correct because the semantics of the program has not changed – there is no call to B.biff(A) • But look! Ref2-21 Strange • What about this case? Rename B.biff(A) B.zoo(A) • Eclipse does the right thing now by renaming all biff methods to zoo… • Because there is a reference… • Just be careful! Ref2-22 Substitute Refactoring int bar(B b, C c, D d) { /* body1 body2 */ } bar(b1,c1,d1) • Substitute changes the body of a method: body1body2 • to substitute “semantically equivalent” code fragments • Example: int find(String person) { if (person.equals(“don”)) return 1; if (person.equals(“john”) return 2; if (person.equals(“sally”) return 3; return 0; } int find(String person) { String[] array = {“don”,”john”,”sally”}; for (i=0; i<array.length; i++) if (person.equals(array[i]) return i; return 0; } Ref2-23 Extract Method / Inline Method Refactorings Lift a block of code into a method void printOwing() { printBanner(); // print details System.out.println(“name: System.out.println(“amount: ” + _name); ” + _getOutstanding()); } extract method aka “lift” inline method void printOwing() { printBanner(); printDetails(); } void printDetails() { // print details System.out.println(“name: System.out.println(“amount: } ” + _name); ” + _getOutstanding()); Ref2-24 Beware of Refactoring Engines • This is what Eclipse does for lift refactoring – create a method of highlighted code • I’m impressed! Not bad at all! Ref2-25 Beware of Refactoring Engines • And what do you think Eclipse does in this case of Lift? • Well, not surprising. This is HARD. Why is this hard??? Ref2-26 Why Am I Telling You This? I like you It’s Thursday To wake you up To prepare you for the midterm Reasons I haven’t told you yet • Reasons: • every bit of your CS education is relevant to software design • one of the distinguishing features about software is that you can’t just focus on one thing – ultimately you have to consider all possibilities • shows you why there are limitations to tools • also tells you where things are headed in the future Ref2-27 motivations for other refactorings – now look at some DESIGN PROBLEMS Ref2-28 Design Problem #1 • Start with a general-purpose class Sample that has operations on statistical samples. You create a class that uses Sample to perform a general cost-benefit analysis that is particular to your business application • This design smells bad – why? And what refactoring do we use to fix it? Ref2-29 Step 1: Eliminate Redundant Code • Lift or extract method brings us to this point Design still smells bad. Why? And what refactoring do we apply? Ref2-30 Step 2: Move Method Via Field remove dependency • Move MyAnalysis.stdev() Sample.stdev() via field MyAnalysis.mySample • This is beautiful! But Eclipse did more than just a move • what extra did it do for you? see slide notes for an answer Ref2-31 Semantic Equality of Designs • These designs are interchangeable equal * MyAnalysis 1 * Sample MyAnalysis 1 +costAnalysis() : double +benefitAnalysis() : double -mySample +average() : double +sum() : double +max() : double Sample -array : double +array : double[] +costAnalysis() : double +benefitAnalysis() : double costAnalysis() { .. sd = // code to compute 2 * standard deviation on mySample.array .. } costAnalysis() { .. sd = 2 * mySample.stdev(); .. } benefitAnalysis() { .. xy = // code to compute 1.5 * standard deviation on mySample.array .. } benefitAnalysis() { .. xy = 1.5 * mySample.stdev(); .. } -mySample +average() : double +sum() : double +max() : double +std() : double stdev() { // return standard deviation on array } • Why? • Are there hidden preconditions for equality? Ref2-32 Perspective • We used in this example the following refactorings: • lift or extract method • move via field • Eclipse does a beautiful job – almost… can you spot the problem? • NetBeans supports none of these refactorings Ref2-33 Design Problem #2 • Sometimes vanilla constructors are not enough • Consider a class of complex numbers – what is its constructor? Complex( float im, float re ) {…} • Suppose we need a constructor for complex numbers that takes polar coordinates as input. What would it look like? Complex( float radius, float angle ) {…} • Oops! All constructors have to have the same name “Complex”! Now What? Ref2-34 Solution • Hide actual constructor and use static methods with understandable names as constructors – called static factory methods class Complex { public static Complex fromCartesian(double real, double imag) { return new Complex(real, imag); } public static Complex fromPolar(double radius, double angle) { return new Complex( radius*cos(angle), radius*sin(angle)); } private Complex(double i, double r) { // ... } } Complex c = Complex.fromPolar(1, pi); // same as fromCartesian(-1, 0) Ref2-35 Factory Method Refactoring A factory method provides a typically static method for a constructor. // application Employee -data ... new Employee( d ) ... +Employee(in data) Employee( data ) { this.data = data; } Employee // application -data ... Employee.New( d ) ... -Employee(in data) +New(in data) static Employee New( data ) { return new Employee(data); } Note: most refactorings are simple structural rewrites that have many, many semantic applications. Ref2-36 Delegate Refactoring • Common primitive refactoring that is usually bundled up with larger refactorings start here rename delegate and hide constructor create delegate of constructor Ref2-37 More Generally • Can create delegate for object methods as well – works in the obvious way • makeDelegate of average: • afterward, go and rename delegate…. Ref2-38 Automate makeFactory Refactoring • In principle, this “transformation” or “refactoring” is composite static RMethod makeFactory(RMethod c, String name) { if (!c.isConstructor()) throw RefactoringError(“must supply constructor!”); RMethod f = c.makeDelegate(); f.rename(name); c.makePrivate(); return f; } • c.makeFactory(“New”) class C { c(int x) {...} } class C { private c(int x) {...} static C New(int x) { return c(x); } } Ref2-39 Design Problem #3 • There is not much that is wrong with the design below, but there is a small irritant. You never need more than one instance of Math really // application Math m = new Math(); x = m.sqrt( m.sin(4.0) * m.cos(2.0) ) • What could be an improvement? Math +e : double +pi : double +sin(in x : double) : double +cos(in x : double) : double +sqrt(in x : double) : double +exp(in x : double) : double +Math() Math class constructor Ref2-40 Several Refactoring Solutions Math // application Math m = new Math(); x = m.sqrt( m.sin(4.0) * m.cos(2.0) ) +e : double +pi : double +sin(in x : double) : double +cos(in x : double) : double +sqrt(in x : double) : double +exp(in x : double) : double +Math() Math // application make static with no Math parameter x = Math.sqrt( Math.sin(4.0) * Math.cos(2.0) ) // application Math m = Math.singleton; singleton singleton creates 1 instance class x = m.sqrt( m.sin(..)* m.cos(..)); ... +e : double +pi : double +sin(in x : double) : double +cos(in x : double) : double +sqrt(in x : double) : double +exp(in x : double) : double Java Solution Math +e : double +pi : double +singleton : Math +sin(in x : double) : double +cos(in x : double) : double +sqrt(in x : double) : double +exp(in x : double) : double -Math() class Math { public static singleton = new Math(); ... } Design Patterns Solution Ref2-41 Design Problem #4 • You are developing a tax code package • You want to debug a NewCalcTaxes class; it does everything CalcTaxes does, except it has new algorithms. • How can you redesign our application so it easy to flip between classes? // application CalcTaxes c = new CalcTaxes(); c.salary() c.deduction() ... CalcTaxes +year +state +deduction() +interest() +salary() +charity() NewCalcTaxes +year +state +deduction() +interest() +salary() +charity() Ref2-42 Solution • Extract an interface TaxInterface from CalcTaxes or NewCalcTaxes; this is a refactoring called Extract Interface • Make CalcTaxes or NewCalcTaxes implement both • Create single TaxInterface variable and use it throughout the program // application TaxInterface c; c = new CalcTaxes(); // c = new NewCalcTaxes(); «interface» TaxInterface +deduction() +interest() +salary() +charity() c.salary() c.deduction() ... CalcTaxes +year +state +deduction() +interest() +salary() +charity() NewCalcTaxes +year +state +deduction() +interest() +salary() +charity() Ref2-43 Extract Interface Refactoring static RInterface extractInterface(RClass c, String name) { RPackage p = c.getPackage(); RInterface i = p.createInterface(name); for (RMethod m : c.getMethods()) { if (m.isPublic() (m.isPublic())&& !m.isConstructor()) i.addMethod(m); } c.implements(i); return i; } 1. 2. 3. 4. 5. find the package in which class c exists create an empty interface i in this package create a method “stub” for each public method in c make c implement i return i Ref2-44 inheritance hierarchy MEMBER MOVEMENTS Ref2-45 Pull Up Member Move identical members in one or more subclasses to superclass. Employee Employee -getName Salesman -getName Engineer Salesman Engineer -getName how would you “automate” or program this refactoring? BE CAREFUL with Methods! why? and where have you seen this before? Ref2-46 Push Down Member Move a member from a superclass to one or more subclasses; remaining subclasses do not use this member. Employee Employee -getName Salesman -getName Engineer Salesman Engineer -getName how would you “automate” or program this refactoring? Ref2-47 how individual classes evolve CLASS REFACTORINGS Ref2-48 Design Problem #5 • You maintain a collection of Person objects: Person -Name -Age -ZipCode -City Name Age ZipCode City Don 55 78746 Austin Elaine 45 44126 Cleveland Alex 17 78746 Austin Elizabeth 15 78761 Buda Betty 36 78746 Austin • Problems: • zipcode + city information replicated • zipcodes periodically get absorbed into another city, ex: Buda + Austin • How can this design be improved? Ref2-49 Class Extraction: Table Normalization • Fundamental idea from databases: a design is bad when a row represents multiple, logically distinct rows. Bad idea if a single object contains replicated smaller objects Name Age Don Name Age ZipCode City Don 55 78746 Austin Elaine 45 44126 Cleveland Alex 17 78746 Austin Elizabeth 15 78761 Buda Betty 36 78746 Austin ZC bad design zipcode-city pairings repeated ZipCode City 55 78746 Austin Elaine 45 78761 Buda Alex 17 44126 Cleveland Elizabeth 15 Betty 36 better design zipcode-city facts in separate table Ref2-50 Table Normalize Class Refactoring You have one class doing work that should be done by two. Create a new class and move the relevant fields and methods from the old class into the new class. Person -name -age -city -state +getName() +getAge() +getCity() +getState() normalize Inline class Person -name -age +getName() +getAge() +getCity() +getState() -hasInhabitants 1..* Note: Person.getCity() delegates to City.getName(); -livesIn 1 City -name -state +getName() +getState() what would it take to implement this? Ref2-51 Design Problem #6 • You’ve written a single Java class that implements both a GUI and some functionality • Your manager is unhappy with your GUI, but does see the benefit of the functionality that you’ve created • What can you do to help fix the problem? GUI+Functionality -guiAttributes -functionalityAttributes +guiMethods() +functionalityMethods() +guiMethodsCallingFMethods() GUI Functionality -guiAttributes +guiMethods() +guiMethodsCallingFMethods() 1 1 -functionalityAttributes +functionalityMethods() Ref2-52 Another: Partition Class • Just slice object/class into 2 connected pieces Name Age Don ZC Name Age ZipCode City Don 55 78746 Austin Elaine 45 44126 Cleveland Alex 17 78746 Austin Elizabeth 15 78761 Buda Betty 36 78746 Austin ZipCode City 55 78746 Austin Elaine 45 44126 Cleveland Alex 17 78746 Austin Elizabeth 15 78761 Buda Betty 36 78746 Austin what would it take to implement this? Ref2-53 Table Normalize and Partition Refactorings • Table Normalize: Person objects are 1:n with City objects Person -name -age -city -state +getName() +getAge() +getCity() +getState() Person -name -age +getName() +getAge() -hasInhabitants 1..* -livesIn 1 City -name -state +getName() +getState() • Partition: Person objects are 1:1 with Rest objects Person -name -age -city -state +getName() +getAge() +getCity() +getState() Person -name -state +getAge() +getCity() -1stHalf Rest -2ndHalf -age -city 1 1 Ref2-54 before iphones, children kept themselves busy pulling taffy… HIERARCHY REFACTORINGS Ref2-55 Extract Interface Several clients use the same subset of a class’s interface, or two classes have part of their interfaces in common. Extract a subset of methods into an interface. Employee +getRank() +getSkills() +getName() +getDept() «interface» Billable +getRank() +getSkills() we saw extract interface before Employee interfaces also hides implementation details of modules/classes +getRank() +getSkills() +getName() +getDept() Ref2-56 Simple GUI Picker static RInterface extractSubsetInterface(RClass c, String name) { RPackage p = c.getPackage(); RInterface i = p.createInterface(name); Set<RMethod> methods = new Set<>(); for (RMethod m : c.getMethods()) if (m.isPublic()) methods.add(m); Set<RMethod> meths = gui.picker(methods); for (RMethod mm : meths) i.addMethod(mm); c.implements(i); return i; } Ref2-57 Extract Superclass You want extract general members from a class. Creates an abstract superclass in which to move sharable fields and methods. abstract specific definition of method “plug-in” Ref2-58 Example Polygon Polygon -edges[ ] Square -edges[ ] -edges[ +draw() ] +area() +draw() +area() Square -edges[ ] +draw() +draw() +area() +area() Ref2-59 What are Extract Super Preconditions? • What superclasses can be extracted from the class below? • Rule: whatever result you end up with must be type safe no compilation errors class add { int a; int b; int compute() { return a+b; } } Ref2-60 Template Similar to Extract Superclass but uses hooks Creates an abstract superclass in which to move fields and methods. template method (hook is abstract) finishes template “plug-in” Ref2-61 Hook Methods • Take a general method that is customized for a plug in and make it general again • The result is a “general” method + a lifted plug-in specific method called a hook • What should be “lifted” into a hook is decided by a programmer not a machine int compute(...) { general-stuff; x = specific-stuff; more-general-stuff(x); } int compute(...) { general-stuff; x = hook(…); more-general-stuff(x); } int hook(…) { return specific-stuff; } Ref2-62 Example: “Raise” Taxes What is an appropriate Template Superclass? class OhioTax { float countyTax = .09; float stateTax = .10; float tax( float price ) { countyTax*price + stateTax*price; } } class TexasTax { float countyTax = .08; float stateTax = .03; float tax( float price ) { countyTax*price + stateTax*price; } } what are the hook methods? you have to know how to generalize machines can’t do this Ref2-63 Solution abstract class Tax { tax(price) { countyTax()*price + stateTax()*price; } abstract float countyTax(); // hook abstract float stateTax(); // hook } class OhioTax extends Tax { float countyTax() { return .09; } // hooks for Ohio float stateTax() { return .10; } // plug-in } class TexasTax extends Tax { float countyTax() { return .08; } // hooks for Texas float stateTax() { return .03; } // plug-m } Ref2-64 Pull Up Class A superclass and subclass are not that different. Merge them together. Employee Salesman Employee what constraints would you insist upon? Ref2-65 Extract Superclass Variant You have two classes with similar features. Create a superclass and move the common features to the superclass. Department Party +getTotalAnnualCost() +getName() +getHeadCount() +getAnnualCost() +getName() Employee +getAnnualCost() +getName() +getId() Employee Department +getAnnualCost() +getId() +getAnnualCost() +getHeadCount() This looks really familiar – How would you code it? Ref2-66 8th Grade Math Vector v1 = [cos(30°)*sqrt(4), sin(30°)*sqrt(4)] Vector v2 = [cos(50°)*sqrt(7), sin(50°)*sqrt(7)] Vector v3 = [cos(72°)*sqrt(43), sin(72°)*sqrt(43)] • above smells bad static Vector v(double angle, double sqrLength) { double len = sqrt(sqrLength); return new Vector(cos(angle)*len, sin(angle)*len); Vector v1 = v(30,4); Vector v2 = v(50,7); Vector v3 = v(72,43); Ref2-67 And There is a Lot More… http://refactoring.com/catalog/index.html • Consolidate Conditional Expression – how is this different than extract method? • Hide Delegate • Take your pick… • The next slides will tell you the significance of refactorings Ref2-68 skip cultural enrichment – view slide animations as hard-copies don’t convey full story ADVANCES IN PROGRAM REFACTORING Ref2-69 Evolution of APIs • Interesting refactoring problem • Use of components frameworks, libraries are common in software development • build systems faster and cheaper • Application Program Interface (API) of a component – set of Java interfaces and classes that are exported to application developers • ideally, APIs don’t change, but of course they do! • when APIs change, client code must also change • very disruptive event in program development • Need an easy and safe way to update applications when component’s API changes Ref2-70 A Common API Change • Dig’s Move Method • instance method becomes static method of host class • moved method takes instance of home class as extra argument • all references to old method are replaced with m() call to new method update move update to class callhost call class home { class bar { class host {Note: although component code changes, void y() { X m(..) { ... } static X m(..,home f) client code must also change { ... } host.m(..,f) void b() { f.m(..); f.m(..) host.m(..,f) ... } } }But a component developer doesn’t have the client code } } Component Client Code Ref2-71 This Change is a Transformation Pnew = Pold class host { static X m(..,home f) { ... } ... } class home { X m(..) { ... } } void b() { f.m(); host.m(..,f) } Component class bar { void y() { } } f.m(..) host.m(..,f) Client Code Ref2-72 Other Common API Changes • Move Field • Delete Method • usually done after method is renamed or moved • Change Argument Type • ex: replace argument type with its super type • Replace Method Call • with another that is semantically equivalent and in the same class • Lots of others… Ref2-73 Dig & Johnson Paper “How do APIs Evolve: A Story of Refactoring” Journal of Software Maintenance & Evolution: Research & Practice 2006 • Manually analyzed change logs, documentation, etc. of different versions of 5 medium to large systems ex: 50K to 2M LOC • Eclipse, Struts, JHotDraw... • Found over 80% of API changes are refactorings • means LOTS of tedious & error-prone updates can be automated • explain elegance of their solution using transformations Ref2-74 In the Future • Programmers will use advanced IDEs that “mark” API classes, methods, fields • only way marked elements can change is by refactorings (b) • “private” component edits modeled by transformations (e) b3e3e2b2b1e1 b2b1 b3 b= version 0 = version 1 transformations to be applied to update client code w.r.t. changes in API • API updates b is a projection of changes where “private” edits are removed Ref2-75 Client Update Transformation U U U client program client program = b = b = b – client program client code client code + version 0 + version 1 version 0 – + version 0 version 1 + version 1 this is not how result was originally presented; it is an transformational representation of their results Ref2-76 In the Future • IDEs will be component manipulators or calculators – part of Eclipse since 2007 • IDEs will create update functions like U for distribution • distribute meta-functions, not components U(x) U(x) • IDEs will apply functions to code bases to automatically update them Ref2-77 Recap • Refactorings are behavior-preserving transformations that map an initial OO program model to a revised OO program model • Backbone of automated updates of programs • Refactorings are the backbone of certain design technologies • XP – extreme programming which doesn’t really believe in up-front designs • Agile Program Development • And Design Patterns our next topic Ref2-78