JavaRush /Java Blog /Random-IT /Come funziona il refactoring in Java

Come funziona il refactoring in Java

Pubblicato nel gruppo Random-IT
Quando si impara a programmare, si dedica molto tempo alla scrittura del codice. La maggior parte degli sviluppatori principianti ritiene che questa sia la loro attività futura. Ciò è in parte vero, ma i compiti di un programmatore includono anche il mantenimento e il refactoring del codice. Oggi parleremo di refactoring. Come funziona il refactoring in Java - 1

Refactoring nel corso JavaRush

Il corso JavaRush tratta l'argomento del refactoring due volte: Grazie al grande compito, c'è l'opportunità di conoscere nella pratica il vero refactoring e una lezione sul refactoring in IDEA ti aiuterà a comprendere gli strumenti automatici che rendono la vita incredibilmente più semplice.

Cos'è il refactoring?

Si tratta di un cambiamento nella struttura del codice senza modificarne la funzionalità. Ad esempio, esiste un metodo che confronta 2 numeri e restituisce vero se il primo è maggiore e falso altrimenti:
public boolean max(int a, int b) {
    if(a > b) {
        return true;
    } else if(a == b) {
        return false;
    } else {
        return false;
    }
}
Il risultato è stato un codice molto complicato. Anche i principianti raramente scrivono qualcosa del genere, ma il rischio esiste. Sembrerebbe, perché c'è un blocco qui if-elsese puoi scrivere un metodo più breve di 6 righe:
public boolean max(int a, int b) {
     return a>b;
}
Ora questo metodo sembra semplice ed elegante, sebbene faccia la stessa cosa dell'esempio sopra. Il refactoring funziona così: cambia la struttura del codice senza intaccarne l'essenza. Esistono molti metodi e tecniche di refactoring, che considereremo in modo più dettagliato.

Perché è necessario il refactoring?

Ci sono diversi motivi. Ad esempio, la ricerca della semplicità e della concisione del codice. I sostenitori di questa teoria credono che il codice dovrebbe essere il più conciso possibile, anche se richiede decine di righe di commento per capirlo. Altri sviluppatori ritengono che il codice debba essere rifattorizzato in modo che sia comprensibile con un numero minimo di commenti. Ogni squadra sceglie la propria posizione, ma dobbiamo ricordare che il refactoring non è una riduzione . Il suo obiettivo principale è migliorare la struttura del codice. Diversi obiettivi possono essere inclusi in questo obiettivo globale:
  1. Il refactoring migliora la comprensione del codice scritto da un altro sviluppatore;
  2. Aiuta a trovare e correggere gli errori;
  3. Consente di aumentare la velocità di sviluppo del software;
  4. Nel complesso migliora la composizione del software.
Se il refactoring non viene effettuato per un lungo periodo, possono sorgere difficoltà di sviluppo, fino al completo arresto del lavoro.

“Odore di codice”

Quando il codice richiede il refactoring dicono che "puzza". Naturalmente, non letteralmente, ma il codice in realtà non sembra molto carino. Di seguito considereremo le principali tecniche di refactoring per la fase iniziale.

Elementi inutilmente grandi

Esistono classi e metodi ingombranti con cui è impossibile lavorare in modo efficace proprio a causa delle loro enormi dimensioni.

Grande classe

Una classe di questo tipo ha un numero enorme di righe di codice e molti metodi diversi. Di solito è più semplice per uno sviluppatore aggiungere una funzionalità a una classe esistente piuttosto che crearne una nuova, motivo per cui cresce. Di norma, la funzionalità di questa classe è sovraccarica. In questo caso, è utile separare parte della funzionalità in una classe separata. Ne parleremo più in dettaglio nella sezione sulle tecniche di refactoring.

Grande Metodo

Questo "odore" si verifica quando uno sviluppatore aggiunge nuove funzionalità a un metodo. “Perché dovrei inserire il controllo dei parametri in un metodo separato se posso scriverlo qui?”, “Perché è necessario separare il metodo per trovare l’elemento massimo nell’array, lasciamolo qui. In questo modo il codice è più chiaro” e altri malintesi. Esistono due regole per il refactoring di un metodo di grandi dimensioni:
  1. Se, quando si scrive un metodo, si desidera aggiungere un commento al codice, è necessario separare questa funzionalità in un metodo separato;
  2. Se un metodo richiede più di 10-15 righe di codice, dovresti identificare le attività e le sottoattività che esegue e provare a separare le sottoattività in un metodo separato.
Diversi modi per eliminare un metodo di grandi dimensioni:
  • Separare parte della funzionalità di un metodo in un metodo separato;
  • Se le variabili locali non ti consentono di estrarre parte della funzionalità, puoi passare l'intero oggetto ad un altro metodo.

Utilizzo di molti tipi di dati primitivi

In genere, questo problema si verifica quando il numero di campi in cui archiviare i dati in una classe aumenta nel tempo. Ad esempio, se utilizzi tipi primitivi invece di piccoli oggetti per archiviare dati (valuta, data, numeri di telefono, ecc.) o costanti per codificare qualsiasi informazione. Una buona pratica in questo caso sarebbe raggruppare logicamente i campi e inserirli in una classe separata (selezionando una classe). Puoi anche includere metodi per elaborare questi dati nella classe.

Lungo elenco di opzioni

Un errore abbastanza comune, soprattutto in combinazione con un metodo di grandi dimensioni. Di solito si verifica se la funzionalità del metodo è sovraccarica o se il metodo combina diversi algoritmi. Lunghi elenchi di parametri sono molto difficili da comprendere e tali metodi sono scomodi da utilizzare. Pertanto, è meglio trasferire l'intero oggetto. Se l'oggetto non dispone di dati sufficienti, vale la pena utilizzare un oggetto più generale o suddividere la funzionalità del metodo in modo che elabori i dati logicamente correlati.

Gruppi di dati

Gruppi di dati logicamente correlati compaiono spesso nel codice. Ad esempio, i parametri di connessione al database (URL, nome utente, password, nome schema, ecc.). Se non è possibile rimuovere un singolo campo dall'elenco degli elementi, l'elenco è un gruppo di dati che deve essere inserito in una classe separata (selezione della classe).

Soluzioni che rovinano il concetto di OOP

Questo tipo di "odore" si verifica quando lo sviluppatore viola il design OOP. Ciò accade se non comprende appieno le capacità di questo paradigma, le utilizza in modo incompleto o errato.

Rifiuto dell'eredità

Se una sottoclasse utilizza una minima parte delle funzioni della classe genitore, puzza di gerarchia errata. In genere, in questo caso, i metodi non necessari semplicemente non vengono sovrascritti o vengono generate eccezioni. Se una classe viene ereditata da un'altra, ciò implica l'utilizzo quasi completo delle sue funzionalità. Esempio di gerarchia corretta: Come funziona il refactoring in Java - 2 Esempio di gerarchia errata: Come funziona il refactoring in Java - 3

dichiarazione di cambio

Cosa potrebbe esserci di sbagliato in un operatore switch? È brutto quando il suo design è molto complesso. Ciò include anche molti blocchi nidificatiif .

Classi alternative con interfacce diverse

Diverse classi in realtà fanno la stessa cosa, ma i loro metodi hanno nomi diversi.

Campo temporaneo

Se la classe contiene un campo temporaneo di cui l'oggetto ha bisogno solo occasionalmente, quando è pieno di valori, e il resto del tempo è vuoto o, Dio non voglia,null , allora il codice "puzza" e un tale disegno è dubbio decisione.

Odori che rendono difficile la modifica

Questi “odori” sono più gravi. Il resto pregiudica soprattutto la comprensione del codice, mentre questi non permettono di modificarlo. Quando si introducono funzionalità, metà degli sviluppatori se ne andrà e l'altra metà impazzirà.

Gerarchie di eredità parallele

Quando crei una sottoclasse di una classe, devi creare un'altra sottoclasse di un'altra classe.

Distribuzione uniforme delle dipendenze

Quando si eseguono modifiche, è necessario cercare tutte le dipendenze (usi) di questa classe e apportare molte piccole modifiche. Un cambiamento: modifiche in molte classi.

Albero di modifica complesso

Questo odore è l'opposto del precedente: le modifiche interessano un gran numero di metodi della stessa classe. Di norma, la dipendenza in tale codice è a cascata: dopo aver cambiato un metodo, è necessario correggere qualcosa in un altro, quindi in un terzo e così via. Una lezione, tanti cambiamenti.

“Odore di spazzatura”

Una categoria di odori piuttosto sgradevole che provoca mal di testa. Codice vecchio, inutile, inutile. Fortunatamente, gli IDE e i linter moderni hanno imparato a mettere in guardia da tali odori.

Un gran numero di commenti nel metodo

Il metodo ha molti commenti esplicativi su quasi ogni riga. Questo di solito è associato a un algoritmo complesso, quindi è meglio dividere il codice in diversi metodi più piccoli e dare loro nomi significativi.

Duplicazione del codice

Classi o metodi diversi utilizzano gli stessi blocchi di codice.

Classe pigra

La classe assume pochissime funzionalità, sebbene molte siano state pianificate.

Codice non utilizzato

Una classe, un metodo o una variabile non vengono utilizzati nel codice e rappresentano un "peso morto".

Accoppiamento eccessivo

Questa categoria di odori è caratterizzata da un gran numero di connessioni non necessarie nel codice.

Metodi di terze parti

Un metodo utilizza i dati di un altro oggetto molto più spesso dei propri dati.

Intimità inappropriata

Una classe utilizza campi di servizio e metodi di un'altra classe.

Chiamate in classe lunghe

Una classe ne chiama un'altra, che richiede dati alla terza, quella alla quarta e così via. Una catena di chiamate così lunga implica un elevato livello di dipendenza dall’attuale struttura di classe.

Commerciante di compiti di classe

Una classe è necessaria solo per passare un'attività a un'altra classe. Forse andrebbe rimosso?

Tecniche di refactoring

Di seguito parleremo delle tecniche di refactoring iniziale che aiuteranno ad eliminare gli odori di codice descritti.

Selezione della classe

La classe esegue troppe funzioni; alcune di esse devono essere spostate in un'altra classe. Ad esempio, esiste una classe Humanche contiene anche un indirizzo di residenza e un metodo che fornisce l'indirizzo completo:
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();
   }
}
Sarebbe una buona idea inserire le informazioni sull'indirizzo e il metodo (comportamento di elaborazione dei dati) in una classe separata:
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();
   }
}

Selezione del metodo

Se è possibile raggruppare una funzionalità in un metodo, è necessario inserirla in un metodo separato. Ad esempio, un metodo che calcola le radici di un'equazione quadratica:
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");
    }
}
Spostiamo il calcolo di tutte e tre le possibili opzioni in metodi separati:
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");
}
Il codice per ciascun metodo è diventato molto più breve e chiaro.

Trasferimento dell'intero oggetto

Quando chiami un metodo con parametri, a volte puoi vedere un codice come questo:
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;
}
Nel metodo employeeMethodvengono allocate fino a 2 righe per ottenere valori e memorizzarli in variabili primitive. A volte tali progetti richiedono fino a 10 righe. È molto più semplice passare l'oggetto stesso al metodo, da dove è possibile estrarre i dati necessari:
public void employeeMethod(Employee employee) {
    // Некоторые действия
    double monthlySalary = getMonthlySalary(employee);
    // Продолжение обработки
}

public double getMonthlySalary(Employee employee) {
    return (employee.getYearlySalary() + employee.getAwards())/12;
}
Semplice, breve e conciso.

Raggruppamento logico di campi e inserimento in una classe separata

Nonostante il fatto che gli esempi sopra riportati siano molto semplici e guardandoli molti potrebbero porre la domanda "Chi lo fa realmente?", molti sviluppatori, a causa di disattenzione, riluttanza a rifattorizzare il codice o semplicemente "Andrà bene", fanno errori strutturali simili.

Perché il refactoring è efficace

Il risultato di un buon refactoring è un programma il cui codice è facile da leggere, le modifiche alla logica del programma non diventano una minaccia e l'introduzione di nuove funzionalità non si trasforma in un inferno di analisi del codice, ma in un'attività piacevole per un paio di giorni . Il refactoring non dovrebbe essere utilizzato se sarebbe più semplice riscrivere il programma da zero. Ad esempio, il team stima che i costi di manodopera per l'analisi, l'analisi e il refactoring del codice siano più elevati rispetto all'implementazione della stessa funzionalità da zero. Oppure il codice che deve essere sottoposto a refactoring contiene molti errori di cui è difficile eseguire il debug. Saper migliorare la struttura del codice è obbligatorio nel lavoro di un programmatore. Bene, è meglio imparare la programmazione Java su JavaRush, un corso online con un'enfasi sulla pratica. Oltre 1200 attività con verifica istantanea, circa 20 mini-progetti, attività di gioco: tutto ciò ti aiuterà a sentirti sicuro nella programmazione. Il momento migliore per iniziare è adesso :) Come funziona il refactoring in Java - 4

Risorse per approfondire ulteriormente il refactoring

Il libro più famoso sul refactoring è “Refactoring. Migliorare la progettazione del codice esistente” di Martin Fowler. Esiste anche un'interessante pubblicazione sul refactoring, scritta sulla base di un libro precedente: "Refactoring with Patterns" di Joshua Kiriewski. A proposito di modelli. Durante il refactoring è sempre molto utile conoscere i design pattern di base dell'applicazione. Questi fantastici libri ti aiuteranno in questo:
  1. “Design Patterns” - di Eric Freeman, Elizabeth Freeman, Kathy Sierra, Bert Bates della serie Head First;
  2. "Codice leggibile o programmazione come arte" - Dustin Boswell, Trevor Faucher.
  3. “Perfect Code” di Steve McConnell, che delinea i principi di un codice bello ed elegante.
Bene, alcuni articoli sul refactoring:
  1. Che compito: iniziamo a refactoring del codice legacy ;
  2. Refactoring ;
  3. Refactoring per tutti .
    Commenti
    TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
    GO TO FULL VERSION