JavaRush /Blog Java /Random-PL /Jak działa refaktoryzacja w Javie

Jak działa refaktoryzacja w Javie

Opublikowano w grupie Random-PL
Ucząc się programowania, dużo czasu spędza się na pisaniu kodu. Większość początkujących programistów uważa, że ​​jest to ich przyszłe zajęcie. Po części jest to prawdą, ale do zadań programisty należy także utrzymanie i refaktoryzacja kodu. Dziś porozmawiamy o refaktoryzacji. Jak działa refaktoryzacja w Javie - 1

Refaktoryzacja w kursie JavaRush

Kurs JavaRush dwukrotnie omawia temat refaktoryzacji: Dzięki dużemu zadaniu istnieje możliwość zapoznania się z prawdziwym refaktoryzacją w praktyce, a wykład na temat refaktoryzacji w IDEA pomoże Ci zrozumieć automatyczne narzędzia, które niesamowicie ułatwiają życie.

Co to jest refaktoryzacja?

Jest to zmiana struktury kodu bez zmiany jego funkcjonalności. Na przykład istnieje metoda, która porównuje 2 liczby i zwraca wartość true , jeśli pierwsza jest większa, lub false w przeciwnym razie:
public boolean max(int a, int b) {
    if(a > b) {
        return true;
    } else if(a == b) {
        return false;
    } else {
        return false;
    }
}
Rezultatem był bardzo uciążliwy kod. Nawet początkujący rzadko piszą coś takiego, ale istnieje takie ryzyko. Wydawałoby się, po co tu jest blok, if-elseskoro można napisać metodę 6 linii krócej:
public boolean max(int a, int b) {
     return a>b;
}
Teraz ta metoda wygląda prosto i elegancko, chociaż działa tak samo, jak w powyższym przykładzie. Tak działa refaktoryzacja: zmienia strukturę kodu bez wpływu na jego istotę. Istnieje wiele metod i technik refaktoryzacji, które rozważymy bardziej szczegółowo.

Dlaczego refaktoryzacja jest potrzebna?

Jest kilka powodów. Na przykład dążenie do prostoty i zwięzłości kodu. Zwolennicy tej teorii uważają, że kod powinien być jak najbardziej zwięzły, nawet jeśli jego zrozumienie wymaga kilkudziesięciu linijek komentarza. Inni programiści uważają, że kod należy poddać refaktoryzacji, aby był zrozumiały przy minimalnej liczbie komentarzy. Każdy zespół wybiera własne stanowisko, jednak trzeba pamiętać, że refaktoryzacja to nie redukcja . Jego głównym celem jest ulepszenie struktury kodu. W tym celu globalnym można uwzględnić kilka celów:
  1. Refaktoryzacja poprawia zrozumienie kodu napisanego przez innego programistę;
  2. Pomaga znaleźć i naprawić błędy;
  3. Pozwala zwiększyć szybkość tworzenia oprogramowania;
  4. Ogólnie poprawia kompozycję oprogramowania.
Jeśli refaktoryzacja nie będzie przeprowadzana przez dłuższy czas, mogą pojawić się trudności rozwojowe, aż do całkowitego zatrzymania pracy.

„Kod śmierdzi”

Kiedy kod wymaga refaktoryzacji, mówią, że „pachnie”. Oczywiście nie dosłownie, ale taki kod naprawdę nie wygląda zbyt ładnie. Poniżej rozważymy główne techniki refaktoryzacji na etapie początkowym.

Niepotrzebnie duże elementy

Istnieją kłopotliwe klasy i metody, z którymi nie można efektywnie pracować 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. Zwykle programiście łatwiej jest dodać funkcję do istniejącej klasy niż stworzyć nową, dlatego się rozwija. Z reguły funkcjonalność tej klasy jest przeciążona. W tym przypadku pomaga wydzielenie części funkcjonalności do osobnej klasy. Porozmawiamy o tym bardziej szczegółowo w sekcji Techniki refaktoryzacji.

Wielka metoda

Ten „zapach” pojawia się, gdy programista dodaje nową funkcjonalność do metody. „Dlaczego miałbym umieścić sprawdzanie parametrów w osobnej metodzie, skoro mogę to napisać tutaj?”, „Dlaczego konieczne jest rozdzielenie metody znajdowania maksymalnego elementu w tablicy, zostawmy to tutaj. W ten sposób kod jest wyraźniejszy” i inne nieporozumienia. Istnieją dwie zasady refaktoryzacji dużej metody:
  1. Jeśli podczas pisania metody chcesz dodać komentarz do kodu, musisz wydzielić tę funkcjonalność w osobną metodę;
  2. Jeśli metoda zajmuje więcej niż 10-15 linii kodu, powinieneś zidentyfikować zadania i podzadania, które wykonuje, i spróbować oddzielić podzadania w ramach osobnej metody.
Kilka sposobów na wyeliminowanie dużej metody:
  • Oddziel część funkcjonalności metody w oddzielną metodę;
  • Jeśli zmienne lokalne nie pozwalają na wyodrębnienie części funkcjonalności, możesz przekazać cały obiekt do innej metody.

Używanie wielu prymitywnych typów danych

Zwykle ten problem występuje, gdy liczba pól do przechowywania danych w klasie rośnie z czasem. Na przykład, jeśli używasz typów pierwotnych zamiast małych obiektów do przechowywania danych (waluta, data, numery telefonów itp.) lub stałych do kodowania dowolnych informacji. Dobrą praktyką w tym przypadku byłoby logiczne pogrupowanie pól i umieszczenie ich w osobnej klasie (wybranie klasy). W klasie możesz także uwzględnić metody przetwarzania tych danych.

Długa lista opcji

Dość częsty błąd, szczególnie w połączeniu z dużą metodą. Zwykle ma to miejsce, gdy funkcjonalność metody jest przeciążona lub metoda łączy w sobie kilka algorytmów. Długie listy parametrów są bardzo trudne do zrozumienia, a stosowanie takich metod jest niewygodne. Dlatego lepiej przenieść cały obiekt. Jeśli obiekt nie posiada wystarczającej ilości danych, warto zastosować obiekt bardziej ogólny lub podzielić funkcjonalność metody tak, aby przetwarzała logicznie powiązane dane.

Grupy danych

Logicznie powiązane grupy danych często pojawiają się w kodzie. Na przykład parametry połączenia z bazą danych (adres URL, nazwa użytkownika, hasło, nazwa schematu itp.). Jeśli z listy elementów nie można usunąć ani jednego pola, wówczas lista jest grupą danych, które należy umieścić w osobnej klasie (wybór klasy).

Rozwiązania psujące koncepcję OOP

Ten typ „zapachu” pojawia się, gdy programista narusza projekt OOP. Dzieje się tak, jeśli nie do końca rozumie możliwości tego paradygmatu, wykorzystuje je niekompletnie lub nieprawidłowo.

Odmowa dziedziczenia

Jeśli podklasa wykorzystuje minimalną część funkcji klasy nadrzędnej, pachnie to niepoprawną hierarchią. Zwykle w tym przypadku niepotrzebne metody po prostu nie są zastępowane lub zgłaszane są wyjątki. Jeśli klasa jest dziedziczona od innej, oznacza to prawie całkowite wykorzystanie jej funkcjonalności. Przykład prawidłowej hierarchii: Jak działa refaktoryzacja w Javie - 2 Przykład nieprawidłowej hierarchii: Jak działa refaktoryzacja w Javie - 3

instrukcja switch

Co może być nie tak z operatorem switch? Źle jest, gdy jego projekt jest bardzo skomplikowany. Dotyczy to również wielu zagnieżdżonych bloków if.

Alternatywne klasy z różnymi interfejsami

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

Pole tymczasowe

Jeśli klasa zawiera pole tymczasowe, którego obiekt potrzebuje tylko sporadycznie, gdy jest wypełniony wartościami, a przez resztę czasu jest puste lub, nie daj Boże, null, to kod „pachnie” i taki projekt jest wątpliwy decyzja.

Zapachy utrudniające modyfikację

Te „zapachy” są poważniejsze. Reszta głównie utrudnia zrozumienie kodu, a te nie pozwalają na jego modyfikację. Podczas wprowadzania jakichkolwiek funkcji połowa programistów odejdzie, a połowa oszaleje.

Hierarchie dziedziczenia równoległego

Kiedy tworzysz podklasę klasy, musisz utworzyć kolejną podklasę innej klasy.

Jednolity rozkład zależności

Wykonując jakiekolwiek modyfikacje, trzeba poszukać wszystkich zależności (zastosowań) tej klasy i dokonać wielu drobnych zmian. Jedna zmiana - zmiany w wielu zajęciach.

Złożone drzewo modyfikacji

Ten zapach jest przeciwieństwem poprzedniego: zmiany dotyczą dużej liczby metod tej samej klasy. Z reguły zależność w takim kodzie jest kaskadowa: po zmianie jednej metody trzeba coś naprawić w innej, potem w trzeciej i tak dalej. Jedna klasa – wiele zmian.

„Zapach śmieci”

Dość nieprzyjemna kategoria zapachów powodująca 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 zawiera wiele objaśniających komentarzy w prawie każdym wierszu. Wiąże się to zwykle ze złożonym algorytmem, dlatego lepiej podzielić kod na kilka mniejszych metod i nadać im znaczące nazwy.

Powielanie kodu

Różne klasy lub metody korzystają z tych samych bloków kodu.

Leniwa klasa

Klasa przyjmuje bardzo mało funkcjonalności, choć sporo z niej zostało zaplanowanych.

Niewykorzystany kod

Klasa, metoda lub zmienna nie są używane w kodzie i stanowią „nośny ciężar”.

Nadmierne sprzężenie

Ta kategoria zapachów charakteryzuje się dużą liczbą niepotrzebnych połączeń w kodzie.

Metody stron trzecich

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

Niewłaściwa intymność

Klasa korzysta z pól usług i metod innej klasy.

Długie rozmowy klasowe

Jedna klasa wywołuje drugą, która żąda danych od trzeciej, drugiej od czwartej i tak dalej. Tak długi łańcuch wywołań oznacza wysoki poziom zależności od aktualnej struktury klasowej.

Dealer klasowy

Klasa jest potrzebna tylko do przekazania zadania innej klasie. Może warto go usunąć?

Techniki refaktoryzacji

Poniżej omówimy techniki wstępnej refaktoryzacji, które pomogą wyeliminować opisane zapachy kodu.

Wybór klasy

Klasa pełni zbyt wiele funkcji, niektóre z nich należy przenieść do innej klasy. Na przykład istnieje klasa Human, która zawiera również adres zamieszkania i metoda podająca 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();
   }
}
Dobrym pomysłem byłoby umieszczenie informacji adresowych i 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();
   }
}

Wybór metody

Jeśli jakąkolwiek funkcjonalność można zgrupować w metodzie, należy ją umieścić w osobnej metodzie. Na przykład metoda obliczająca 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");
    }
}
Przenieśmy obliczenia wszystkich trzech możliwych opcji na osobne metody:
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 bardziej przejrzysty.

Przeniesienie całego obiektu

Podczas wywoływania metody z parametrami można czasami zobaczyć taki kod:
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;
}
W metodzie employeeMethodprzeznaczone są aż 2 linie do uzyskania wartości i przechowywania ich w zmiennych prymitywnych. Czasami takie projekty zajmują do 10 linii. Dużo łatwiej jest przekazać sam obiekt do metody, skąd można wydobyć potrzebne dane:
public void employeeMethod(Employee employee) {
    // Некоторые действия
    double monthlySalary = getMonthlySalary(employee);
    // Продолжение обработки
}

public double getMonthlySalary(Employee employee) {
    return (employee.getYearlySalary() + employee.getAwards())/12;
}
Prosto, krótko i zwięźle.

Logiczne grupowanie pól i umieszczanie ich w osobnej klasie

Pomimo tego, że powyższe przykłady są bardzo proste i patrząc na nie wiele osób może zadać pytanie „Kto to właściwie robi?”, wielu programistów z powodu nieuwagi, niechęci do refaktoryzacji kodu lub po prostu „wystarczy” sprawia, że podobne błędy strukturalne.

Dlaczego refaktoryzacja jest skuteczna

Efektem dobrej refaktoryzacji jest program, którego kod jest łatwy do odczytania, modyfikacje w logice programu nie stają się zagrożeniem, a wprowadzenie nowych funkcji nie zamienia się w piekło analizowania kodu, ale przyjemną czynność na kilka dni . Nie należy stosować refaktoryzacji, jeśli łatwiej byłoby napisać program od nowa. Na przykład zespół szacuje, że koszty pracy związane z parsowaniem, analizowaniem i refaktoryzacją kodu są wyższe niż w przypadku wdrożenia tej samej funkcjonalności od zera. Lub kod wymagający refaktoryzacji zawiera wiele błędów trudnych do debugowania. Umiejętność ulepszania struktury kodu jest obowiązkowa w pracy programisty. Cóż, programowania w Javie lepiej uczyć się na JavaRush – kursie online kładącym nacisk na praktykę. Ponad 1200 zadań z natychmiastową weryfikacją, około 20 miniprojektów, zadania z gier - to wszystko pomoże Ci poczuć się pewnie w kodowaniu. Najlepszy czas, żeby zacząć, jest teraz :) Jak działa refaktoryzacja w Javie - 4

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

Najbardziej znaną książką na temat refaktoryzacji jest „Refactoring. Udoskonalanie projektu istniejącego kodu” Martina Fowlera. Istnieje również ciekawa publikacja na temat refaktoryzacji, napisana na podstawie poprzedniej książki „Refactoring with Patterns” Joshuy Kiriewskiego. Skoro mowa o szablonach. Podczas refaktoryzacji zawsze bardzo przydatna jest znajomość podstawowych wzorców projektowania aplikacji. Pomogą Ci w tym te wspaniałe książki:
  1. „Design Patterns” – autorstwa Erica Freemana, Elizabeth Freeman, Kathy Sierra, Berta Batesa z serii Head First;
  2. „Czytelny kod, czyli programowanie jako sztuka” – Dustin Boswell, Trevor Faucher.
  3. „Idealny kod” Steve’a McConnella, który przedstawia zasady pięknego i eleganckiego kodu.
Cóż, kilka artykułów na temat refaktoryzacji:
  1. Piekło zadania: zacznijmy refaktoryzację starszego kodu ;
  2. Refaktoryzacja ;
  3. Refaktoryzacja dla każdego .
    Komentarze
    TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
    GO TO FULL VERSION