Refactoring
description
Transcript of Refactoring
![Page 1: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/1.jpg)
Refactoring
Compl
![Page 2: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/2.jpg)
“Bad smells” in your software
• Code duplication• Long operations, big classes• Operations with lots of parameters• Divergent or shotgun fixes during maintenance• Feature envy • You use “switch” (*)• “fake attributes” in your class• comments inside an operation (Eh? I’m sorry!?)
2
![Page 3: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/3.jpg)
Suboptimaal software
• Complexity & flexibility of your software components are not optimal.
maintenance cost bugs fix, adapting features, adding features
Don’t underestimate maintenance.
3
• Mitigated by: invest in a good design, employ design patterns• Still, under pressure (deadline!) the temptation to do dirty solving is big...• Let’s take a look at “refactoring”
![Page 4: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/4.jpg)
Refactoring
4
Refactoring is a transformation on the source code of a software to make it easier to understand and to maintain.
The transformation must preserve the software’s observable behavior.
So, performance is not a consideration in refactoring. This is not to say that performance is unimportant. But “refactoring” is limited to achieve the above mentioned goals.
Fowler, 2000. With contrib. from e.g. Erich Gamma (GOF) & Kent Beck (JUnit)
Belong to basic vocab of sw. engineers.
![Page 5: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/5.jpg)
How to do it?
• Formal theory of refactoring? sadly none practical enough.• So, we do it in a series of small steps, like ...• Rely on testing (!)• Intergrate it structurally into your maintenance workflow
5
Keep in mind that the goal of refactoring is not a cosmetic make-over, though some of the steps may look cosmetics. The goal is: mitigating your long term maintenance.
I will mark the refactoring names like this: Extract Method
![Page 6: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/6.jpg)
Example
6
Film- title- priceCode : int
RentInfo- numOfDays
*
film
Customer- name- CustPoints+ rent(film)+ back(film)+ mkInvoice()
*
We’ll keep getter and setter implicit.
RentInfo- numOfDays
**rentInfos
film1
Three price categories, encoded as constants:CHILDREN = 0STANDARD = 1 NEW = 2
C: CustomerF1 : Film
F2 : Film
3d : RentInfo
7d : RentInfo
![Page 7: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/7.jpg)
7
mkInvoice () { totPrice = 0 pts = 0 println(“Invoice for” + name) for (RentInfo v : rentInfos) { // calculate price for a single film v_price = 0 switch (v.getFilm().getPriceCode()) { case Film.STANDARD : v_price = 3 euro * v.numOfdays ; break case Film.NEW : v_price = 5 euro * v.numOfdays ; break case Film.CHILDREN : v_price = 2 euro * v.numOfdays } totPrice += v_price // calculate earned points pts++ if (v.getFilm().getPriceCode()==Film.NEW) pts++ println(“Film ” + v.getName() + “, price = ” + v_price) } println(“Tot price = ” + totPrice) println(“Points earned = ” + pts)}
Bad smells?Let’s rework this, but in small steps refactoring.
![Page 8: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/8.jpg)
8
Extract method
mkInvoice () { totPrice = 0 pts = 0 println(“Invoice for” + name) for (RentInfo v : rentInfos) { // calculate price for a single film v_price = 0 switch (v.getFilm().getPriceCode()) { case Film.STANDARD : v_price = ... ; break case Film.NEW : v_price = ... ; break case Film.CHILDREN : v_price = ... } totPrice += v_price // calculate earned points pts++ if (v.getFilm().getPriceCode()==Film.NEW) pts++ println(“Film ” + v.getName() + “, price = ” + v_price) } println(“Tot price = ” + totPrice) println(“Points earned = ” + pts)}
promote a fraction of code to a method.(comments often give hint)
![Page 9: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/9.jpg)
9
Extract method
mkInvoice () { totPrice = 0 pts = 0 println(“Invoice for” + name) for (RentInfo v : rentInfos) { // calculate price for a single film v_price = 0 switch (v.getFilm().getPriceCode()) { case Film.STANDARD : v_price = ... ; break case Film.NEW : v_price = ... ; break case Film.CHILDREN : v_price = ... } totPrice += v_price // calculate earned points pts++ if (v.getFilm().getPriceCode()==Film.NEW) pts++ println(“Film ” + v.getName() + “, price = ” + v_price) } println(“Tot price = ” + totPrice) println(“Points earned = ” + pts)}
calculatePrice(v) { v_price = 0 switch (v.getFilm().getPriceCode()) { case Film.STANDARD : v_price = ... ; break case Film.NEW : v_price = ... ; break case Film.CHILDREN : v_price = ... } return v_price}
v_price = calculatePrice(v)
![Page 10: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/10.jpg)
10
Extract methodmkInvoice () { totPrice = 0 pts = 0 println(“Invoice for” + name) for (RentInfo v : rentInfos) { // calculate price for a single film .... totPrice += v_price // calculate earned points pts++ if (v.getFilm().getPriceCode()==Film.NEW) pts++ println(“Film ” + v.getName() + “, price = ” + v_price) } println(“Tot price = ” + totPrice) println(“Points earned = ” + pts)}
??
Take sufficient context along
caculateEarnedPoints(v,pts) { pts++ if (v.getFilm().getPriceCode()==Film.NEW) pts++ return pts}
pts = calculateEarnedPoints(v,pts)
![Page 11: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/11.jpg)
11
After extraction
mkInvoice () { totPrice = 0 pts = 0 println(“Invoice for” + name) for (RentInfo v : rentInfos) { v_price = calculatePrice(v) totPrice += v_price pts = calculateEarnedPoints(v,pts) println(“Film ” + v.getName() + “, price = ” + v_price) } println(“Tot price = ” + totPrice) println(“Points earned = ” + pts)}
these were comments! Now self-documenting.
This is easier to understand now, no?
![Page 12: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/12.jpg)
12
Don’t we all love temporary variables?
int p = calculatePrice(v)totPrice += pprintln(“Film ” + v.getName() + “, price = ” + p)
Conceptually, v_price is just use to hold the result of calculatePrice. They are synonymous; but we have introduced it to get a bit of performance. But it does complicate your code! (your mind have to keep track of one more item in your program)
totPrice += calculatePrice(v)println(“Film ” + v.getName() + “, price = ” + calculatePrice(v))
Remove temp
How about performance? • Balancing between maintenance and performance.• 90% of the time, your software is doing just 10% of your code.• User profiler to locate this 10%, and target your performance optimization on this 10%.
![Page 13: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/13.jpg)
13
“Features envy”
calculatePrice(v) { v_price = 0 switch (v.getFilm().getPriceCode()) { case Film.STANDARD : v_price = 3 euro * v.numOfdays ; break case Film.NEW : v_price = 5 euro * v.numOfdays ; break case Film.CHILDREN : v_price = 2 euro * v.numOfdays } return v_price}
Customer is doing too much work on data obtained from Film. Shouldn’t Film does this work instead?? risk of shot-gun fixes in the future “Move method”
CustomercalculatePrice(v)
FilmcalculatePrice(v)
![Page 14: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/14.jpg)
14
Why is this switch bad?
calculatePrice(v) { v_price = 0 switch (v.getFilm().getPriceCode()) { case Film.STANDARD : v_price = ... ; break case Film.NEW : v_price = ... ; break case Film.CHILDREN : v_price = ... } return v_price}
FilmcalculatePrice()v
Adding a new kind of film forces you to change calculatePrice divergent fix.
FilmcalculatePrice(v)
StandardFilm
NewFilm
ChildrenFilm
Solving it with inheritence.
Unfortunaly in this example, “film-type” should be able to change dynamically! So you need a different solution.
![Page 15: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/15.jpg)
15
Replace type-code with strategy + polymorphism
calculatePrice() { v_price = 0 switch (v.getFilm().getPriceCode()) { case Film.STANDARD : v_price = ... ; break case Film.NEW : v_price = ... ; break case Film.CHILDREN : v_price = ... } return v_price}
FilmcalculatePrice(v)
FilmPriceStrategycalculatePrice(v)
StandardPrice
NewPrice
ChildrenPrice
1
calculatePrice(v) { return priceStrg.calculatePrice(v)}
priceStrg
You can change the strategy dynamically: film.priceStrg = new NewPrice() film.priceStrg = new StandardPrice()
![Page 16: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/16.jpg)
Fowler’s refactoring set
• Streamlining methodes (9)• Moving features (8)• Organizing data (16)• Simplifying conditional statements (8)• Simplifying method calls (15)• Generalization (12)
16
![Page 17: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/17.jpg)
Streamlining methods
• extract method– inverse: inline method but you’ll risk code duplication.
To limit: apply if the body is just as clear as the method’s name, and is not called in too many other methods.
• eliminate and introduce temp– temp is a form of indirection – furthermore makes method extraction more difficult as
you have to move its context as well
17
Fowler: long methods are often the sources of problems (he prefers more methods; but short)
![Page 18: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/18.jpg)
Temp
• introduce explaining variable and eliminate temp
• replace temp with query
• you can’t elimimate all temps loop counter, accumulation var.
18
double sellPrice= product.getPrice() * 1.5p = sellPrice > 10.0
p = product.getPrice() * 1.5 > 10.0
expression is too complex
p = sellPrice() > 10.0
![Page 19: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/19.jpg)
You have too many parameters …
• Introduce parameter object
19
price(date , age, isMember, discountCard, isOpenDay)
price(personProfile, dateProfile)personProfileageisMemberdiscountCard
dateProfiledateisOpenday
![Page 20: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/20.jpg)
Moving Features
• Main tool: move method/field (you have seen this)• spliting and combining classes• introducing and eliminating delegation
20
Distribute the work (which class does what). A class that does too much is difficult to understand.
![Page 21: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/21.jpg)
Split and combine
• Extract class: a class C does too much. Identify a part of C that can be group to its own class.
• inverse: inline class.
21
Personnameagestreethousenrpostcode…printAddress()
Personnameage...
AddressstreethousenrpostcodeprintAddress()
1
1
![Page 22: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/22.jpg)
Delegate
• Hide delegate
• remove middle man
22
Person
Address PostcodegetCity()
address.getPostcode().getCity()
Person
AddressgetCity()
PostcodegetCity()
address. getCity()
getCity() { return postcode.getCity() }
If Address is doing too mush delegation work instead of real work, then reverse: let the client access the delagate directly.
A client (Person) of Address is calling Address’ delegate Hide this delegate.
1
1
1
1
![Page 23: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/23.jpg)
Streamlining conditionals
• Main tool : extract method on conditionals– decompose conditional, consolidate conditional,
• Simplification on the structure– remove control flag– replace conditional with polymorphism (you have seen this)– replace nested conditional with guard clauses
23
Complex domain logics translate to complex conditional statements, which are often difficult to understand (hence error prone).
![Page 24: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/24.jpg)
Extract method on conditions
24
if (date.before(SUMMER_BEGIN) || date.after(SUMMER_END) ) price = 0.8 * price + ... // some complicated expr
if (notSummer(date ) ) price = 0.8 * price ...
A complicate conditional:
Decompose conditional
if (notSummer(date ) ) price = summerPrice(...)
![Page 25: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/25.jpg)
Extract method on conditions
25
price(person) { if (isFree(person)) return 0 ; return stdPrice(person)}
price(person) { if (person.age ≤ 10) if (person.isMember) if (person.isBirthday()) return 0 ; return stdPrice(person)}
Consolidate conditional expression
giveCandy(person) { if (person.age ≤ 10) return candy if (person.isMember) return candy if (person.isBirthday()) return candy return null}
Series of “if” encoding “or” Cascade of “if” encoding “and”
giveCandy(person) { if (candyEligible(person)) return candy ; return null}
![Page 26: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/26.jpg)
Simplifying cond. structure
26
found = false product = nullfor (iter = V.iterator() ; iter.hasNext() && !found ; ) { product = iter.next() found = product.price() <= 10.0}return product
remove control flag
control variable
for (Product p : V) { product = p if (product.prijs() <= 10.0) break}return product
if (product.prijs() <= 10.0) break
![Page 27: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/27.jpg)
Simplifying cond. structure
27
giveMilk(person) { if (person is alergic) m = null else { m = new Milk() ; m.warmup() } return m}
giveCandy(person) { if (person.age < 12) c = new MagicCandy() else c = new StdCandy() c.wrap() return c}
“If” implementing two paths of your “normal flow” :
An “alternate flow” seeks to break off the computation early, due to some “exceptional” condition. “If” implementing an “alternate flow” :
![Page 28: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/28.jpg)
Simplifying cond. structure
28
giveMilk(person) { if (person is alergic) m = null else { m = new Milk() ; m.warmup() } return m}
An “alternate flow” break off the computation early, due to some “exceptional” condition. “If” implementing an “alternate flow” :
Here programmer also tried to make the program to have a single entry and exit point. That seems to be a good idea, no?
Fowler: favor whichever is more readable.
giveMilk(person) { if (person is alergic) return null m = new Milk() ; m.warmup() return m}
Replace (nested) conditionals with “guard clauses”
![Page 29: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/29.jpg)
Generalization
• Moving features within a hierarchy• Spliting and combining hierarchy• Replacing inheritence with delegation, or the other way
around
29
Inheritence is importtant in OO; optimize your inheritence structure.
![Page 30: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/30.jpg)
Pull up / pull down
30
Productname
Applediscount()
Coffeediscount()
Productname
discount()
Apple Coffee
pull up
Productnamedrink()
Apple Coffee
Productname
Apple Coffeedrink()
pull down
GreenApple
VeryGreenApple
GreenApplecollapse
hierarchy
![Page 31: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/31.jpg)
Extract sub/super class
31
Productname
discount()
But discount is only relevant for a subset of the instances of Product.
Productname
DiscountedProductdiscount()
extract subclass
Productprice
getIcon ()setIcon(i)
resetIcon()
Personname
getIcon ()setIcon(i)
resetIcon()
Productprice
Personname
ItemWithIcongetIcon ()setIcon(i)
resetIcon()extract superclass
The classes have a common subset of features
(Q: and what if we don’t have multiple inheritance?)
![Page 32: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/32.jpg)
Extract interface
32
Productname
getPrice()getTax()
getDiscount()…
WebShop Invoice
we have multiple client-classes that use the same subset of Product’s features
Productname
…
<<interface>>SellableProduct
getPrice()getTax()
getDiscount()
extract interface
<<use>> <<use>>
WebShop Invoice
![Page 33: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/33.jpg)
Replace delegation with inheritence, or back
33
Productnameprice
ItemWithIcongetIcon ()setIcon(i)
resetIcon()…
Productnameprice
ItemWithIcongetIcon ()setIcon(i)
resetIcon()
10..1
If Product turns out to use only very few features of ItemWithIcon
If Product turns out to delegate too much work to ItemWithIcon
Replace delegation with Inheritence
Replace Inheritence with delegation
![Page 34: Refactoring](https://reader035.fdocuments.us/reader035/viewer/2022062501/568164d8550346895dd71ce6/html5/thumbnails/34.jpg)
Tools
• Refactoring can’t be fully automated– some refactoring steps are difficult to automate
• some extent supported by IDEs, – Eclipse, Netbeans, IntelliJ– no correctness theory that is also practical enough you
will have to rely on testing
34