CodeGym/Blog Java/Random-PL/Jak działa refaktoryzacja w Javie
Autor
Artem Divertitto
Senior Android Developer at United Tech

Jak działa refaktoryzacja w Javie

Opublikowano w grupie Random-PL
Ucząc się programowania, spędzasz dużo czasu na pisaniu kodu. Większość początkujących programistów uważa, że ​​właśnie tym będą się zajmować w przyszłości. Jest to częściowo prawda, ale praca programisty obejmuje również konserwację i refaktoryzację kodu. Dzisiaj porozmawiamy o refaktoryzacji. Jak działa refaktoryzacja w Javie — 1

Refaktoryzacja w CodeGym

Refaktoryzacja jest omawiana dwukrotnie na kursie CodeGym: Wielkie zadanie daje możliwość zapoznania się z prawdziwym refaktoringiem poprzez praktykę, a lekcja o refaktoryzacji w IDEA pomaga zanurzyć się w zautomatyzowane narzędzia, które niesamowicie ułatwią Ci życie.

Co to jest refaktoryzacja?

Jest to zmiana struktury kodu bez zmiany jego funkcjonalności. Załóżmy na przykład, że mamy metodę, która porównuje 2 liczby i zwraca prawdę , jeśli pierwsza jest większa, i fałsz w przeciwnym razie:
public boolean max(int a, int b) {
    if(a > b) {
        return true;
    } else if (a == b) {
        return false;
    } else {
        return false;
    }
}
To raczej nieporęczny kod. Nawet początkujący rzadko by coś takiego napisali, ale jest szansa. Po co używać if-elsebloku, jeśli można bardziej zwięźle napisać metodę 6-wierszową?
public boolean max(int a, int b) {
     return a > b;
}
Teraz mamy prostą i elegancką metodę, która wykonuje tę samą operację, co w powyższym przykładzie. Tak działa refaktoryzacja: zmieniasz strukturę kodu bez wpływu na jego istotę. Istnieje wiele metod i technik refaktoryzacji, którym przyjrzymy się bliżej.

Dlaczego potrzebujesz refaktoryzacji?

Jest kilka powodów. Na przykład, aby osiągnąć prostotę i zwięzłość kodu. Zwolennicy tej teorii uważają, że kod powinien być jak najbardziej zwięzły, nawet jeśli do jego zrozumienia potrzeba kilkudziesięciu linijek komentarzy. Inni programiści są przekonani, że kod powinien być refaktoryzowany, aby był zrozumiały przy minimalnej liczbie komentarzy. Każdy zespół przyjmuje swoje stanowisko, ale pamiętaj, że refaktoryzacja nie oznacza redukcji . Jego głównym celem jest poprawa struktury kodu. Do tego ogólnego celu można zaliczyć kilka zadań:
  1. Refaktoryzacja poprawia zrozumienie kodu napisanego przez innych programistów.
  2. Pomaga znaleźć i naprawić błędy.
  3. Może przyspieszyć tempo tworzenia oprogramowania.
  4. Ogólnie rzecz biorąc, poprawia projektowanie oprogramowania.
Jeśli refaktoryzacja nie jest wykonywana przez dłuższy czas, rozwój może napotkać trudności, w tym całkowite zatrzymanie pracy.

„Pachnie kodem”

Kiedy kod wymaga refaktoryzacji, mówi się, że ma „zapach”. Oczywiście nie dosłownie, ale taki kod naprawdę nie wygląda zbyt zachęcająco. Poniżej omówimy podstawowe techniki refaktoryzacji na etapie początkowym.

Nieracjonalnie duże klasy i metody

Klasy i metody mogą być uciążliwe, niemożliwe do efektywnej pracy właśnie ze względu na ich ogromny rozmiar.

Duża klasa

Taka klasa ma ogromną liczbę linii kodu i wiele różnych metod. Deweloperowi zwykle łatwiej jest dodać funkcję do istniejącej klasy niż stworzyć nową, dlatego klasa się rozrasta. Z reguły w takiej klasie mieści się zbyt wiele funkcji. W takim przypadku pomocne jest przeniesienie części funkcjonalności do osobnej klasy. Porozmawiamy o tym bardziej szczegółowo w części poświęconej technikom refaktoryzacji.

Metoda długa

Ten „zapach” pojawia się, gdy programista dodaje nową funkcjonalność do metody: „Dlaczego miałbym umieszczać sprawdzanie parametrów w osobnej metodzie, skoro mogę napisać kod tutaj?”, „Dlaczego potrzebuję osobnej metody wyszukiwania, aby znaleźć maksimum element w tablicy? Zostawmy to tutaj. W ten sposób kod będzie bardziej przejrzysty” i inne tego typu nieporozumienia.

Istnieją dwie zasady refaktoryzacji długiej metody:

  1. Jeśli masz ochotę dodać komentarz podczas pisania metody, powinieneś umieścić tę funkcjonalność w osobnej metodzie.
  2. Jeśli metoda zajmuje więcej niż 10-15 wierszy kodu, należy zidentyfikować zadania i podzadania, które wykonuje, i spróbować umieścić podzadania w osobnej metodzie.

Istnieje kilka sposobów na wyeliminowanie długiej metody:

  • Przenieś część funkcjonalności metody do osobnej metody
  • Jeśli zmienne lokalne uniemożliwiają przeniesienie części funkcjonalności, możesz przenieść cały obiekt do innej metody.

Używanie wielu prymitywnych typów danych

Ten problem zwykle występuje, gdy liczba pól w klasie rośnie w czasie. Na przykład, jeśli przechowujesz wszystko (waluta, data, numery telefonów itp.) w prymitywnych typach lub stałych zamiast małych obiektów. W takim przypadku dobrą praktyką byłoby przeniesienie logicznego zgrupowania pól do osobnej klasy (wyodrębnij klasę). Możesz także dodać metody do klasy, aby przetwarzać dane.

Zbyt wiele parametrów

Jest to dość częsty błąd, szczególnie w połączeniu z długą metodą. Zwykle występuje, gdy metoda ma zbyt dużą funkcjonalność lub jeśli metoda implementuje wiele algorytmów. Długie listy parametrów są bardzo trudne do zrozumienia, a używanie metod z takimi listami jest niewygodne. W rezultacie lepiej przekazać cały obiekt. Jeśli obiekt nie ma wystarczającej ilości danych, należy użyć bardziej ogólnego obiektu lub podzielić funkcjonalność metody tak, aby każda metoda przetwarzała logicznie powiązane dane.

Grupy danych

W kodzie często pojawiają się grupy logicznie powiązanych danych. Na przykład parametry połączenia z bazą danych (adres URL, nazwa użytkownika, hasło, nazwa schematu itp.). Jeśli z listy pól nie da się usunąć ani jednego pola, wówczas należy przenieść te pola do osobnej klasy (klasy ekstraktu).

Rozwiązania naruszające zasady OOP

Te „zapachy” pojawiają się, gdy programista narusza właściwy projekt OOP. Dzieje się tak, gdy nie rozumie on w pełni możliwości OOP i nie potrafi ich w pełni lub właściwie wykorzystać.

Nieużycie dziedziczenia

Jeśli podklasa używa tylko małego podzbioru funkcji klasy nadrzędnej, to pachnie niewłaściwą hierarchią. Kiedy tak się dzieje, zwykle zbędne metody po prostu nie są zastępowane lub zgłaszają wyjątki. Jedna klasa dziedziczy inną, co oznacza, że ​​klasa potomna wykorzystuje prawie całą funkcjonalność klasy nadrzędnej. Przykład prawidłowej hierarchii: Jak działa refaktoryzacja w Javie — 2Przykład nieprawidłowej hierarchii: Jak działa refaktoryzacja w Javie — 3

Instrukcja przełączania

Co może być nie tak z switchoświadczeniem? Źle jest, gdy staje się to bardzo skomplikowane. Podobnym problemem jest duża liczba zagnieżdżonych ifinstrukcji.

Alternatywne klasy z różnymi interfejsami

Wiele klas robi to samo, ale ich metody mają różne nazwy.

Pole tymczasowe

Jeśli klasa ma tymczasowe pole, którego obiekt potrzebuje tylko od czasu do czasu, gdy jego wartość jest ustawiona, i jest ono puste lub, nie daj Boże, nullprzez resztę czasu, to kod śmierdzi. To wątpliwa decyzja projektowa.

Zapachy utrudniające modyfikację

Te zapachy są poważniejsze. Inne zapachy głównie utrudniają zrozumienie kodu, ale uniemożliwiają jego modyfikację. Kiedy próbujesz wprowadzić jakieś nowe funkcje, połowa programistów rezygnuje, a połowa wariuje.

Hierarchie dziedziczenia równoległego

Ten problem objawia się, gdy podklasa klasy wymaga utworzenia innej podklasy dla innej klasy.

Równomiernie rozłożone zależności

Wszelkie modyfikacje wymagają wyszukania wszystkich zastosowań (zależności) klasy i wprowadzenia wielu drobnych zmian. Jedna zmiana — edycje w wielu klasach.

Złożone drzewo modyfikacji

Ten zapach jest przeciwieństwem poprzedniego: zmiany dotyczą dużej liczby metod w jednej klasie. Z reguły taki kod ma zależność kaskadową: zmiana jednej metody wymaga poprawienia czegoś w innej, potem w trzeciej i tak dalej. Jedna klasa — wiele zmian.

„Zapach śmieci”

Dość nieprzyjemna kategoria zapachów, która powoduje bóle głowy. Bezużyteczny, niepotrzebny, stary kod. Na szczęście współczesne IDE i lintery nauczyły się ostrzegać przed takimi zapachami.

Duża liczba komentarzy w metodzie

Metoda ma wiele komentarzy wyjaśniających w prawie każdym wierszu. Zwykle wynika to ze złożonego algorytmu, dlatego lepiej podzielić kod na kilka mniejszych metod i nadać im objaśniające nazwy.

Zduplikowany kod

Różne klasy lub metody używają tych samych bloków kodu.

Leniwa klasa

Klasa ma bardzo małą funkcjonalność, chociaż planowano, że będzie duża.

Niewykorzystany kod

Klasa, metoda lub zmienna nie jest używana w kodzie i jest martwa.

Nadmierna łączność

Ta kategoria zapachów charakteryzuje się dużą liczbą nieuzasadnionych zależności w kodzie.

Metody zewnętrzne

Metoda wykorzystuje dane z innego obiektu znacznie częściej niż własne dane.

Niewłaściwa intymność

Klasa zależy od szczegółów implementacji innej klasy.

Długie rozmowy klasowe

Jedna klasa wywołuje inną, która żąda danych od trzeciej, która otrzymuje dane od czwartej i tak dalej. Tak długi łańcuch wezwań oznacza dużą zależność od obecnej struktury klasowej.

Klasa dealera zadań

Klasa jest potrzebna tylko do wysłania zadania do innej klasy. Może należałoby go usunąć?

Techniki refaktoryzacji

Poniżej omówimy podstawowe techniki refaktoryzacji, które mogą pomóc wyeliminować opisane zapachy kodu.

Wyodrębnij klasę

Klasa wykonuje zbyt wiele funkcji. Część z nich trzeba przenieść do innej klasy. Załóżmy na przykład, że mamy Humanklasę, która również przechowuje adres domowy i ma metodę, która zwraca pełny adres:
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();
    }
 }
Dobrą praktyką jest umieszczenie informacji adresowych i powiązanej metody (zachowania przetwarzania danych) w osobnej klasie:
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();
   }
}

Wyodrębnij metodę

Jeśli metoda ma pewną funkcjonalność, którą można wyizolować, należy umieścić ją w osobnej metodzie. Na przykład metoda, która oblicza pierwiastki równania kwadratowego:
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");
    }
}
Każdą z trzech możliwych opcji obliczamy osobnymi metodami:
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");
}
Kod każdej metody stał się znacznie krótszy i łatwiejszy do zrozumienia.

Mijanie całego obiektu

Kiedy metoda jest wywoływana z parametrami, możesz czasami zobaczyć taki kod:
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;
}
Posiada employeeMethod2 całe linie poświęcone pobieraniu wartości i przechowywaniu ich w zmiennych pierwotnych. Czasami takie konstrukcje mogą zająć do 10 linii. Znacznie łatwiej jest przekazać sam obiekt i użyć go do wydobycia niezbędnych danych:
public void employeeMethod(Employee employee) {
    // Some actions
    double monthlySalary = getMonthlySalary(employee);
    // Continue processing
}

public double getMonthlySalary(Employee employee) {
    return (employee.getYearlySalary() + employee.getAwards())/12;
}

Prosto, krótko i zwięźle.

Logiczne pogrupowanie pól i przeniesienie ich do osobnego classDespitefaktu, że powyższe przykłady są bardzo proste i patrząc na nie, wielu z was może zapytać: „Kto to robi?”, wielu programistów popełnia takie błędy strukturalne z powodu nieuwagi, niechęć do refaktoryzacji kodu lub po prostu postawa „to wystarczy”.

Dlaczego refaktoryzacja jest skuteczna

Dzięki dobremu refaktoryzacji program ma czytelny kod, perspektywa zmiany jego logiki nie jest straszna, a wprowadzanie nowych funkcji nie staje się piekłem analizy kodu, ale przyjemnym doświadczeniem na kilka dni . Nie powinieneś refaktoryzować, jeśli łatwiej byłoby napisać program od zera. Załóżmy na przykład, że Twój zespół szacuje, że praca potrzebna do zrozumienia, przeanalizowania i refaktoryzacji kodu będzie większa niż wdrożenie tej samej funkcjonalności od podstaw. Lub jeśli kod do refaktoryzacji ma wiele problemów, które są trudne do debugowania. Wiedza o tym, jak poprawić strukturę kodu, jest niezbędna w pracy programisty. A nauka programowania w Javie jest najlepsza na CodeGym, kursie online, który kładzie nacisk na praktykę. Ponad 1200 zadań z natychmiastową weryfikacją, około 20 mini-projektów, zadania w grze — to wszystko sprawi, że poczujesz się pewnie w programowaniu. Najlepszy czas by zacząć jest teraz :)

Zasoby umożliwiające dalsze zagłębienie się w refaktoryzację

Najbardziej znaną książką na temat refaktoryzacji jest „Refactoring. Improving the Design of Existing Code” autorstwa Martina Fowlera. Jest też ciekawa publikacja na temat refaktoryzacji, oparta na poprzedniej książce: „Refactoring using Patterns” autorstwa Joshua Kerievsky. Mówiąc o wzorcach... Podczas refaktoryzacji zawsze bardzo przydatna jest znajomość podstawowych wzorców projektowych. Pomogą w tym te doskonałe książki: Mówiąc o wzorcach... Podczas refaktoryzacji zawsze bardzo przydatna jest znajomość podstawowych wzorców projektowych. Pomogą w tym te doskonałe książki:
  1. „Wzorce projektowe” autorstwa Erica Freemana, Elizabeth Robson, Kathy Sierra i Berta Batesa z serii Head First
  2. „Sztuka czytelnego kodu” Dustina Boswella i Trevora Fouchera
  3. „Code Complete” Steve'a McConnella, który określa zasady pięknego i eleganckiego kodu.
Komentarze
  • Popularne
  • Najnowsze
  • Najstarsze
Musisz się zalogować, aby dodać komentarz
Ta strona nie ma jeszcze żadnych komentarzy