introductie verschillende refactoringstappen refactoring
TRANSCRIPT
• 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.
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
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.
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
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
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
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
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
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)}
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!
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
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
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.
Switch
15
switch (expressie) {
case 0 : doe iets ; break
case 1 : doe iets ; break
case 9 : doe iets ; }
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”
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”
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())
}
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
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)
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
Als je parameterlijst te lang is…
Introduce parameter object
22
prijs(datum , leeftijd, isLid, actiekaart, isOpenDag)
prijs(persoonsProfiel, datumProfiel)persoonsProfiel
leeftijdlidmaatschap
actiekaart
datumProfieldatum
opendag
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.
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
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
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.
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
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}
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}
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 ; }
Generalisatie
Verplaatsen van features binnen een hiërarchie.Splitsen en samenvoegenVervangen van inheritence met delegatie, of
andersom
31
Optimaliseer je inheritence structuur.
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
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
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
Collapse hierarchy
35
Appel
GroenAppel
ze blijken niet veel van elkaar verschillen.
Appel
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
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