JavaRush /Java-Blog /Random-DE /Wie Refactoring in Java funktioniert

Wie Refactoring in Java funktioniert

Veröffentlicht in der Gruppe Random-DE
Beim Erlernen des Programmierens wird viel Zeit mit dem Schreiben von Code verbracht. Die meisten angehenden Entwickler glauben, dass dies ihre zukünftige Tätigkeit ist. Das stimmt teilweise, aber zu den Aufgaben eines Programmierers gehört auch die Pflege und Umgestaltung von Code. Heute sprechen wir über Refactoring. So funktioniert Refactoring in Java – 1

Refactoring im JavaRush-Kurs

Der JavaRush-Kurs behandelt das Thema Refactoring gleich zweimal: Dank der großen Aufgabe besteht die Möglichkeit, echtes Refactoring in der Praxis kennenzulernen, und ein Vortrag über Refactoring in IDEA hilft Ihnen, die automatischen Tools zu verstehen, die das Leben unglaublich einfacher machen.

Was ist Refactoring?

Dies ist eine Änderung der Struktur des Codes, ohne seine Funktionalität zu ändern. Es gibt beispielsweise eine Methode, die zwei Zahlen vergleicht und „ true“ zurückgibt , wenn die erste größer ist, andernfalls „false“ :
public boolean max(int a, int b) {
    if(a > b) {
        return true;
    } else if(a == b) {
        return false;
    } else {
        return false;
    }
}
Das Ergebnis war sehr umständlicher Code. Selbst Anfänger schreiben selten so etwas, aber es besteht ein solches Risiko. Es scheint, warum gibt es hier einen Block, if-elsewenn Sie eine Methode 6 Zeilen kürzer schreiben können:
public boolean max(int a, int b) {
     return a>b;
}
Nun sieht diese Methode einfach und elegant aus, obwohl sie das Gleiche bewirkt wie das obige Beispiel. So funktioniert Refactoring: Es verändert die Struktur des Codes, ohne sein Wesen zu beeinträchtigen. Es gibt viele Refactoring-Methoden und -Techniken, die wir genauer betrachten werden.

Warum ist ein Refactoring erforderlich?

Es gibt verschiedene Gründe. Zum Beispiel das Streben nach Einfachheit und Prägnanz des Codes. Befürworter dieser Theorie glauben, dass Code so prägnant wie möglich sein sollte, auch wenn zum Verständnis Dutzende Zeilen Kommentar erforderlich sind. Andere Entwickler sind der Meinung, dass der Code so umgestaltet werden sollte, dass er mit einer minimalen Anzahl von Kommentaren verständlich ist. Jedes Team wählt seine eigene Position, aber wir müssen bedenken, dass Refactoring keine Reduzierung ist . Sein Hauptziel ist die Verbesserung der Struktur des Codes. In dieses globale Ziel können mehrere Ziele einbezogen werden:
  1. Refactoring verbessert das Verständnis von Code, der von einem anderen Entwickler geschrieben wurde.
  2. Hilft Fehler zu finden und zu beheben;
  3. Ermöglicht Ihnen, die Geschwindigkeit der Softwareentwicklung zu erhöhen;
  4. Verbessert insgesamt die Softwarezusammensetzung.
Wenn das Refactoring längere Zeit nicht durchgeführt wird, kann es zu Entwicklungsschwierigkeiten bis hin zum vollständigen Arbeitsstopp kommen.

„Code riecht“

Wenn Code überarbeitet werden muss, sagen sie, dass er „stinkt“. Natürlich nicht im wörtlichen Sinne, aber so ein Code sieht wirklich nicht besonders schön aus. Im Folgenden betrachten wir die wichtigsten Refactoring-Techniken für die Anfangsphase.

Unnötig große Elemente

Es gibt umständliche Klassen und Methoden, mit denen gerade wegen ihrer enormen Größe nicht effektiv gearbeitet werden kann.

Große Klasse

Eine solche Klasse verfügt über eine große Anzahl von Codezeilen und viele verschiedene Methoden. Normalerweise ist es für einen Entwickler einfacher, einer vorhandenen Klasse eine Funktion hinzuzufügen, als eine neue zu erstellen, weshalb sie wächst. In der Regel ist die Funktionalität dieser Klasse überlastet. In diesem Fall hilft es, einen Teil der Funktionalität in eine separate Klasse zu unterteilen. Wir werden im Abschnitt Refactoring-Techniken ausführlicher darauf eingehen.

Große Methode

Dieser „Geruch“ entsteht, wenn ein Entwickler einer Methode neue Funktionalität hinzufügt. „Warum sollte ich die Parameterprüfung in eine separate Methode einbauen, wenn ich sie hier schreiben kann?“, „Warum ist es notwendig, die Methode zu trennen, um das maximale Element im Array zu finden, lassen wir es hier.“ „Auf diese Weise ist der Code klarer“ und andere Missverständnisse. Es gibt zwei Regeln für das Refactoring einer großen Methode:
  1. Wenn Sie beim Schreiben einer Methode einen Kommentar zum Code hinzufügen möchten, müssen Sie diese Funktionalität in eine separate Methode aufteilen;
  2. Wenn eine Methode mehr als 10–15 Codezeilen benötigt, sollten Sie die Aufgaben und Unteraufgaben identifizieren, die sie ausführt, und versuchen, die Unteraufgaben in eine separate Methode aufzuteilen.
Mehrere Möglichkeiten, eine große Methode zu eliminieren:
  • Trennen Sie einen Teil der Funktionalität einer Methode in eine separate Methode.
  • Wenn lokale Variablen es Ihnen nicht ermöglichen, einen Teil der Funktionalität zu extrahieren, können Sie das gesamte Objekt an eine andere Methode übergeben.

Verwendung vieler primitiver Datentypen

Dieses Problem tritt normalerweise auf, wenn die Anzahl der Felder zum Speichern von Daten in einer Klasse mit der Zeit zunimmt. Wenn Sie beispielsweise primitive Typen anstelle kleiner Objekte zum Speichern von Daten (Währung, Datum, Telefonnummern usw.) oder Konstanten zum Kodieren beliebiger Informationen verwenden. In diesem Fall empfiehlt es sich, die Felder logisch zu gruppieren und in einer separaten Klasse zu platzieren (Auswahl einer Klasse). Sie können auch Methoden zur Verarbeitung dieser Daten in die Klasse einbinden.

Lange Liste von Optionen

Ein recht häufiger Fehler, insbesondere in Kombination mit einer großen Methode. Dies tritt normalerweise auf, wenn die Funktionalität der Methode überlastet ist oder die Methode mehrere Algorithmen kombiniert. Lange Parameterlisten sind sehr schwer zu verstehen und solche Methoden sind unpraktisch in der Anwendung. Daher ist es besser, das gesamte Objekt zu übertragen. Wenn das Objekt nicht über genügend Daten verfügt, lohnt es sich, ein allgemeineres Objekt zu verwenden oder die Funktionalität der Methode so aufzuteilen, dass sie logisch zusammengehörige Daten verarbeitet.

Datengruppen

Logisch verwandte Datengruppen erscheinen häufig im Code. Zum Beispiel Verbindungsparameter zur Datenbank (URL, Benutzername, Passwort, Schemaname usw.). Wenn kein einziges Feld aus der Liste der Elemente entfernt werden kann, handelt es sich bei der Liste um eine Gruppe von Daten, die in einer separaten Klasse platziert werden müssen (Klassenauswahl).

Lösungen, die das Konzept von OOP zerstören

Diese Art von „Geruch“ entsteht, wenn der Entwickler gegen das OOP-Design verstößt. Dies geschieht, wenn er die Möglichkeiten dieses Paradigmas nicht vollständig versteht, sie unvollständig oder falsch nutzt.

Verweigerung der Erbschaft

Wenn eine Unterklasse nur einen minimalen Teil der Funktionen der übergeordneten Klasse nutzt, riecht es nach einer falschen Hierarchie. Typischerweise werden in diesem Fall unnötige Methoden einfach nicht überschrieben oder es werden Ausnahmen ausgelöst. Wenn eine Klasse von einer anderen geerbt wird, bedeutet dies, dass deren Funktionalität nahezu vollständig genutzt wird. Beispiel für eine korrekte Hierarchie: Wie Refactoring in Java funktioniert – 2 Beispiel für eine falsche Hierarchie: Wie Refactoring in Java funktioniert – 3

switch-Anweisung

Was könnte mit einem Betreiber falsch sein switch? Es ist schlecht, wenn das Design sehr komplex ist. Dazu gehören auch viele verschachtelte Blöcke if.

Alternative Klassen mit unterschiedlichen Schnittstellen

Mehrere Klassen machen tatsächlich dasselbe, aber ihre Methoden werden unterschiedlich benannt.

Temporäres Feld

Wenn die Klasse ein temporäres Feld enthält, das das Objekt nur gelegentlich benötigt, wenn es mit Werten gefüllt ist, und die restliche Zeit leer ist oder, Gott bewahre, nulldann „riecht“ der Code und ein solches Design ist zweifelhaft Entscheidung.

Gerüche, die eine Modifikation erschweren

Diese „Gerüche“ sind schwerwiegender. Der Rest beeinträchtigt vor allem das Verständnis des Codes, während diese keine Veränderung ermöglichen. Bei der Einführung von Funktionen gibt die Hälfte der Entwickler auf und die andere Hälfte wird verrückt.

Parallele Vererbungshierarchien

Wenn Sie eine Unterklasse einer Klasse erstellen, müssen Sie eine weitere Unterklasse für eine andere Klasse erstellen.

Einheitliche Abhängigkeitsverteilung

Wenn Sie Änderungen vornehmen, müssen Sie nach allen Abhängigkeiten (Verwendungen) dieser Klasse suchen und viele kleine Änderungen vornehmen. Eine Änderung – Änderungen in vielen Klassen.

Komplexer Modifikationsbaum

Dieser Geruch ist das Gegenteil des vorherigen: Änderungen wirken sich auf eine große Anzahl von Methoden derselben Klasse aus. In der Regel ist die Abhängigkeit in einem solchen Code kaskadierend: Nachdem Sie eine Methode geändert haben, müssen Sie etwas in einer anderen und dann in einer dritten usw. reparieren. Eine Klasse – viele Veränderungen.

„Müll riecht“

Eine eher unangenehme Geruchskategorie, die Kopfschmerzen verursacht. Nutzloser, unnötiger, alter Code. Glücklicherweise haben moderne IDEs und Linters gelernt, vor solchen Gerüchen zu warnen.

Eine große Anzahl von Kommentaren in der Methode

Die Methode enthält in fast jeder Zeile viele erläuternde Kommentare. Dies ist in der Regel mit einem komplexen Algorithmus verbunden, daher ist es besser, den Code in mehrere kleinere Methoden aufzuteilen und ihnen aussagekräftige Namen zu geben.

Code-Duplizierung

Verschiedene Klassen oder Methoden verwenden dieselben Codeblöcke.

Faule Klasse

Die Klasse übernimmt sehr wenig Funktionalität, obwohl viel davon geplant war.

Unbenutzter Code

Eine Klasse, Methode oder Variable wird im Code nicht verwendet und ist „totes Gewicht“.

Übermäßige Kopplung

Diese Geruchskategorie zeichnet sich durch eine große Anzahl unnötiger Verbindungen im Code aus.

Methoden von Drittanbietern

Eine Methode verwendet die Daten eines anderen Objekts viel häufiger als ihre eigenen Daten.

Unangemessene Intimität

Eine Klasse nutzt Servicefelder und Methoden einer anderen Klasse.

Lange Unterrichtsgespräche

Eine Klasse ruft eine andere auf, die Daten von der dritten, jene von der vierten usw. anfordert. Eine derart lange Aufrufkette bedeutet ein hohes Maß an Abhängigkeit von der aktuellen Klassenstruktur.

Klassen-Aufgaben-Dealer

Eine Klasse wird nur benötigt, um eine Aufgabe an eine andere Klasse zu übergeben. Vielleicht sollte es entfernt werden?

Refactoring-Techniken

Im Folgenden werden wir über anfängliche Refactoring-Techniken sprechen, die dabei helfen, die beschriebenen Code-Gerüche zu beseitigen.

Klassenauswahl

Die Klasse führt zu viele Funktionen aus; einige davon müssen in eine andere Klasse verschoben werden. Beispielsweise gibt es eine Klasse Human, die auch eine Wohnadresse enthält, und eine Methode, die die vollständige Adresse bereitstellt:
class Human {
   private String name;
   private String age;
   private String country;
   private String city;
   private String street;
   private String house;
   private String quarter;

   public String getFullAddress() {
       StringBuilder result = new StringBuilder();
       return result
                       .append(country)
                       .append(", ")
                       .append(city)
                       .append(", ")
                       .append(street)
                       .append(", ")
                       .append(house)
                       .append(" ")
                       .append(quarter).toString();
   }
}
Es wäre eine gute Idee, die Adressinformationen und die Methode (Datenverarbeitungsverhalten) in einer separaten Klasse unterzubringen:
class Human {
   private String name;
   private String age;
   private Address address;

   private String getFullAddress() {
       return address.getFullAddress();
   }
}
class Address {
   private String country;
   private String city;
   private String street;
   private String house;
   private String quarter;

   public String getFullAddress() {
       StringBuilder result = new StringBuilder();
       return result
                       .append(country)
                       .append(", ")
                       .append(city)
                       .append(", ")
                       .append(street)
                       .append(", ")
                       .append(house)
                       .append(" ")
                       .append(quarter).toString();
   }
}

Methodenauswahl

Wenn eine Funktionalität in einer Methode gruppiert werden kann, sollte sie in einer separaten Methode platziert werden. Beispielsweise eine Methode, die die Wurzeln einer quadratischen Gleichung berechnet:
public void calcQuadraticEq(double a, double b, double c) {
    double D = b * b - 4 * a * c;
    if (D > 0) {
        double x1, x2;
        x1 = (-b - Math.sqrt(D)) / (2 * a);
        x2 = (-b + Math.sqrt(D)) / (2 * a);
        System.out.println("x1 = " + x1 + ", x2 = " + x2);
    }
    else if (D == 0) {
        double x;
        x = -b / (2 * a);
        System.out.println("x = " + x);
    }
    else {
        System.out.println("Equation has no roots");
    }
}
Lassen Sie uns die Berechnung aller drei möglichen Optionen in separate Methoden verlagern:
public void calcQuadraticEq(double a, double b, double c) {
    double D = b * b - 4 * a * c;
    if (D > 0) {
        dGreaterThanZero(a, b, D);
    }
    else if (D == 0) {
        dEqualsZero(a, b);
    }
    else {
        dLessThanZero();
    }
}

public void dGreaterThanZero(double a, double b, double D) {
    double x1, x2;
    x1 = (-b - Math.sqrt(D)) / (2 * a);
    x2 = (-b + Math.sqrt(D)) / (2 * a);
    System.out.println("x1 = " + x1 + ", x2 = " + x2);
}

public void dEqualsZero(double a, double b) {
    double x;
    x = -b / (2 * a);
    System.out.println("x = " + x);
}

public void dLessThanZero() {
    System.out.println("Equation has no roots");
}
Der Code für jede Methode ist viel kürzer und klarer geworden.

Übertragen des gesamten Objekts

Wenn Sie eine Methode mit Parametern aufrufen, können Sie manchmal Code wie diesen sehen:
public void employeeMethod(Employee employee) {
    // Некоторые действия
    double yearlySalary = employee.getYearlySalary();
    double awards = employee.getAwards();
    double monthlySalary = getMonthlySalary(yearlySalary, awards);
    // Продолжение обработки
}

public double getMonthlySalary(double yearlySalary, double awards) {
     return (yearlySalary + awards)/12;
}
In der Methode employeeMethodwerden bis zu 2 Zeilen zum Abrufen von Werten und deren Speicherung in primitiven Variablen zugewiesen. Manchmal dauern solche Designs bis zu 10 Zeilen. Es ist viel einfacher, das Objekt selbst an die Methode zu übergeben, von wo aus Sie die erforderlichen Daten extrahieren können:
public void employeeMethod(Employee employee) {
    // Некоторые действия
    double monthlySalary = getMonthlySalary(employee);
    // Продолжение обработки
}

public double getMonthlySalary(Employee employee) {
    return (employee.getYearlySalary() + employee.getAwards())/12;
}
Einfach, kurz und prägnant.

Logische Gruppierung von Feldern und deren Platzierung in einer separaten Klasse

Trotz der Tatsache, dass die obigen Beispiele sehr einfach sind und viele beim Betrachten die Frage stellen: „Wer macht das eigentlich?“, machen viele Entwickler aus Unaufmerksamkeit, mangelnder Bereitschaft, den Code umzugestalten, oder einfach „Es wird reichen“ etwas ähnliche Strukturfehler.

Warum Refactoring effektiv ist

Das Ergebnis eines guten Refactorings ist ein Programm, dessen Code leicht lesbar ist, Änderungen an der Programmlogik nicht zur Bedrohung werden und die Einführung neuer Funktionen nicht zur Code-Parsing-Hölle, sondern zu einer angenehmen Beschäftigung für ein paar Tage wird . Refactoring sollte nicht verwendet werden, wenn es einfacher wäre, das Programm von Grund auf neu zu schreiben. Beispielsweise schätzt das Team, dass die Arbeitskosten für das Parsen, Analysieren und Refactoring von Code höher sind als für die Implementierung derselben Funktionalität von Grund auf. Oder der Code, der umgestaltet werden muss, weist viele Fehler auf, die schwer zu debuggen sind. Für die Arbeit eines Programmierers ist es unerlässlich, zu wissen, wie man die Struktur des Codes verbessern kann. Nun, es ist besser, Java-Programmierung bei JavaRush zu lernen – einem Online-Kurs mit Schwerpunkt auf der Praxis. Über 1200 Aufgaben mit sofortiger Überprüfung, etwa 20 Miniprojekte, Spielaufgaben – all das wird Ihnen helfen, sich beim Programmieren sicher zu fühlen. Der beste Zeitpunkt, damit anzufangen ist jetzt :) So funktioniert Refactoring in Java – 4

Ressourcen zum weiteren Eintauchen in das Refactoring

Das bekannteste Buch zum Thema Refactoring ist „Refactoring. Verbesserung des Designs vorhandenen Codes“ von Martin Fowler. Es gibt auch eine interessante Veröffentlichung zum Thema Refactoring, die auf einem früheren Buch basiert – „Refactoring with Patterns“ von Joshua Kiriewski. Apropos Vorlagen. Beim Refactoring ist es immer sehr nützlich, die grundlegenden Muster des Anwendungsdesigns zu kennen. Diese tollen Bücher helfen dabei:
  1. „Design Patterns“ – von Eric Freeman, Elizabeth Freeman, Kathy Sierra, Bert Bates aus der Head First-Reihe;
  2. „Lesbarer Code oder Programmieren als Kunst“ – Dustin Boswell, Trevor Faucher.
  3. „Perfect Code“ von Steve McConnell, der die Prinzipien von schönem und elegantem Code umreißt.
Nun, ein paar Artikel zum Thema Refactoring:
  1. Eine verdammt schwere Aufgabe: Beginnen wir mit der Umgestaltung von Legacy-Code .
  2. Refactoring ;
  3. Refactoring für alle .
    Kommentare
    TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
    GO TO FULL VERSION