Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Wandelbarkeit wieder herstellen - Refactoring C# Legacy Code

803 views

Published on

http://refactoring-legacy-code.net

Die meisten Teams beginnen ihre Projekte nicht auf der sogenannten "grünen Wiese", sondern sie arbeiten an Bestandscode. Dieser Code ist häufig schon sehr alt und über die Jahre durch viele Entwicklerhände gegangen. Da mit den Legacy-Systemen nach wie vor Geld verdient wird, ist Abschalten keine Option. Auch Neuschreiben kommt aufgrund des Umfangs nicht in Frage. Um den Code wieder unter die Kontrolle des Teams zu bringen, muss ein Refactoring her.

In seinem Vortrag gibt Stefan Lieser Antworten auf die Frage, wie man dabei vorgeht. Er zeigt anhand von Beispielen auf, wie mit kleinen toolgestützten Refactoring-Maßnahmen die Lesbarkeit des Codes verbessert werden kann. Ferner zeigt er auf, wie wichtig automatisierte Tests als Sicherheitsnetz sind und wie sie auch bei Legacy-Code ergänzt werden können. Damit ist die Grundlage geschaffen, um mit der Mikado-Methode größere Refactorings umzusetzen, um auf diese Weise die Codebasis wieder wandelbar zu machen und so die hohen Investitionen zu schützen. Alle Beispiele liegen in C# vor, sind aber übertragbar auf andere Sprachen und Plattformen.

Published in: Education
  • Be the first to comment

Wandelbarkeit wieder herstellen - Refactoring C# Legacy Code

  1. 1. Refactoring C#
 Legacy Code Stefan Lieser @StefanLieser http://refactoring-legacy-code.net
  2. 2. Houston, we’ve had a problem. http://er.jsc.nasa.gov/seh/13index.jpg
  3. 3. Wandelbarkeit https://pixabay.com/de/chamäleon-hautnah-exotische-grün-1414084/
  4. 4. Investitionsschutz = Wandelbarkeit Nicht-funktionale Anforderungen Funktionale Anforderungen
  5. 5. Das Problem besteht aus drei Teilen: • Lesbarkeit/Verständlichkeit ist nicht gegeben • Automatisierte Tests fehlen • Komplexe Refactorings sind notwendig
  6. 6. Das Dilemma: • Um Tests zu ergänzen,
 => muss refactored werden.
 • Um zu refactoren,
 => müssen Tests ergänzt werden.
  7. 7. KISS - Keep It Simple Stupid • „Einfach“ in Bezug auf • Schreiben / Erstellen? • Lesen / Modifizieren !
  8. 8. Ein Sicherheitsnetz spannen: • Versionskontrolle • Continuous Integration • Code Reviews • Automatisierte Tests
  9. 9. Begründung für Refactorings: • NICHT: Clean Code Developer, Prinzipien,
 Werte, Craftsmen, …
 • Sondern Kundennutzen: • Neues Feature • Bugfix
  10. 10. Es gibt zwei Arten
 von Refactorings:
 
 einfache und
 komplexe.
  11. 11. Einfache Refactorings Rename Extract Method Introduce Variable
 Introduce Parameter etc. Vollständig werkzeuggestützt durchführbar
  12. 12. Komplexe Refactorings Nicht mehr ausschließlich werkzeuggestützt durchführbar
  13. 13. Einfache Refactorings Lesbarkeit herstellen. Erkenntnisse über den Code im Code sichern.
  14. 14. 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
  15. 15. 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!
  16. 16. public IDictionary<string, string> ToDictionary(string configuration) { var settings = SplitIntoSettings(configuration); var pairs = SplitIntoKeyValuePairs(settings); var result = InsertIntoDictionary(pairs); return result; } public IDictionary<string, string> ToDictionary(string configuration) { return InsertIntoDictionary(SplitIntoKeyValuePairs( SplitIntoSettings(configuration))); } Introduce Variable
  17. 17. public IDictionary<string, string> ToDictionary(string configuration) { // Split configuration into settings var settings = configuration.Split(';'); // Split settings into key/value pairs var pairs = new List<KeyValuePair<string, string>>(); foreach (var setting in settings) { var keyAndValue = setting.Split('='); pairs.Add(new KeyValuePair<string, string>(keyAndValue[0], keyAndValue[1])); } // Insert pairs into dictionary var result = new Dictionary<string, string>(); foreach (var pair in pairs) { result.Add(pair.Key, pair.Value); } return result; } Extract Method
  18. 18. public IDictionary<string, string> ToDictionary(string configuration) { var settings = SplitIntoSettings(configuration); var pairs = SplitIntoPairs(settings); var result = InsertIntoDictionary(pairs); return result; }
  19. 19. 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<string, string> result) { var key_and_value = setting.Split('='); result.Add(key_and_value[0], key_and_value[1]); }
  20. 20. 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; }
  21. 21. 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(); }
  22. 22. 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
  23. 23. 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; } } ... }
  24. 24. 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; }
  25. 25. Automatisierte Tests ergänzen
  26. 26. Herausforderungen beim Ergänzen von Tests: • Abhängigkeiten • Aspekte nicht getrennt • Ressourcenzugriffe • Sichtbarkeit • Globaler Zustand
  27. 27. Lösungen: • Zunächst vermehrt Integrationstests,
 dann Refactoring und Unit Tests ergänzen. • Mock Frameworks wie
 TypeMock Isolator oder Telerik JustMock
 einsetzen.
  28. 28. Wenige Integrationstests Viele Unittests Viele Integrationstests Wenige Unittests
  29. 29. using System.Collections.Generic; using System.IO; namespace ressourcen { public class CsvReader { public IEnumerable<string[]> Read(string filename) { var lines = File.ReadAllLines(filename); foreach (var line in lines) { var fields = line.Split(';'); yield return fields; } } } } Integrierter Ressourcenzugriff
  30. 30. [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
  31. 31. 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..."); } }
  32. 32. [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
  33. 33. Komplexe Refactorings Mikado Methode
  34. 34. Change
  35. 35. Change
  36. 36. Change
  37. 37. Change
  38. 38. Change
  39. 39. Change
  40. 40. Change
  41. 41. Change
  42. 42. Change
  43. 43. Hm… ist das wirklich clever?
  44. 44. Change
  45. 45. Change Cha
  46. 46. Change Cha
  47. 47. Change B Cha
  48. 48. Revert! Change B Cha
  49. 49. Change B Cha
  50. 50. Change B D Cha C
  51. 51. Revert! Change B D Cha C
  52. 52. Change B D Cha C
  53. 53. Change B D Cha C
  54. 54. Change B D Cha C E
  55. 55. Revert! Change B D Cha C E
  56. 56. Change B D Cha C E
  57. 57. Change B D Cha C E
  58. 58. Change B D F Cha C E
  59. 59. Revert! Change B D F Cha C E
  60. 60. Change B D F Cha C E
  61. 61. Change B D F Cha C E
  62. 62. Change B D F A Cha C E
  63. 63. Revert! Change B D F A Cha C E
  64. 64. Change
  65. 65. ✓ Change
  66. 66. Change
  67. 67. ✓ Change
  68. 68. Change
  69. 69. ✓ Change
  70. 70. Change
  71. 71. ✓ Change
  72. 72. Change
  73. 73. ✓ Change
  74. 74. Change
  75. 75. Change ✓
  76. 76. ✓Change
  77. 77. ✓Change
  78. 78. Hurra!
  79. 79. B D F A Change C E
  80. 80. B D F A Change C E A n a l y s e R e f a c t o r i n g
  81. 81. Nach Lösungen suchen Das Mikado Ziel notieren Das Ziel bzw. die aktuelle Vorbedingung naiv implementieren Gibt es ein Problem? Ist das Mikado Ziel erreicht? Nein Commit der Änderungen in die Versionskontrolle Ja Ergibt die Änderung Sinn? Ja Fertig! Nein Nein Nächste Vorbedingung auswählen, um damit zu arbeiten Die Lösungen als Vorbedingungen im Mikado Graph notieren Verwerfen aller Änderungen mit Hilfe der Versionskontrolle Start Ja
  82. 82. • Ä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.
  83. 83. Demo
  84. 84. Uhrzeitwird fortlaufendangezeigt
  85. 85. Uhrzeitwird fortlaufendangezeigt EigeneTimerklasse
 erstellen
  86. 86. Uhrzeitwird fortlaufendangezeigt EigeneTimerklasse integrieren EigeneTimerklasse
 erstellen
  87. 87. Uhrzeitwird fortlaufendangezeigt AnzeigenderUhrzeitauf eigenemEvent EigeneTimerklasse integrieren EigeneTimerklasse
 erstellen
  88. 88. Uhrzeitwird fortlaufendangezeigt AnzeigenvonUhrzeit undREstzeittrennen AnzeigenderUhrzeitauf eigenemEvent EigeneTimerklasse integrieren EigeneTimerklasse
 erstellen
  89. 89. Ziel Unterziel Unterziel Unterziel Unterziel Unterziel Unterziel Unterziel A n a l y s e R e f a c t o r i n g
  90. 90. Vorteile der Mikado Methode • Immer auslieferbar • Nur auf dem Trunk, kein Branching • Fokus auf das Ziel; nur das Nötigste tun • Visualisierung
  91. 91. Fazit • Einfache Refactorings: Lesbarkeit • Automatisierte Tests: Korrektheit • Komplexe Refactorings: Wandelbarkeit
  92. 92. Fragen?
  93. 93. http://refactoring-legacy-code.net http://linkedin.com/in/stefanlieser https://twitter.com/StefanLieser http://xing.com/profile/stefan_lieser

×