Eine kurze Einführung in das Refactoring für Einsteiger
Marta Swiatkowska
Junior Software Engineer
Vielleicht schreibe ich hier über etwas, das für viele offensichtlich ist, aber vielleicht nicht für jeden. Refactoring ist meiner Meinung nach ein kompliziertes Thema, weil es darum geht, den Code zu ändern, ohne seine Funktionsweise zu beeinträchtigen.
Daher ist es für manche unverständlich, dass Refaktorierung ist eigentlich ein Bereich der Programmierung, und es ist auch ein sehr wichtiger Teil der Arbeit des Programmierers. Der Code entwickelt sich ständig weiter, er wird so lange geändert, wie die Möglichkeit besteht, neue Funktionen hinzuzufügen. Er kann jedoch eine Form annehmen, die es nicht mehr erlaubt, neue Funktionalitäten effektiv hinzuzufügen, und es wäre einfacher, das gesamte Programm neu zu schreiben.
Was ist Refactoring?
In der Regel hört man als Antwort, dass die Struktur des Codes durch Anwendung einer Reihe von Refactoring-Transformationen geändert wird, ohne das beobachtbare Verhalten des Codes zu beeinflussen. Das ist richtig. Kürzlich bin ich auch auf eine Definition von Martin Fowler in seinem Buch "Verbesserung der Gestaltung von bestehendem Code" wo er beschreibt Refaktorierung als "große Veränderungen in kleinen Schritten vornehmen". Er beschreibt Refaktorierung als eine Änderung des Codes, die sich nicht auf den Betrieb auswirkt, aber er betont, dass dies in kleinen Schritten geschehen muss.
In dem Buch wird auch befürwortet, dass Refaktorierung die Funktionsweise des Codes nicht beeinträchtigt und darauf hinweist, dass sie sich zu keinem Zeitpunkt auf das Bestehen der Tests auswirkt. Es wird Schritt für Schritt beschrieben, wie man sicher durchführen kann Refaktorierung. Ich mochte sein Buch, weil es einfache Tricks beschreibt, die man bei der täglichen Arbeit anwenden kann.
Warum brauchen wir Refactoring?
Meistens braucht man sie, wenn man eine neue Funktion einführen will und der Code in seiner aktuellen Version dies nicht zulässt oder es ohne Änderungen am Code schwieriger wäre. Sie ist auch dann nützlich, wenn das Hinzufügen weiterer Funktionen zeitlich unrentabel ist, d. h. es wäre schneller, den Code von Grund auf neu zu schreiben. Ich glaube, es wird manchmal vergessen, dass Refaktorierung kann den Code sauberer und lesbarer machen. Martin schreibt in seinem Buch, wie er Refactoring durchführt, wenn er unangenehme Gerüche im Code wahrnimmt und, wie er es ausdrückt, "Es bleibt immer Raum für das Bessere". Und hier hat er mich überrascht, indem er Refactoring als ein Element der alltäglichen Codearbeit sieht. Für mich sind die Codes oft unverständlich, sie zu lesen ist eine kleine Erfahrung, da der Code oft unintuitiv ist.
Ein gut konzipiertes Programm zeichnet sich dadurch aus, dass es ModularitätDank der Modularität ist es ausreichend, nur einen kleinen Teil des Codes zu kennen, um die meisten Änderungen vorzunehmen. Die Modularität macht es auch neuen Mitarbeitern leichter, sich einzuarbeiten und die Arbeit effizienter zu gestalten. Um diese Modularität zu erreichen, müssen zusammengehörige Programmelemente gruppiert werden, wobei die Zusammenhänge verständlich und leicht zu finden sein müssen. Es gibt keine allgemeingültige Faustregel dafür, wie dies zu bewerkstelligen ist. Je mehr man weiß und versteht, wie der Code funktionieren soll, desto besser kann man die Elemente gruppieren, aber manchmal muss man auch testen und überprüfen.
Eine der Regeln für das Refactoring in YAGNIist ein Akronym für "You Aren't Gonna Need It" und leitet sich ab von eXtreme Programmierung (XP) hauptsächlich verwendet in AgilSoftware-Entwicklung Teams. Lange Rede kurzer Sinn, YAGNI besagt, dass nur aktuelle Dinge getan werden sollten. Das bedeutet im Grunde, dass selbst wenn etwas in der Zukunft benötigt werden könnte, es nicht jetzt gemacht werden sollte. Aber wir können auch weitere Erweiterungen nicht unterdrücken, und hier wird die Modularität wichtig.
Wenn Sie über Refaktorierungmuss eines der wichtigsten Elemente, nämlich die Tests, erwähnt werden. Unter Refaktorierungmüssen wir wissen, dass der Code noch funktioniert, denn Refaktorierung ändert nicht die Funktionsweise, sondern die Struktur, so dass alle Tests bestanden werden müssen. Es ist am besten, nach jeder kleinen Änderung Tests für den Teil des Codes durchzuführen, an dem wir gerade arbeiten. Dadurch erhalten wir eine Bestätigung, dass alles so funktioniert, wie es sollte, und es verkürzt die Zeit des gesamten Vorgangs. Das ist es, worüber Martin in seinem Buch spricht - führen Sie so oft wie möglich Tests durch, um nicht einen Schritt zurückzutreten und Zeit mit der Suche nach einer Transformation zu verschwenden, die etwas kaputt gemacht hat.
Code-Refactoring Ohne Tests ist es mühsam, und die Wahrscheinlichkeit, dass etwas schief geht, ist groß. Wenn es möglich ist, wäre es am besten, zumindest einige grundlegende Tests hinzuzufügen, die uns ein wenig Gewissheit geben, dass der Code funktioniert.
Die unten aufgeführten Transformationen sind nur Beispiele, aber sie sind bei der täglichen Programmierung sehr hilfreich:
Funktionsextraktion und Variablenextraktion - wenn die Funktion zu lang ist, prüfen Sie, ob es kleinere Funktionen gibt, die extrahiert werden können. Das Gleiche gilt für lange Zeilen. Diese Transformationen können dabei helfen, Duplikate im Code zu finden. Dank der kleinen Funktionen wird der Code klarer und verständlicher,
Umbenennung von Funktionen und Variablen - die Verwendung der richtigen Namenskonvention ist für eine gute Programmierung unerlässlich. Gut gewählte Variablennamen können eine Menge über den Code aussagen,
Gruppierung der Funktionen in einer Klasse - diese Änderung ist hilfreich, wenn zwei Klassen ähnliche Operationen durchführen, da sie die Länge der Klasse verkürzen kann,
Überschreiben der verschachtelten Anweisung - wenn die Bedingung für einen speziellen Fall erfüllt ist, geben Sie eine Rückgabeanweisung aus, wenn die Bedingung eintritt. Diese Art von Tests wird oft als Schutzklausel bezeichnet. Das Ersetzen einer verschachtelten bedingten Anweisung durch eine Exit-Anweisung ändert die Gewichtung im Code. Das if-else-Konstrukt weist beiden Varianten das gleiche Gewicht zu. Für die Person, die den Code liest, ist dies eine Botschaft, dass jede der beiden Varianten gleich wahrscheinlich und wichtig ist,
Einführung eines Sonderfalls - wenn Sie einige Bedingungen in Ihrem Code häufig verwenden, kann es sich lohnen, dafür eine eigene Struktur zu erstellen. So können die meisten Sonderfallprüfungen durch einfache Funktionsaufrufe ersetzt werden. Der häufigste Wert, der eine spezielle Verarbeitung erfordert, ist null. Daher wird dieses Muster häufig als Null-Objekt bezeichnet. Dieser Ansatz kann jedoch in jedem Sonderfall verwendet werden,
Ersetzung des Polymorphismus für bedingte Anweisungen.
Beispiel
Dies ist ein Artikel über Refaktorierung und ein Beispiel wird benötigt. Ich möchte im Folgenden ein einfaches Refactoring-Beispiel mit der Verwendung von Überschreiben der verschachtelten Anweisung und Ersetzung des Polymorphismus für bedingte Anweisungen. Nehmen wir an, wir haben eine Programmfunktion, die einen Hash mit Informationen darüber zurückgibt, wie man Pflanzen im wirklichen Leben gießt. Diese Informationen würden wahrscheinlich im Modell stehen, aber in diesem Beispiel haben wir sie in der Funktion.
def bewaesserung_info(pflanze)
result = {}
if plant.is_a? Sukkulent || plant.is_a? Kaktus
result = { water_amount: "Ein bisschen" , how_to: "Von unten", wässern_dauer: "2 Wochen" }
elsif plant.is_a? Alocasia || plant.is_a? Maranta
result = { water_amount: "Große Menge", how_to: "Wie Sie es wünschen", watering_duration: "5 Tage" }
elsif plant.is_a? Peperomia
result = { water_amount: "Dicent amount",
how_to: "Von unten! Sie mögen kein Wasser auf den Blättern",
wässern_dauer: "1 Woche" }
sonst
result = { water_amount: "Dicent amount",
how_to: "Wie Sie wünschen",
wässern_dauer: "1 Woche"
}
end
Ergebnis zurückgeben
end
Die Idee ist, sich zu ändern, wenn man zurückkehrt:
if plant.isa? Sukkulent || plant.isa? Kaktus
result = { wateramount: "Ein bisschen" , howto: "Von unten",
Und so geht es weiter, bis wir zu einer Funktion kommen, die wie folgt aussieht:
def bewaesserung_info(pflanze)
return result = { wateramount: "Ein wenig" , howto: "Von unten", wateringduration: "2 Wochen" } if plant.isa? Sukkulent || plant.is_a? Kaktus
return result = { wateramount: "Große Menge", howto: "Wie Sie wollen", Bewässerungsdauer: "5 Tage" } if plant.isa? Alocasia || plant.is_a? Maranta
return result = { water_amount: "Dicent amount",
howto: "Von unten! Sie mögen kein Wasser auf den Blättern",
wateringduration: "1 Woche" } if plant.is_a? Peperomia
return result = { water_amount: "Dicent amount",
how_to: "Wie Sie wünschen",
wässern_dauer: "1 Woche"
}
end
Ganz am Ende hatten wir bereits ein Ergebnis. Und eine gute Angewohnheit ist es, dies Schritt für Schritt zu tun und jede Änderung zu testen. Sie könnten diesen if-Block durch einen switch case ersetzen und es würde sofort besser aussehen, und Sie müssten nicht jedes Mal alle ifs überprüfen. Das würde dann so aussehen:
def bewaesserung_info(pflanze)
swich plant.class.to_string
case Sukkulent, Kaktus
{ wateramount: "Ein wenig" , howto: "Von unten", watering_duration: "2 Wochen" }
Fall Alocasia, Maranta
{ wateramount: "Große Menge", howto: "Wie Sie möchten", watering_duration: "5 Tage" }
Fall Peperomia
{ water_amount: "Dicent amount",
how_to: "Von unten! Sie mögen kein Wasser auf den Blättern",
watering_duration: "1 Woche" }
sonst
{ water_amount: "Dicent amount",
how_to: "Wie Sie wünschen",
watering_duration: "1 Woche" }
end
end
Und dann können Sie die Ersetzen des Polymorphismus für bedingte Anweisungen. Damit soll eine Klasse mit einer Funktion erstellt werden, die den richtigen Wert zurückgibt und sie an die richtige Stelle setzt.
Klasse Sukkulent
...
def bewaesserung_info()
return { wateramount: "Ein bisschen" , howto: "Von unten", watering_duration: "2 Wochen" }
end
end
Klasse Kaktus
...
def bewaesserung_info()
return { wateramount: "Ein bisschen" , howto: "Von unten", watering_duration: "2 Wochen" }
end
end
Klasse Alocasia
...
def bewässerung_info
return { wateramount: "Große Menge", howto: "Wie Sie wollen", watering_duration: "5 Tage" }
end
end
Klasse Maranta
...
def bewässerung_info
return { wateramount: "Große Menge", howto: "Wie Sie wollen", watering_duration: "5 Tage" }
end
end
Klasse Peperomia
...
def gießen_info
return { water_amount: "Dicent amount",
how_to: "Von unten! Sie mögen kein Wasser auf den Blättern",
watering_duration: "1 Woche" }
end
end
Klasse Plant
...
def bewässerung_info
return { water_amount: "Dicent amount",
how_to: "Wie Sie wünschen",
watering_duration: "1 Woche" }
end
end
Und in der Hauptfunktion watering_infofunction wird der Code wie folgt aussehen:
def bewaesserung_info(pflanze)
Pflanze.map(&:Bewässerungsinfo)
end
Natürlich kann diese Funktion entfernt und durch ihren Inhalt ersetzt werden. Mit diesem Beispiel wollte ich die allgemeine Refactoring-Muster.
Zusammenfassung
Refaktorierung ist ein großes Thema. Ich hoffe, dieser Artikel war ein Anreiz, mehr zu lesen. Diese Refactoring-Fähigkeiten helfen Ihnen, Ihre Fehler zu finden und Ihren Workshop für sauberen Code zu verbessern. Ich empfehle die Lektüre von Martins Buch (Improving the Design of Existing Code), das ein ziemlich grundlegendes und hilfreiches Regelwerk von Refaktorierung. Der Autor zeigt verschiedene Transformationen Schritt für Schritt mit einer vollständigen Erklärung und Motivation und Tipps, wie man Fehler zu vermeiden in Refaktorierung. Aufgrund seiner Vielseitigkeit ist es ein reizvolles Buch für Frontend- und Backend-Entwickler.