introductie verschillende refactoringstappen refactoring

37
Introductie Verschillende refactoringstappen Refactoring

Upload: petra-ten

Post on 08-Jun-2015

226 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Introductie Verschillende refactoringstappen Refactoring

• Introductie• Verschillende refactoringstappen

Refactoring

Page 2: Introductie Verschillende refactoringstappen Refactoring

2

Fowler, 2000. Met bijdrage van o.a. Erich Gamma (GOF) en Kent Beck (JUnit)

Behoort tot de basisvocab van sw. ontwikkelaars.

Page 3: Introductie Verschillende refactoringstappen Refactoring

Suboptimaal software

Complexiteit en flexibiliteit van de componenten van de software niet optimaal.

Leidt tot hoger onderhoud (bug fixing, feature aanpassen, feature toevoegen) kosten

Onderschat onderhoud niet vaak grote kostenpost!

3

Page 4: Introductie Verschillende refactoringstappen Refactoring

Refactoring

Dus bijvoorbeeld performance is niet de hoofddoel van refactoring.

4

Refactoring is transformatie op (de code van) een software om de software makkelijker te begrijpen en te onderhouden; de transformatie mag de observeerbaar gedraag van de software niet veranderen.

Page 5: Introductie Verschillende refactoringstappen Refactoring

Bad smells in software

Indicatoren dat refactoring nodig is:Code duplicatieLange methode, grote klasseMethode met veel parametersDivergent fix, shotgun fixFeature envy SwitchTemporary field (tijdelijk attribuut)Commentaar

5

Page 6: Introductie Verschillende refactoringstappen Refactoring

Hoe doen we dat ?Formele theorie van refactoring? Klassen zijn echter

complex (attributen, methoden, toegangmodifiers, statics, inheritence …), zulke theorie wordt snel onpraktisch.

We doen refactoring als een serie van kleine stappen. Zoals:Geef een betere naam aan een methodeExtractie van een block code tot een methode

We leunen op testing om te garanderen dat elke stap de software consistent houdt. (retest na elke stap!)

Maar deze zijn toch slechts cosmetische modificaties? niet erg belangrijk?

De doel van refactoring is pragmatische onderhoudRefactoring doen we structureel

6

Page 7: Introductie Verschillende refactoringstappen Refactoring

Refactoring doen we structureel…

Regelmatig

In principe kun je je eigen refactoringstappen bedenken. Fowler’s cataloog (~50) nuttig als je basis vocabulaire.

Je hebt een goede test suite nodigDekkendAutonoom

7

Page 8: Introductie Verschillende refactoringstappen Refactoring

Voorbeeld

8

Film- titel

-prijsCode : int+$ KINDER

+$ STANDAARD+$ NIEUW

LeenInfo- aantalDagen

*

film

Klant- naam

- KlantPunten+ leen(uitleen)+ terug(uitleen)+ mkFactuur()

* leent

•Hypothetisch nemen we aan dat dit een onderdeel van een groot software •getter en setter impliciet • bron: Fowler

Page 9: Introductie Verschillende refactoringstappen Refactoring

Flattening associatieklasse

9

Film- titel

-prijsCode : int+$ KINDER

+$ STANDAARD+$ NIEUW

LeenInfo- aantalDagen

1

0..1

film

Klant- naam

- KlantPunten+ leen(uitleen)+ terug(uitleen)+ mkFactuur()

*

1

leent

• Omdat Java geen 1e klass associatieklasse niet heeft• Alleen voor uitleg laat ik de assoc. klasse omgezet naar gewone klassen en relaties

leeninfos

Page 10: Introductie Verschillende refactoringstappen Refactoring

10

mkFactuur() { double totPrijs = 0 int punten = 0 println(“Factuur voor ” + naam) for (LeenInfo v : leeninfos) { // bereken de huurprijs van v double v_prijs = 0 switch (v.getFilm().getPrijsCode()) { case Film.GEWOON : v_prijs = … ; break case Film.NIEUW : v_prijs = … ; break case Film.KINDER : v_prijs = … ; break } totPrijs += v_prijs // bereken verkregen klantpunten uit v punten++ if (v.getFilm().getPrijsCode()==Film.NIEUW) punten++ println(“Film ” + v.getNaam() + “, prijs ” + v_prijs) } println(“Tot prijs ” + totprijs) println(“Punten verdiend ” + punten)}

Page 11: Introductie Verschillende refactoringstappen Refactoring

11

mkFactuur() { double totPrijs = 0 int punten = 0 println(“Factuur voor ” + naam) for (LeenInfo v : leeninfos) { // bereken de huurprijs van v double v_prijs = 0 switch (v.getFilm().getPrijsCode()) { case Film.GEWOON : v_prijs = … ; break case Film.NIEUW : v_prijs = … ; break case Film.KINDER : v_prijs = … ; break } totPrijs += v_prijs // bereken verkregen klantpunten uit v punten++ if (v.getFilm().getPrijsCode()==Film.NIEUW) punten++ println(“Film ” + v.getNaam() + “, prijs ” + v_prijs) } println(“Tot prijs ” + totprijs) println(“Punten verdiend ” + punten)}

“extract method” :• promoveer een groep statement tot een methode• commentaar geeft hint• neem de context ook mee!

Page 12: Introductie Verschillende refactoringstappen Refactoring

12

mkFactuur() { double totPrijs = 0 int punten = 0 println(“Factuur voor ” + naam) for (LeenInfo v : leeninfos) { double v_prijs = berekenPrijs(v) totPrijs += v_prijs punten += berekenPunten(v) println(“Film ” + v.getNaam() + “, prijs ” + v_prijs) } println(“Tot prijs ” + totprijs) println(“Punten verdiend ” + punten)}

Is dit nu makkelijker om te begrijpen of niet ??

Discussiepunt: de tijdelijke lokale variabele

Page 13: Introductie Verschillende refactoringstappen Refactoring

13

mkFactuur() { double totPrijs = 0 int punten = 0 println(“Factuur voor ” + naam) for (LeenInfo v : leeninfos) { double v_prijs = berekenPrijs(v) totPrijs += v_prijs punten += berekenPunten(v) println(“Film ” + v.getNaam() + “, prijs ” + v_prijs) } println(“Tot prijs ” + totprijs) println(“Punten verdiend ” + punten)}

Maar dit gaat toch ten koste van performance…

berekenPrijs(v)

Fowler’s stelling: • 90% van de tijd is je software bezig met 10% van je code.• Performance optimalisatie is makkelijker na refactoring.(gebruik profiler!)

replace temp with query

Page 14: Introductie Verschillende refactoringstappen Refactoring

14

class Klant { … berekenPrijs(LeenInfo v) { double v_prijs = 0 switch (v.getFilm().getPrijsCode()) { case Film.GEWOON : v_prijs = … ; break case Film.NIEUW : v_prijs = … ; break case Film.KINDER : v_prijs = … ; break } return v_prijs }}

Filmfeature envy

“move method”

Film

Switch over ‘type’ vluchtig… replace type-code with Strategy

GewoneFilm

NieuweFilm

KinderFilm

Werkt helaas niet we willen de prijscode van een film ook dynamisch te kunnen veranderen.

Page 15: Introductie Verschillende refactoringstappen Refactoring

Switch

15

switch (expressie) {

case 0 : doe iets ; break

case 1 : doe iets ; break

case 9 : doe iets ; }

Page 16: Introductie Verschillende refactoringstappen Refactoring

16

PrijsStrategieprijs(n)

GewonePrijs

NieuwePrijs

KinderPrijs

FilmberekenPrijs(v)

1

KlantmkFactuur ()

LeenInfoaantalDagen

*

1

mkFactuur() { double totPrijs = 0 int punten = 0 println(“Factuur voor ” + naam) for (LeenInfo v : leeninfos) { totPrijs += berekenPrijs(v) punten += berekenPunten(v) println(“Film ” + v.getNaam() + “, prijs ” + berekenPrijs(v)) } println(“Tot prijs ” + totprijs) println(“Punten verdiend ” + punten)}

v.getFilm().berekenPrijs(v)

Na “replace type-code with Strategy”

Page 17: Introductie Verschillende refactoringstappen Refactoring

17

PrijsStrategieprijs(n)

GewonePrijs

NieuwePrijs

KinderPrijs

FilmberekenPrijs()

1

KlantmkFactuur ()

LeenInfoaantalDagen

*

1prijs

prijs(n) { if (n<=2) return 2.0 else 2.0 + (n – 2)* 1.5 }

berekenPrijs(LeenInfo v) { double v_prijs = 0 switch (getPrijsCode()) { case Film.GEWOON : v_prijs = … ; break case Film.NIEUW : v_prijs = … ; break case Film.KINDER : v_prijs = … ; break } return v_prijs } this.prijs.prijs(v.getAantalDagen())

“replace conditional with polymorphism”

Page 18: Introductie Verschillende refactoringstappen Refactoring

18

PrijsStrategieprijs(n)

GewonePrijs

NieuwePrijs

KinderPrijs

FilmberekenPrijs()

1

KlantmkFactuur ()

LeenInfoaantalDagen

*

1prijs

berekenPrijs(LeenInfo v) { double v_prijs = 0 switch (getPrijsCode()) { case Film.GEWOON : v_prijs = … ; break case Film.NIEUW : v_prijs = … ; break case Film.KINDER : v_prijs = … ; break } return v_prijs }

“replace conditional with polymorphism”

berekenPrijs(LeenInfo v) {

return prijs.prijs(v.getAantalDagen())

}

Page 19: Introductie Verschillende refactoringstappen Refactoring

Fowler’s refactoringset

Stroomlijnen van methodes (9)Verplaatsen van features (8)Organisatie van data (16)Vereenvoudiging van conditionele statements (8)Vereenvoudiging van methodeaanroep (15)Generalisatie (12)

19

Page 20: Introductie Verschillende refactoringstappen Refactoring

Stroomlijnen van methode

hoofdwapen: extract methodinverse: inline method als de body net zo duidelijk

als de methodenaam.elimineren en introduceren van temp

als je een blok B tot een methode uittrekt, moet je ook de context van B mee nemen temp maakt dit lastiger.

20

Fowler’s stelling: lange methodes zijn vaak bronnen van problemen. (Fowler geeft voorkeur aan (veel) korte methodes)

Page 21: Introductie Verschillende refactoringstappen Refactoring

Temp

inline temp en introduce explaining variable

replace temp with query

je kunt niet alle temp elimineren loopteller, accumulatie var.

21

double verkoopprijs = product.getPrijs() * 1.5return verkoopprijs > 10.0

return product.getPrijs() * 1.5 > 10.0

expressie is te complex

return verkoopprijs() > 10.0

Page 22: Introductie Verschillende refactoringstappen Refactoring

Als je parameterlijst te lang is…

Introduce parameter object

22

prijs(datum , leeftijd, isLid, actiekaart, isOpenDag)

prijs(persoonsProfiel, datumProfiel)persoonsProfiel

leeftijdlidmaatschap

actiekaart

datumProfieldatum

opendag

Page 23: Introductie Verschillende refactoringstappen Refactoring

Features verplaatsen

hoofdwapen: move method/field (heb je al gezien)splitsen en samenvoegen van klassenintroduceren en elimineren van delegatie

23

Verdeel het werk (welke klasse doet wat). Een klasse die te veel doet is ook moeilijker om te begrijpen.

Page 24: Introductie Verschillende refactoringstappen Refactoring

Splitsen en samenvoegen

Extract class: een klasse C doet te veel. Herken een deel van C die je tot een eigen klasse D kan groeperen.

De inverse is inline class.

24

Persoonnaamleeftijdstraathuisnr

postcode…

printAdres()

Persoonnaamleeftijd

Adresstraathuisnr

postcodeprintadres()

1

1

Page 25: Introductie Verschillende refactoringstappen Refactoring

Delegatie

Hide delegate

remove middle man25

Persoon

AdresPostcodegetStad()

adres.getPostcode().getStad()Persoon

AdresgetStad()

PostcodegetStad()

adres. getStad()

getStad() { return postcode.getStad() }

als Adres te veel delegatie doet ipv echt werk

Page 26: Introductie Verschillende refactoringstappen Refactoring

Stroomlijnen van conditionele stmt

Hoofdwapen: extract methode op conditiesdecompose conditional, consolidate conditional,

Vereenvoudiging op de structuurremove control flagreplace conditional with polymorphism (heb je al

gezien)replace nested conditional with guard clauses

26

Complexe domeinlogics vertalen zich naar complexe conditionele statements, die vaak moeilijk te begrijpen en dus foutgevoelig is.

Page 27: Introductie Verschillende refactoringstappen Refactoring

Extract methode op conditionele stmt

Decompose conditional

Consolidate conditional expression

27

if (datum.voor(ZOMER_BEGIN) || datum.na(ZOMER_EIND) ) prijs = 0.8 * prijs

if (…???.... (datum ) ) prijs = 0.8 * prijs

prijs(persoon) { if (isGratis(persoon)) return 0 return normalePrijs(persoon)}

prijs(persoon) { if (leeftijd ≤ 10) if (persoon.isLid if (persoon.isJarig()) return 0 ; return normalePrijs(persoon)}

if (nietZommer(datum ) ) prijs = 0.8 * prijs

Page 28: Introductie Verschillende refactoringstappen Refactoring

Vereenvoudiging van structuur

remove control flag

28

gevonden = false product = nullfor (iter = V.iterator() ; iter.hasNext() && !gevonden ; ) { product = iter.next() gevonden = product.prijs() <= 10.0}

for (Product p : V) { product = p if (product.prijs() <= 10.0) break}

Page 29: Introductie Verschillende refactoringstappen Refactoring

Vereenvoudiging van structuur

Soms kun je “replace nested conditional with guarded clauses” doen.If-else voor normale flow met meerdere varianten

Soms heb je normale flow met alternatieven die je liever ziet als afwijkende flows minder expliciet in if-else

29

prijs (persoon) { normaleprijs = 30 ; if (persoon.leeftijd ≤ 4) return 0 else if (!dag.isWeekend ()) return normaleprijs+10 ; else return normaleprijs ; }

prijs() { if (dag.isWeekend()) return 40.0 else return 30.0}

Page 30: Introductie Verschillende refactoringstappen Refactoring

Vereenvoudiging van structuur

Soms heb je normale flow met een of meer afwijkende flows minder expliciet in if-else

30

prijs (persoon) { normaleprijs = 30 if (persoon.leeftijd ≤ 4) return 0 if (datum.isOpenDag()) normaleprijs += 10 return normaleprijs}

prijs (persoon) { normaleprijs = 30 ; if (persoon.leeftijd ≤ 4) return 0 else if (!dag.isWeekend ()) return normaleprijs+10 ; else return normaleprijs ; }

Page 31: Introductie Verschillende refactoringstappen Refactoring

Generalisatie

Verplaatsen van features binnen een hiërarchie.Splitsen en samenvoegenVervangen van inheritence met delegatie, of

andersom

31

Optimaliseer je inheritence structuur.

Page 32: Introductie Verschillende refactoringstappen Refactoring

Pull up / pull down

32

Productnaam

Appelkorting()

Koffiekorting()

Productnaam

korting()

Appel Koffie

pull up

Productnaam

korting()

Appel Koffie

als korting() eigenlijk alleen relevant is voor appel

Productnaam

Appelkorting()

Koffie

pull down

Page 33: Introductie Verschillende refactoringstappen Refactoring

Extract sub/super class

33

Productnaam

korting()

korting wordt alleen door sommige instanties gebruikt

Productnaam

KortingProductkorting()

extract subclass

Productnaamprijs

getIcoon ()setIcoon(i)

resetIcoon()

Persoonnaam

getIcoon ()setIcoon(i)

resetIcoon()

Productnaamprijs

Persoonnaam

ItemMetIcoongetIcoon ()setIcoon(i)

resetIcoon()

extract superclass

Klassen met een subset van dezelfde features

Page 34: Introductie Verschillende refactoringstappen Refactoring

Extract interface

34

Productnaam

getPrijs()getActie()

getKorting()

WebShop Factuur

meerdere klantklassen die dezelfde subset van methodes van Product gebruiken

Productnaam

PrijsItemgetPrijs()getActie()

getKorting()extract

interface

Page 35: Introductie Verschillende refactoringstappen Refactoring

Collapse hierarchy

35

Appel

GroenAppel

ze blijken niet veel van elkaar verschillen.

Appel

Page 36: Introductie Verschillende refactoringstappen Refactoring

replace delegation with inheritence, en omgekeerd

36

Productnaamprijs

ItemMetIcoongetIcoon ()setIcoon(i)

resetIcoon()…

Productnaamprijs

ItemMetIcoongetIcoon ()setIcoon(i)

resetIcoon()

10..1

als Product blijkt slechts een relatief klein deel van ItemMetIcoon gebruikt

als Product blijkt veel werk naar ItemMetIcoon delegeert

Page 37: Introductie Verschillende refactoringstappen Refactoring

Tools

Liever laat je refactoringstappen geautomatiseerd door een toolveel sneller dan handmatigkans om fout te maken is veel kleiner.maar sommige refactoringstappen zijn moeilijk te

automatiseren (zoals replace cond with polymorhpism)

Ondersteunt door IDEs, maar ondersteuning varieertEclipse, Netbeans, IntelliJElimineer de noodzaak van testing niet.

37