Refactoring C# Legacy Code - .Das Problem besteht aus drei Teilen: •...

download Refactoring C# Legacy Code - .Das Problem besteht aus drei Teilen: • Lesbarkeit/Verst¤ndlichkeit

of 95

  • date post

    12-Aug-2019
  • Category

    Documents

  • view

    213
  • download

    0

Embed Size (px)

Transcript of Refactoring C# Legacy Code - .Das Problem besteht aus drei Teilen: •...

  • Refactoring C#
 Legacy Code

    Stefan Lieser @StefanLieser

    http://refactoring-legacy-code.net

    http://refactoring-legacy-code.net 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 Arten
 von Refactorings:


    
 einfache und


    komplexe.

  • Einfache Refactorings Rename Extract Method Introduce Variable
 Introduce Parameter etc.

    Vollständig werkzeuggestützt durchführbar

  • Komplexe Refactorings Nicht mehr ausschließlich

    werkzeuggestützt durchführbar

  • Einfache Refactorings Lesbarkeit herstellen.

    Erkenntnisse über den Code im Code sichern.

  • public double Endpreis(int anzahl, double netto) { 
 var nettoPreis = anzahl * netto; return nettoPreis * 1.19; }

    public double Endpreis(int anzahl, double netto) { 
 return (anzahl * netto) * 1.19; }

    public double Endpreis(int anzahl, double netto) { 
 var nettoPreis = anzahl * netto; var bruttoPreis = nettoPreis * 1.19; return bruttoPreis; }

    Introduce Variable

    Introduce Variable

  • public double Endpreis(int anzahl, double netto) { const double mwSt = 0.19; var nettoPreis = anzahl * netto; var steuer = nettoPreis * mwSt; var bruttoPreis = nettoPreis + steuer; return bruttoPreis; }

    public double Endpreis(int anzahl, double netto) { 
 var nettoPreis = anzahl * netto; var bruttoPreis = nettoPreis * 1.19; return bruttoPreis; } Nicht mehr nur

    toolgestützt!

  • public IDictionary ToDictionary(string configuration) { return InsertIntoDictionary(SplitIntoKeyValuePairs( SplitIntoSettings(configuration))); }

    "a=1;b=2;c=3" => {{"a", "1"},{"b", "2"}, {"c", "3"}}

  • public IDictionary ToDictionary(string configuration) { var settings = SplitIntoSettings(configuration); var pairs = SplitIntoKeyValuePairs(settings); var result = InsertIntoDictionary(pairs);

    return result; }

    public IDictionary ToDictionary(string configuration) { return InsertIntoDictionary(SplitIntoKeyValuePairs( SplitIntoSettings(configuration))); }

    Introduce Variable

  • public IDictionary ToDictionary(string configuration) { // Split configuration into settings var settings = configuration.Split(';');

    // Split settings into key/value pairs var pairs = new List(); foreach (var setting in settings) { var keyAndValue = setting.Split('='); pairs.Add(new KeyValuePair(keyAndValue[0], keyAndValue[1])); }

    // Insert pairs into dictionary var result = new Dictionary(); foreach (var pair in pairs) { result.Add(pair.Key, pair.Value); }

    return result; }

    Extract Method

  • public IDictionary ToDictionary(string configuration) { var settings = SplitIntoSettings(configuration); var pairs = SplitIntoPairs(settings); var result = InsertIntoDictionary(pairs); return result; }

  • foreach (var setting in settings) { var key_and_value = setting.Split('='); result.Add(key_and_value[0], key_and_value[1]); }

    Extract Method

    foreach (var setting in settings) { AddSettingToResult(setting, result); }

    private static void AddSettingToResult(string setting, Dictionary result) { var key_and_value = setting.Split('='); result.Add(key_and_value[0], key_and_value[1]); }

  • if (DateTime.Now >= weckZeit) { using (var soundPlayer = new SoundPlayer(@"chimes.wav")) { soundPlayer.Play(); } timer.Stopp(); }

    Extract Method

    if (WeckzeitErreicht()) { using (var soundPlayer = new SoundPlayer(@"chimes.wav")) { soundPlayer.Play(); } timer.Stopp(); }

    private bool WeckzeitErreicht() { return DateTime.Now >= weckZeit; }

  • if (WeckzeitErreicht()) { using (var soundPlayer = new SoundPlayer(@"chimes.wav")) { soundPlayer.Play(); } timer.Stopp(); }

    private bool WeckzeitErreicht() { return DateTime.Now >= weckZeit; }

    Extract Method

    if (WeckzeitErreicht()) { Wecken(); }

  • public string Format(string vorname, string nachname, string strasse, string hn, string plz, string ort) { return $"{vorname} {nachname}, {strasse} {hn}, {plz} {ort}"; }

    public string Format(Adresse adresse, string vorname, string nachname) { return $"{vorname} {nachname}, {adresse.Strasse} {adresse.Hn}, {adresse.Plz} {adresse.Ort}"; }

    Extract Class from Parameters

  • public class Adresse { private string strasse; private string hn; private string plz; private string ort;

    public Adresse(string strasse, string hn, string plz, string ort) { this.strasse = strasse; this.hn = hn; this.plz = plz; this.ort = ort; }

    public string Strasse { get { return strasse; } } ... }

  • public string[] CsvTabellieren(string[] csvZeilen) { _csvZeilen = csvZeilen;

    RecordsErstellen(); int[] breiten = SpaltenbreitenErmitteln(); TabelleErstellen(breiten);

    return _tabelle; }

    public string[] CsvTabellieren(string[] csvZeilen) { var records = RecordsErstellen(csvZeilen); int[] breiten = SpaltenbreitenErmitteln(records); var tabelle = TabelleErstellen(breiten, records);

    return tabelle; }

  • 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

  • using System.Collections.Generic; using System.IO;

    namespace ressourcen { public class CsvReader { public IEnumerable Read(string filename) { var lines = File.ReadAllLines(filename); foreach (var line in lines) { var fields = line.Split(';'); yield return fields; } } } }

    Integrierter Ressourcenzugriff

  • [Test, Isolated] public void Isolated_logic_test() { var sut = new CsvReader(); Isolate .WhenCalled(() => File.ReadAllLines("")) .WillReturn(new[] { "a;b;c", "1;2;3" });

    var records = 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

  • public class PrivatesZeugs { private void SaveToDatabase(string data) { Console.WriteLine("Ressourcenzugriff..." + data); throw new Exception(); }

    public void DoSomething() { Console.WriteLine("DoSomething..."); SaveToDatabase("daten daten daten..."); } }

  • [TestFixture] public class PrivateMethoden { [Test] public void Private_Methode_nicht_ausführen() { var sut = new PrivatesZeugs(); Isolate.NonPublic.WhenCalled(sut, "SaveToDatabase").IgnoreCall();

    sut.DoSomething(); } }

    Verhalten der Attrappe beschreiben

  • Komplexe Refactorings Mikado Methode

  • Change

  • Change

  • Change

  • Change

  • Change

  • Change

  • Change

  • Change

  • Change

  • Hm… ist das wirklich clever?

  • Change

  • Change

    Cha

  • Change

    Cha

  • Change

    B

    Cha

  • Revert!

    Chang