CodeGym /Blog Java /Aleatoriu /Cum funcționează refactorizarea în Java
John Squirrels
Nivel
San Francisco

Cum funcționează refactorizarea în Java

Publicat în grup
Pe măsură ce înveți să programezi, petreci mult timp scriind cod. Majoritatea dezvoltatorilor începători cred că aceasta este ceea ce vor face în viitor. Acest lucru este parțial adevărat, dar munca unui programator include și întreținerea și refactorizarea codului. Astăzi vom vorbi despre refactorizare. Cum funcționează refactorizarea în Java - 1

Refactorizarea pe CodeGym

Refactorizarea este acoperită de două ori în cursul CodeGym: Sarcina mare oferă o oportunitate de a vă familiariza cu refactorizarea reală prin practică, iar lecția despre refactorizare din IDEA vă ajută să vă scufundați în instrumente automate care vă vor face viața incredibil de ușoară.

Ce este refactorizarea?

Schimbă structura codului fără a-i schimba funcționalitatea. De exemplu, să presupunem că avem o metodă care compară 2 numere și returnează adevărat dacă primul este mai mare și fals în caz contrar:

    public boolean max(int a, int b) {
        if(a > b) {
            return true;
        } else if (a == b) {
            return false;
        } else {
            return false;
        }
    }
Acesta este un cod destul de greu de manevrat. Chiar și începătorii ar scrie rar așa ceva, dar există o șansă. De ce să folosiți un if-elsebloc dacă puteți scrie metoda cu 6 linii mai concis?

 public boolean max(int a, int b) {
      return a > b;
 }
Acum avem o metodă simplă și elegantă care efectuează aceeași operație ca exemplul de mai sus. Așa funcționează refactorizarea: schimbi structura codului fără a-i afecta esența. Există multe metode și tehnici de refactorizare pe care le vom arunca o privire mai atentă.

De ce ai nevoie de refactorizare?

Există mai multe motive. De exemplu, pentru a obține simplitate și concizie în cod. Susținătorii acestei teorii consideră că codul ar trebui să fie cât mai concis posibil, chiar dacă sunt necesare câteva zeci de rânduri de comentarii pentru a-l înțelege. Alți dezvoltatori sunt convinși că codul ar trebui refactorizat pentru a-l face ușor de înțeles cu un număr minim de comentarii. Fiecare echipă își adoptă propria poziție, dar rețineți că refactorizarea nu înseamnă reducere . Scopul său principal este de a îmbunătăți structura codului. În acest scop general pot fi incluse mai multe sarcini:
  1. Refactorizarea îmbunătățește înțelegerea codului scris de alți dezvoltatori.
  2. Ajută la găsirea și remedierea erorilor.
  3. Poate accelera viteza de dezvoltare a software-ului.
  4. În general, îmbunătățește designul software-ului.
Dacă refactorizarea nu este efectuată o perioadă lungă de timp, dezvoltarea poate întâmpina dificultăți, inclusiv oprirea completă a lucrării.

„Cod miroase”

Când codul necesită refactorizare, se spune că are un „miros”. Desigur, nu la propriu, dar un astfel de cod chiar nu pare foarte atrăgător. Mai jos vom explora tehnicile de bază de refactorizare pentru etapa inițială.

Clase și metode nerezonabil de mari

Clasele și metodele pot fi greoaie, imposibil de lucrat eficient tocmai din cauza dimensiunii lor uriașe.

Clasa mare

O astfel de clasă are un număr mare de linii de cod și multe metode diferite. De obicei, este mai ușor pentru un dezvoltator să adauge o caracteristică la o clasă existentă, mai degrabă decât să creeze una nouă, motiv pentru care clasa crește. De regulă, prea multă funcționalitate este înghesuită într-o astfel de clasă. În acest caz, ajută la mutarea unei părți a funcționalității într-o clasă separată. Vom vorbi despre asta mai detaliat în secțiunea despre tehnicile de refactorizare.

Metoda lungă

Acest „miros” apare atunci când un dezvoltator adaugă o nouă funcționalitate unei metode: „De ce ar trebui să pun o verificare a parametrilor într-o metodă separată dacă pot scrie codul aici?”, „De ce am nevoie de o metodă de căutare separată pentru a găsi maximul element într-o matrice? Să-l păstrăm aici. Codul va fi mai clar în acest fel", și alte astfel de concepții greșite.

Există două reguli pentru refactorizarea unei metode lungi:

  1. Dacă doriți să adăugați un comentariu atunci când scrieți o metodă, ar trebui să puneți funcționalitatea într-o metodă separată.
  2. Dacă o metodă necesită mai mult de 10-15 linii de cod, ar trebui să identificați sarcinile și subsarcinile pe care le efectuează și să încercați să puneți subsarcinile într-o metodă separată.

Există câteva moduri de a elimina o metodă lungă:

  • Mutați o parte din funcționalitatea metodei într-o metodă separată
  • Dacă variabilele locale vă împiedică să mutați o parte a funcționalității, puteți muta întregul obiect într-o altă metodă.

Folosind o mulțime de tipuri de date primitive

Această problemă apare de obicei atunci când numărul de câmpuri dintr-o clasă crește în timp. De exemplu, dacă stocați totul (monedă, dată, numere de telefon etc.) în tipuri sau constante primitive în loc de obiecte mici. În acest caz, o bună practică ar fi să mutați o grupare logică de câmpuri într-o clasă separată (clasa de extragere). De asemenea, puteți adăuga metode la clasă pentru a procesa datele.

Prea mulți parametri

Aceasta este o greșeală destul de comună, mai ales în combinație cu o metodă lungă. De obicei, apare dacă o metodă are prea multă funcționalitate sau dacă o metodă implementează mai mulți algoritmi. Listele lungi de parametri sunt foarte greu de înțeles, iar utilizarea metodelor cu astfel de liste este incomod. Drept urmare, este mai bine să treceți un obiect întreg. Dacă un obiect nu are suficiente date, ar trebui să utilizați un obiect mai general sau să împărțiți funcționalitatea metodei astfel încât fiecare metodă să prelucreze date legate logic.

Grupuri de date

Grupuri de date legate logic apar adesea în cod. De exemplu, parametrii de conectare la baza de date (URL, nume de utilizator, parolă, nume de schemă etc.). Dacă niciun câmp nu poate fi eliminat dintr-o listă de câmpuri, atunci aceste câmpuri ar trebui mutate într-o clasă separată (clasa de extragere).

Soluții care încalcă principiile POO

Aceste „mirosuri” apar atunci când un dezvoltator încalcă designul OOP adecvat. Acest lucru se întâmplă atunci când el sau ea nu înțelege pe deplin capabilitățile OOP și nu le folosește pe deplin sau corect.

Neutilizarea moștenirii

Dacă o subclasă folosește doar un mic subset de funcții ale clasei părinte, atunci miroase a ierarhie greșită. Când se întâmplă acest lucru, de obicei metodele superflue pur și simplu nu sunt suprascrise sau aruncă excepții. O clasă moștenind alta implică faptul că clasa copil folosește aproape toată funcționalitatea clasei părinte. Exemplu de ierarhie corectă: Cum funcționează refactorizarea în Java - 2Exemplu de ierarhie incorectă: Cum funcționează refactorizarea în Java - 3

Declarație Switch

Ce ar putea fi în neregulă cu o switchdeclarație? Este rău când devine foarte complex. O problemă înrudită este un număr mare de ifinstrucțiuni imbricate.

Clase alternative cu interfețe diferite

Mai multe clase fac același lucru, dar metodele lor au nume diferite.

Câmp temporar

Dacă o clasă are un câmp temporar de care un obiect are nevoie doar ocazional când valoarea lui este setată și este goală sau, Doamne ferește, nullîn restul timpului, atunci codul miroase. Aceasta este o decizie de proiectare discutabilă.

Mirosuri care îngreunează modificarea

Aceste mirosuri sunt mai grave. Alte mirosuri fac mai dificilă înțelegerea codului, dar acestea vă împiedică să îl modificați. Când încercați să introduceți funcții noi, jumătate dintre dezvoltatori renunță și jumătate înnebunesc.

Ierarhii de moștenire paralele

Această problemă se manifestă atunci când subclasarea unei clase necesită să creați o altă subclasă pentru o clasă diferită.

Dependențe distribuite uniform

Orice modificare necesită să căutați toate utilizările unei clase (dependențe) și să faceți o mulțime de mici modificări. O schimbare - editări în multe clase.

Arborele complex al modificărilor

Acest miros este opusul celui precedent: modificările afectează un număr mare de metode dintr-o clasă. De regulă, un astfel de cod are dependență în cascadă: schimbarea unei metode necesită să reparați ceva în alta, apoi în a treia și așa mai departe. O singură clasă - multe schimbări.

„Miroase a gunoiului”

O categorie destul de neplăcută de mirosuri care provoacă dureri de cap. Cod inutil, inutil, vechi. Din fericire, IDE-urile și linterurile moderne au învățat să avertizeze despre astfel de mirosuri.

Un număr mare de comentarii într-o metodă

O metodă are o mulțime de comentarii explicative pe aproape fiecare rând. Acest lucru se datorează de obicei unui algoritm complex, deci este mai bine să împărțiți codul în mai multe metode mai mici și să le dați nume explicative.

Cod duplicat

Clase sau metode diferite folosesc aceleași blocuri de cod.

Clasă leneșă

O clasă are foarte puține funcționalități, deși a fost planificată să fie mare.

Cod neutilizat

O clasă, o metodă sau o variabilă nu este utilizată în cod și are o greutate moartă.

Conectivitate excesivă

Această categorie de mirosuri se caracterizează printr-un număr mare de relații nejustificate în cod.

Metode externe

O metodă folosește datele de la un alt obiect mult mai des decât propriile sale date.

Intimitate nepotrivită

O clasă depinde de detaliile de implementare ale altei clase.

Apeluri la cursuri lungi

O clasă o cheamă pe alta, care solicită date de la o a treia, care primește date de la o a patra și așa mai departe. Un lanț atât de lung de apeluri înseamnă o dependență ridicată de structura actuală a clasei.

Clasa de sarcină de distribuitor

O clasă este necesară doar pentru trimiterea unei sarcini către o altă clasă. Poate ar trebui eliminat?

Tehnici de refactorizare

Mai jos vom discuta tehnicile de bază de refactorizare care pot ajuta la eliminarea mirosurilor de cod descrise.

Extrageți o clasă

O clasă îndeplinește prea multe funcții. Unii dintre ei trebuie mutați într-o altă clasă. De exemplu, să presupunem că avem o Humanclasă care stochează și o adresă de domiciliu și are o metodă care returnează adresa completă:

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();
    }
 }
Este o bună practică să puneți informațiile despre adresă și metoda asociată (comportamentul de prelucrare a datelor) într-o clasă separată:

 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();
    }
 }

Extrage o metodă

Dacă o metodă are o anumită funcționalitate care poate fi izolată, ar trebui să o plasați într-o metodă separată. De exemplu, o metodă care calculează rădăcinile unei ecuații pătratice:

    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");
        }
    }
Calculăm fiecare dintre cele trei opțiuni posibile în metode separate:

    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");
    }
Codul fiecărei metode a devenit mult mai scurt și mai ușor de înțeles.

Trecând pe lângă un obiect întreg

Când o metodă este apelată cu parametri, este posibil să vedeți uneori cod ca acesta:

 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;
 }
Are employeeMethod2 linii întregi dedicate recepționării valorilor și stocării lor în variabile primitive. Uneori, astfel de construcții pot dura până la 10 linii. Este mult mai ușor să treceți obiectul în sine și să îl utilizați pentru a extrage datele necesare:

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

Simplu, scurt și concis.

Gruparea logică a câmpurilor și mutarea lor într-o formă separată, classDespitefaptul că exemplele de mai sus sunt foarte simple, iar când le priviți, mulți dintre voi s-ar putea întreba: „Cine face asta?”, mulți dezvoltatori fac astfel de erori structurale din cauza neglijenței, refuzul de a refactoriza codul sau pur și simplu o atitudine de „asta e suficient de bun”.

De ce este eficientă refactorizarea

Ca urmare a unei refactorizări bune, un program are cod ușor de citit, perspectiva de a-și modifica logica nu este înfricoșătoare, iar introducerea de noi funcții nu devine un iad de analiză a codului, ci este o experiență plăcută pentru câteva zile. . Nu ar trebui să refactorizezi dacă ar fi mai ușor să scrii un program de la zero. De exemplu, să presupunem că echipa dvs. estimează că munca necesară pentru înțelegerea, analiza și refactorizarea codului va fi mai mare decât implementarea aceleiași funcționalități de la zero. Sau dacă codul care urmează să fie refactorizat are o mulțime de probleme care sunt greu de depanat. A ști cum să îmbunătățești structura codului este esențială în munca unui programator. Și învățarea programării în Java se face cel mai bine pe CodeGym, cursul online care pune accent pe practică. Peste 1200 de sarcini cu verificare instantanee, aproximativ 20 de mini-proiecte, sarcini de joc — toate acestea vă vor ajuta să vă simțiți încrezători în codificare. Cel mai bun moment pentru a începe este acum :)

Resurse pentru a vă scufunda în continuare în refactorizare

Cea mai faimoasă carte despre refactorizare este „Refactoring. Improving the Design of Existing Code” de Martin Fowler. Există și o publicație interesantă despre refactorizare, bazată pe o carte anterioară: „Refactoring Using Patterns” de Joshua Kerievsky. Apropo de modele... Când refactorizați, este întotdeauna foarte util să cunoașteți modelele de design de bază. Aceste cărți excelente vă vor ajuta în acest sens: Vorbind despre modele... Când refactorizați, este întotdeauna foarte util să cunoașteți modelele de design de bază. Aceste cărți excelente vă vor ajuta în acest sens:
  1. „Design Patterns” de Eric Freeman, Elizabeth Robson, Kathy Sierra și Bert Bates, din seria Head First
  2. „Arta codului citibil” de Dustin Boswell și Trevor Foucher
  3. „Code Complete” de Steve McConnell, care stabilește principiile unui cod frumos și elegant.
Comentarii
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION