CodeGym /Java-Blog /Random-DE /Wie Refactoring in Java funktioniert
Autor
Artem Divertitto
Senior Android Developer at United Tech

Wie Refactoring in Java funktioniert

Veröffentlicht in der Gruppe Random-DE
Wenn Sie das Programmieren lernen, verbringen Sie viel Zeit damit, Code zu schreiben. Die meisten angehenden Entwickler glauben, dass sie dies in Zukunft tun werden. Das stimmt zum Teil, aber zum Job eines Programmierers gehört auch die Pflege und Umgestaltung von Code. Heute werden wir über Refactoring sprechen. So funktioniert Refactoring in Java – 1

Refactoring auf CodeGym

Refactoring wird im CodeGym-Kurs zweimal behandelt: Die große Aufgabe bietet die Möglichkeit, sich durch Übung mit echtem Refactoring vertraut zu machen, und die Refactoring-Lektion in IDEA hilft Ihnen, in automatisierte Tools einzutauchen, die Ihr Leben unglaublich einfacher machen.

Was ist Refactoring?

Es verändert die Struktur des Codes, ohne seine Funktionalität zu ändern. Angenommen, wir haben 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;
        }
    }
Dies ist ein ziemlich unhandlicher Code. Selbst Anfänger würden selten so etwas schreiben, aber es besteht eine Chance. Warum einen if-elseBlock verwenden, wenn Sie die 6-Zeilen-Methode prägnanter schreiben können?

 public boolean max(int a, int b) {
      return a > b;
 }
Jetzt haben wir eine einfache und elegante Methode, die den gleichen Vorgang wie im obigen Beispiel ausführt. So funktioniert Refactoring: Sie ändern die Struktur des Codes, ohne sein Wesen zu beeinträchtigen. Es gibt viele Refactoring-Methoden und -Techniken, die wir uns genauer ansehen werden.

Warum brauchen Sie ein Refactoring?

Es gibt verschiedene Gründe. Zum Beispiel, um Einfachheit und Kürze im Code zu erreichen. Befürworter dieser Theorie glauben, dass Code so prägnant wie möglich sein sollte, auch wenn zum Verständnis mehrere Dutzend Kommentarzeilen erforderlich sind. Andere Entwickler sind davon überzeugt, dass Code umgestaltet werden sollte, um ihn mit möglichst wenigen Kommentaren verständlich zu machen. Jedes Team vertritt seine eigene Position, aber denken Sie daran, dass Refactoring keine Reduzierung bedeutet . Sein Hauptzweck besteht darin, die Struktur des Codes zu verbessern. Zu diesem Gesamtzweck können mehrere Aufgaben gehören:
  1. Refactoring verbessert das Verständnis von Code, der von anderen Entwicklern geschrieben wurde.
  2. Es hilft, Fehler zu finden und zu beheben.
  3. Es kann die Geschwindigkeit der Softwareentwicklung beschleunigen.
  4. Insgesamt verbessert es das Softwaredesign.
Wenn das Refactoring über einen längeren Zeitraum nicht durchgeführt wird, kann es zu Schwierigkeiten bei der Entwicklung kommen, bis hin zum vollständigen Stillstand der Arbeiten.

"Code riecht"

Wenn der Code umgestaltet werden muss, spricht man von einem „Geruch“. Natürlich nicht wörtlich, aber solch ein Code sieht wirklich nicht sehr ansprechend aus. Im Folgenden untersuchen wir grundlegende Refactoring-Techniken für die Anfangsphase.

Unangemessen große Klassen und Methoden

Klassen und Methoden können umständlich sein und aufgrund ihrer enormen Größe kann es unmöglich sein, effektiv mit ihnen zu arbeiten.

Große Klasse

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

Lange Methode

Dieser „Geruch“ entsteht, wenn ein Entwickler einer Methode neue Funktionalität hinzufügt: „Warum sollte ich eine Parameterprüfung in eine separate Methode einfügen, wenn ich den Code hier schreiben kann?“, „Warum benötige ich eine separate Suchmethode, um das Maximum zu finden.“ Element in einem Array? Behalten wir es hier. Der Code wird auf diese Weise klarer“ und andere Missverständnisse dieser Art.

Es gibt zwei Regeln für die Umgestaltung einer langen Methode:

  1. Wenn Sie beim Schreiben einer Methode einen Kommentar hinzufügen möchten, sollten Sie die Funktionalität in eine separate Methode einfügen.
  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 einer separaten Methode unterzubringen.

Es gibt mehrere Möglichkeiten, eine lange Methode zu eliminieren:

  • Verschieben Sie einen Teil der Funktionalität der Methode in eine separate Methode
  • Wenn lokale Variablen Sie daran hindern, einen Teil der Funktionalität zu verschieben, können Sie das gesamte Objekt in eine andere Methode verschieben.

Verwendung vieler primitiver Datentypen

Dieses Problem tritt normalerweise auf, wenn die Anzahl der Felder in einer Klasse mit der Zeit zunimmt. Wenn Sie beispielsweise alles (Währung, Datum, Telefonnummern usw.) in primitiven Typen oder Konstanten statt in kleinen Objekten speichern. In diesem Fall wäre es eine gute Vorgehensweise, eine logische Gruppierung von Feldern in eine separate Klasse (Extraktklasse) zu verschieben. Sie können der Klasse auch Methoden hinzufügen, um die Daten zu verarbeiten.

Zu viele Parameter

Dies ist ein recht häufiger Fehler, insbesondere in Kombination mit einer langen Methode. Normalerweise tritt es auf, wenn eine Methode über zu viele Funktionen verfügt oder wenn eine Methode mehrere Algorithmen implementiert. Lange Parameterlisten sind sehr schwer zu verstehen und die Verwendung von Methoden mit solchen Listen ist unpraktisch. Daher ist es besser, ein ganzes Objekt zu übergeben. Wenn ein Objekt nicht über genügend Daten verfügt, sollten Sie ein allgemeineres Objekt verwenden oder die Funktionalität der Methode so aufteilen, dass jede Methode logisch zusammengehörige Daten verarbeitet.

Datengruppen

Im Code erscheinen häufig Gruppen logisch zusammengehöriger Daten. Zum Beispiel Datenbankverbindungsparameter (URL, Benutzername, Passwort, Schemaname usw.). Wenn kein einziges Feld aus einer Feldliste entfernt werden kann, sollten diese Felder in eine separate Klasse (Extraktklasse) verschoben werden.

Lösungen, die gegen die OOP-Prinzipien verstoßen

Diese „Gerüche“ treten auf, wenn ein Entwickler gegen das ordnungsgemäße OOP-Design verstößt. Dies geschieht, wenn er oder sie die OOP-Funktionen nicht vollständig versteht und sie nicht vollständig oder ordnungsgemäß nutzt.

Versäumnis, die Vererbung zu nutzen

Wenn eine Unterklasse nur eine kleine Teilmenge der Funktionen der übergeordneten Klasse nutzt, dann riecht es nach einer falschen Hierarchie. In diesem Fall werden die überflüssigen Methoden normalerweise einfach nicht überschrieben oder sie lösen Ausnahmen aus. Wenn eine Klasse eine andere erbt, bedeutet dies, dass die untergeordnete Klasse nahezu die gesamte Funktionalität der übergeordneten Klasse nutzt. Beispiel für eine korrekte Hierarchie: Wie Refactoring in Java funktioniert – 2Beispiel für eine falsche Hierarchie: Wie Refactoring in Java funktioniert – 3

Switch-Anweisung

Was könnte an einer switchAussage falsch sein? Schlimm ist es, wenn es sehr komplex wird. Ein damit verbundenes Problem ist eine große Anzahl verschachtelter ifAnweisungen.

Alternative Klassen mit unterschiedlichen Schnittstellen

Mehrere Klassen machen dasselbe, aber ihre Methoden haben unterschiedliche Namen.

Temporäres Feld

Wenn eine Klasse ein temporäres Feld hat, das ein Objekt nur gelegentlich benötigt, wenn sein Wert festgelegt ist, und es leer ist oder, Gott bewahre, nulldie restliche Zeit, dann stinkt der Code. Dies ist eine fragwürdige Designentscheidung.

Gerüche, die eine Veränderung erschweren

Diese Gerüche sind schwerwiegender. Andere Gerüche erschweren vor allem das Verständnis des Codes, hindern Sie jedoch daran, ihn zu ändern. Wenn Sie versuchen, neue Funktionen einzuführen, gibt die Hälfte der Entwickler auf und die andere Hälfte wird verrückt.

Parallele Vererbungshierarchien

Dieses Problem tritt auf, wenn Sie für die Unterklassifizierung einer Klasse eine weitere Unterklasse für eine andere Klasse erstellen müssen.

Gleichmäßig verteilte Abhängigkeiten

Bei allen Änderungen müssen Sie nach allen Verwendungsmöglichkeiten (Abhängigkeiten) einer 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 in einer Klasse aus. In der Regel weist ein solcher Code kaskadierende Abhängigkeiten auf: Wenn Sie eine Methode ändern, müssen Sie etwas in einer anderen korrigieren, dann in der dritten und so weiter. Eine Klasse – viele Veränderungen.

"Müll stinkt"

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 einer Methode

Eine Methode enthält in fast jeder Zeile viele erläuternde Kommentare. Dies ist in der Regel auf einen komplexen Algorithmus zurückzuführen, daher ist es besser, den Code in mehrere kleinere Methoden aufzuteilen und ihnen erklärende Namen zu geben.

Doppelter Code

Verschiedene Klassen oder Methoden verwenden dieselben Codeblöcke.

Faule Klasse

Eine Klasse übernimmt nur sehr wenig Funktionalität, obwohl sie groß geplant war.

Unbenutzter Code

Eine Klasse, Methode oder Variable wird im Code nicht verwendet und ist Eigengewicht.

Übermäßige Konnektivität

Diese Geruchskategorie zeichnet sich durch eine Vielzahl ungerechtfertigter Beziehungen im Code aus.

Externe Methoden

Eine Methode verwendet Daten von einem anderen Objekt viel häufiger als ihre eigenen Daten.

Unangemessene Intimität

Eine Klasse hängt von den Implementierungsdetails einer anderen Klasse ab.

Lange Unterrichtsgespräche

Eine Klasse ruft eine andere auf, die Daten von einer dritten anfordert, die Daten von einer vierten erhält und so weiter. Eine so lange Aufrufkette bedeutet eine hohe Abhängigkeit von der aktuellen Klassenstruktur.

Task-Dealer-Klasse

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

Refactoring-Techniken

Im Folgenden besprechen wir grundlegende Refactoring-Techniken, die dabei helfen können, die beschriebenen Code-Gerüche zu beseitigen.

Extrahieren Sie eine Klasse

Eine Klasse führt zu viele Funktionen aus. Einige von ihnen müssen in eine andere Klasse verschoben werden. Angenommen, wir haben eine HumanKlasse, die auch eine Heimatadresse speichert und über eine Methode verfügt, die die vollständige Adresse zurückgibt:

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 empfiehlt sich, die Adressinformationen und die zugehörige 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();
    }
 }

Extrahieren Sie eine Methode

Wenn eine Methode über einige Funktionen verfügt, die isoliert werden können, sollten Sie sie in einer separaten Methode platzieren. 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");
        }
    }
Wir berechnen jede der drei möglichen Optionen in separaten Methoden:

    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 jeder Methode ist viel kürzer und verständlicher geworden.

Übergeben eines gesamten Objekts

Wenn eine Methode mit Parametern aufgerufen wird, sehen Sie manchmal Code wie diesen:

 public void employeeMethod(Employee employee) {
     // Some actions
     double yearlySalary = employee.getYearlySalary();
     double awards = employee.getAwards();
     double monthlySalary = getMonthlySalary(yearlySalary, awards);
     // Continue processing
 }
 
 public double getMonthlySalary(double yearlySalary, double awards) {
      return (yearlySalary + awards)/12;
 }
Das employeeMethodenthält zwei ganze Zeilen, die dem Empfangen von Werten und deren Speicherung in primitiven Variablen gewidmet sind. Manchmal können solche Konstrukte bis zu 10 Zeilen umfassen. Es ist viel einfacher, das Objekt selbst zu übergeben und es zum Extrahieren der erforderlichen Daten zu verwenden:

 public void employeeMethod(Employee employee) {
     // Some actions
     double monthlySalary = getMonthlySalary(employee);
     // Continue processing
 }
 
 public double getMonthlySalary(Employee employee) {
     return (employee.getYearlySalary() + employee.getAwards())/12;
 }

Einfach, kurz und prägnant.

Logisches Gruppieren von Feldern und Verschieben in ein separates Feld classDespiteDie Tatsache, dass die obigen Beispiele sehr einfach sind und viele von Ihnen sich beim Betrachten fragen werden: „Wer macht das?“ Viele Entwickler machen solche strukturellen Fehler aufgrund von Nachlässigkeit. mangelnde Bereitschaft, den Code umzugestalten, oder einfach die Einstellung „Das ist gut genug“.

Warum Refactoring effektiv ist

Durch gutes Refactoring verfügt ein Programm über leicht lesbaren Code, die Aussicht, seine Logik zu ändern, ist nicht beängstigend und die Einführung neuer Funktionen wird nicht zur Hölle der Codeanalyse, sondern ist für ein paar Tage eine angenehme Erfahrung . Sie sollten nicht umgestalten, wenn es einfacher wäre, ein Programm von Grund auf neu zu schreiben. Angenommen, Ihr Team geht davon aus, dass der Arbeitsaufwand für das Verstehen, Analysieren und Umgestalten von Code größer sein wird als für die Implementierung derselben Funktionalität von Grund auf. Oder wenn der Code, der umgestaltet werden soll, viele Probleme aufweist, die schwer zu debuggen sind. Für die Arbeit eines Programmierers ist es wichtig zu wissen, wie man die Struktur des Codes verbessern kann. Und das Programmieren in Java lernt man am besten mit CodeGym, dem Online-Kurs, bei dem die Praxis im Vordergrund steht. Über 1200 Aufgaben mit sofortiger Überprüfung, etwa 20 Miniprojekte, Spielaufgaben – all dies wird Ihnen helfen, sich beim Programmieren sicher zu fühlen. Der beste Zeitpunkt, damit anzufangen ist jetzt :)

Ressourcen, um tiefer in das Refactoring einzutauchen

Das bekannteste Buch zum Thema Refactoring ist „Refactoring. Improving the Design of Existing Code“ von Martin Fowler. Es gibt auch eine interessante Veröffentlichung zum Thema Refactoring, die auf einem früheren Buch basiert: „Refactoring Using Patterns“ von Joshua Kerievsky. Apropos Muster: Beim Refactoring ist es immer sehr nützlich, grundlegende Designmuster zu kennen. Diese hervorragenden Bücher helfen dabei: Apropos Muster ... Beim Refactoring ist es immer sehr nützlich, grundlegende Designmuster zu kennen. Diese hervorragenden Bücher helfen dabei:
  1. „Design Patterns“ von Eric Freeman, Elizabeth Robson, Kathy Sierra und Bert Bates aus der Head First-Reihe
  2. „Die Kunst des lesbaren Codes“ von Dustin Boswell und Trevor Foucher
  3. „Code Complete“ von Steve McConnell, das die Prinzipien für schönen und eleganten Code darlegt.
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION