Upload
stefan-lieser
View
178
Download
0
Embed Size (px)
Citation preview
Refactoring C#Legacy Code
Stefan Lieser @StefanLieser
http://refactoring-legacy-code.net
Houston, we’ve had a problem.
http://er.jsc.nasa.gov/seh/13index.jpg
Wandelbarkeit
https://pixabay.com/de/chamäleon-hautnah-exotische-grün-1414084/
Investitionsschutz = Wandelbarkeit
Nicht-funktionale Anforderungen
Funktionale Anforderungen
Das Problem besteht aus drei Teilen:
• Lesbarkeit/Verständlichkeit ist nicht gegeben • Automatisierte Tests fehlen • Komplexe Refactorings sind notwendig
Das Dilemma:
• Um Tests zu ergänzen, => muss refactored werden.
• Um zu refactoren, => müssen Tests ergänzt werden.
KISS - Keep It Simple Stupid• „Einfach“ in Bezug auf
• Schreiben / Erstellen? • Lesen / Modifizieren !
Ein Sicherheitsnetz spannen:• Versionskontrolle • Continuous Integration • Code Reviews • Automatisierte Tests
Begründung für Refactorings:
• NICHT: Clean Code Developer, Prinzipien,Werte, Craftsmen, …
• Sondern Kundennutzen: • Neues Feature • Bugfix
Es gibt zwei Artenvon Refactorings:
einfache und
komplexe.
Einfache RefactoringsRename Extract Method Introduce VariableIntroduce Parameter etc.
Vollständig werkzeuggestützt durchführbar
Komplexe RefactoringsNicht mehr ausschließlich
werkzeuggestützt durchführbar
Einfache RefactoringsLesbarkeit herstellen.
Erkenntnisse über den Code im Code sichern.
publicdoubleEndpreis(intanzahl,doublenetto){ varnettoPreis=anzahl*netto;returnnettoPreis*1.19;}
publicdoubleEndpreis(intanzahl,doublenetto){ return(anzahl*netto)*1.19;}
publicdoubleEndpreis(intanzahl,doublenetto){ varnettoPreis=anzahl*netto;varbruttoPreis=nettoPreis*1.19;returnbruttoPreis;}
Introduce Variable
Introduce Variable
publicdoubleEndpreis(intanzahl,doublenetto){constdoublemwSt=0.19;varnettoPreis=anzahl*netto;varsteuer=nettoPreis*mwSt;varbruttoPreis=nettoPreis+steuer;returnbruttoPreis;}
publicdoubleEndpreis(intanzahl,doublenetto){ varnettoPreis=anzahl*netto;varbruttoPreis=nettoPreis*1.19;returnbruttoPreis;} Nicht mehr nur
toolgestützt!
publicIDictionary<string,string>ToDictionary(stringconfiguration){varsettings=SplitIntoSettings(configuration);varpairs=SplitIntoKeyValuePairs(settings);varresult=InsertIntoDictionary(pairs);
returnresult;}
publicIDictionary<string,string>ToDictionary(stringconfiguration){returnInsertIntoDictionary(SplitIntoKeyValuePairs(SplitIntoSettings(configuration)));}
Introduce Variable
publicIDictionary<string,string>ToDictionary(stringconfiguration){//Splitconfigurationintosettingsvarsettings=configuration.Split(';');
//Splitsettingsintokey/valuepairsvarpairs=newList<KeyValuePair<string,string>>();foreach(varsettinginsettings){varkeyAndValue=setting.Split('=');pairs.Add(newKeyValuePair<string,string>(keyAndValue[0],keyAndValue[1]));}
//Insertpairsintodictionaryvarresult=newDictionary<string,string>();foreach(varpairinpairs){result.Add(pair.Key,pair.Value);}
returnresult;}
Extract Method
publicIDictionary<string,string>ToDictionary(stringconfiguration){varsettings=SplitIntoSettings(configuration);varpairs=SplitIntoPairs(settings);varresult=InsertIntoDictionary(pairs);returnresult;}
foreach(varsettinginsettings){varkey_and_value=setting.Split('=');result.Add(key_and_value[0],key_and_value[1]);}
Extract Method
foreach(varsettinginsettings){AddSettingToResult(setting,result);}
privatestaticvoidAddSettingToResult(stringsetting,Dictionary<string,string>result){varkey_and_value=setting.Split('=');result.Add(key_and_value[0],key_and_value[1]);}
if(DateTime.Now>=weckZeit){using(varsoundPlayer=newSoundPlayer(@"chimes.wav")){soundPlayer.Play();}timer.Stopp();}
Extract Method
if(WeckzeitErreicht()){using(varsoundPlayer=newSoundPlayer(@"chimes.wav")){soundPlayer.Play();}timer.Stopp();}
privateboolWeckzeitErreicht(){returnDateTime.Now>=weckZeit;}
if(WeckzeitErreicht()){using(varsoundPlayer=newSoundPlayer(@"chimes.wav")){soundPlayer.Play();}timer.Stopp();}
privateboolWeckzeitErreicht(){returnDateTime.Now>=weckZeit;}
Extract Method
if(WeckzeitErreicht()){Wecken();}
publicstringFormat(stringvorname,stringnachname,stringstrasse,stringhn,stringplz,stringort){return$"{vorname}{nachname},{strasse}{hn},{plz}{ort}";}
publicstringFormat(Adresseadresse,stringvorname,stringnachname){return$"{vorname}{nachname},{adresse.Strasse}{adresse.Hn},{adresse.Plz}{adresse.Ort}";}
Extract Class from Parameters
publicclassAdresse{privatestringstrasse;privatestringhn;privatestringplz;privatestringort;
publicAdresse(stringstrasse,stringhn,stringplz,stringort){this.strasse=strasse;this.hn=hn;this.plz=plz;this.ort=ort;}
publicstringStrasse{get{returnstrasse;}}...}
publicstring[]CsvTabellieren(string[]csvZeilen){_csvZeilen=csvZeilen;
RecordsErstellen();int[]breiten=SpaltenbreitenErmitteln();TabelleErstellen(breiten);
return_tabelle;}
publicstring[]CsvTabellieren(string[]csvZeilen){varrecords=RecordsErstellen(csvZeilen);int[]breiten=SpaltenbreitenErmitteln(records);vartabelle=TabelleErstellen(breiten,records);
returntabelle;}
Automatisierte Tests ergänzen
Herausforderungen beim Ergänzen von Tests:• Abhängigkeiten • Aspekte nicht getrennt • Ressourcenzugriffe • Sichtbarkeit • Globaler Zustand
Lösungen:• Zunächst vermehrt Integrationstests,
dann Refactoring und Unit Tests ergänzen. • Mock Frameworks wie
TypeMock Isolator oder Telerik JustMock einsetzen.
Wenige Integrationstests
Viele Unittests
Viele Integrationstests
Wenige Unittests
usingSystem.Collections.Generic;usingSystem.IO;
namespaceressourcen{publicclassCsvReader{publicIEnumerable<string[]>Read(stringfilename){varlines=File.ReadAllLines(filename);foreach(varlineinlines){varfields=line.Split(';');yieldreturnfields;}}}}
Integrierter Ressourcenzugriff
[Test,Isolated]publicvoidIsolated_logic_test(){varsut=newCsvReader();Isolate.WhenCalled(()=>File.ReadAllLines("")).WillReturn(new[]{"a;b;c","1;2;3"});
varrecords=sut.Read("egal.csv").ToArray();
Assert.That(records[0],Is.EqualTo(new[]{"a","b","c"}));Assert.That(records[1],Is.EqualTo(new[]{"1","2","3"}));}
Verhalten der Attrappe beschreiben
publicclassPrivatesZeugs{privatevoidSaveToDatabase(stringdata){Console.WriteLine("Ressourcenzugriff..."+data);thrownewException();}
publicvoidDoSomething(){Console.WriteLine("DoSomething...");SaveToDatabase("datendatendaten...");}}
[TestFixture]publicclassPrivateMethoden{[Test]publicvoidPrivate_Methode_nicht_ausführen(){varsut=newPrivatesZeugs();Isolate.NonPublic.WhenCalled(sut,"SaveToDatabase").IgnoreCall();
sut.DoSomething();}}
Verhalten der Attrappe beschreiben
Komplexe RefactoringsMikado Methode
Change
Change
Change
Change
Change
Change
Change
Change
Change
Hm… ist das wirklich clever?
Change
Change
Cha
Change
Cha
Change
B
Cha
Revert!
Change
B
Cha
Change
B
Cha
Change
B
D
Cha
C
Revert!
Change
B
D
Cha
C
Change
B
D
Cha
C
Change
B
D
Cha
C
Change
B
D
Cha
C
E
Revert!
Change
B
D
Cha
C
E
Change
B
D
Cha
C
E
Change
B
D
Cha
C
E
Change
B
D
F
Cha
C
E
Revert!
Change
B
D
F
Cha
C
E
Change
B
D
F
Cha
C
E
Change
B
D
F
Cha
C
E
Change
B
D
F
A
Cha
C
E
Revert!
Change
B
D
F
A
Cha
C
E
Change
✓
Change
Change
✓
Change
Change
✓
Change
Change
✓
Change
Change
✓
Change
Change
Change
✓
✓Change
✓Change
Hurra!
B
D
F
A
Change
C
E
B
D
F
A
Change
C
E
Ana l
y s e
Re f a c t o r i ng
NachLösungensuchen
DasMikadoZielnotieren
DasZielbzw.dieaktuelleVorbedingungnaivimplementieren
GibteseinProblem?
IstdasMikadoZielerreicht?
Nein CommitderÄnderungenindieVersionskontrolle
Ja
ErgibtdieÄnderungSinn?
Ja
Fertig!
Nein
Nein
NächsteVorbedingungauswählen,umdamitzuarbeiten
DieLösungenalsVorbedingungenimMikadoGraphnotieren
VerwerfenallerÄnderungenmitHilfederVersionskontrolle
Start
Ja
• Änderung naiv ausführen.
•Herausfinden, was kaputt gegangen ist.Dies sind die Vorbedingungen für die Änderung.
•Visualisieren: zum Mikado Graph hinzufügen
• REVERT !!!
•Wiederholen, bis nichts mehr kaputt geht.
Demo
Uhrzeit wird fortlaufend angezeigt
Uhrzeit wird fortlaufend angezeigt
Eigene Timerklasseerstellen
Uhrzeit wird fortlaufend angezeigt
Eigene Timerklasse integrieren
Eigene Timerklasseerstellen
Uhrzeit wird fortlaufend angezeigt
Anzeigen der Uhrzeit auf eigenem Event
Eigene Timerklasse integrieren
Eigene Timerklasseerstellen
Uhrzeit wird fortlaufend angezeigt
Anzeigen von Uhrzeit und REstzeit trennen
Anzeigen der Uhrzeit auf eigenem Event
Eigene Timerklasse integrieren
Eigene Timerklasseerstellen
Ziel
UnterzielUnterziel
Unterziel
Unterziel
Unterziel
Unterziel
Unterziel
Analyse
Refactoring
Vorteile der Mikado Methode
• Immer auslieferbar • Nur auf dem Trunk, kein Branching • Fokus auf das Ziel; nur das Nötigste tun • Visualisierung
Fazit
• Einfache Refactorings: Lesbarkeit • Automatisierte Tests: Korrektheit • Komplexe Refactorings: Wandelbarkeit
Fragen?
http://refactoring-legacy-code.net
http://linkedin.com/in/stefanlieser
https://twitter.com/StefanLieser
http://xing.com/profile/stefan_lieser