CodeGym /Java Blog /Random-IT /Come funziona il refactoring in Java
John Squirrels
Livello 41
San Francisco

Come funziona il refactoring in Java

Pubblicato nel gruppo Random-IT
Mentre impari a programmare, passi molto tempo a scrivere codice. La maggior parte degli sviluppatori principianti crede che questo sia ciò che faranno in futuro. Questo è in parte vero, ma il lavoro di un programmatore include anche la manutenzione e il refactoring del codice. Oggi parleremo di refactoring. Come funziona il refactoring in Java - 1

Refactoring su CodeGym

Il refactoring è trattato due volte nel corso CodeGym: Il grande compito offre l'opportunità di familiarizzare con il refactoring reale attraverso la pratica e la lezione sul refactoring in IDEA ti aiuta a immergerti in strumenti automatizzati che renderanno la tua vita incredibilmente più semplice.

Cos'è il refactoring?

Sta cambiando la struttura del codice senza cambiarne la funzionalità. Ad esempio, supponiamo di avere 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;
        }
    }
Questo è un codice piuttosto ingombrante. Anche i principianti raramente scriverebbero qualcosa del genere, ma c'è una possibilità. Perché usare un if-elseblocco se puoi scrivere il metodo a 6 righe in modo più conciso?

 public boolean max(int a, int b) {
      return a > b;
 }
Ora abbiamo un metodo semplice ed elegante che esegue la stessa operazione dell'esempio precedente. Ecco come funziona il refactoring: cambi la struttura del codice senza intaccarne l'essenza. Esistono molti metodi e tecniche di refactoring che esamineremo più da vicino.

Perché hai bisogno del refactoring?

Ci sono diversi motivi. Ad esempio, per ottenere semplicità e brevità nel codice. I sostenitori di questa teoria ritengono che il codice dovrebbe essere il più conciso possibile, anche se per comprenderlo sono necessarie diverse dozzine di righe di commenti. Altri sviluppatori sono convinti che il codice debba essere sottoposto a refactoring per renderlo comprensibile con il numero minimo di commenti. Ogni squadra adotta la propria posizione, ma ricorda che refactoring non significa riduzione . Il suo scopo principale è quello di migliorare la struttura del codice. Diverse attività possono essere incluse in questo scopo generale:
  1. Il refactoring migliora la comprensione del codice scritto da altri sviluppatori.
  2. Aiuta a trovare e correggere i bug.
  3. Può accelerare la velocità di sviluppo del software.
  4. Nel complesso, migliora la progettazione del software.
Se il refactoring non viene eseguito per molto tempo, lo sviluppo potrebbe incontrare difficoltà, inclusa l'interruzione completa del lavoro.

"Il codice puzza"

Quando il codice richiede il refactoring, si dice che abbia un "odore". Certo, non letteralmente, ma un tale codice non sembra davvero molto allettante. Di seguito esploreremo le tecniche di refactoring di base per la fase iniziale.

Classi e metodi irragionevolmente grandi

Classi e metodi possono essere ingombranti, impossibili da utilizzare in modo efficace proprio a causa delle loro enormi dimensioni.

Classe numerosa

Tale classe ha un numero enorme di righe di codice e molti metodi diversi. Di solito è più facile per uno sviluppatore aggiungere una funzionalità a una classe esistente piuttosto che crearne una nuova, motivo per cui la classe cresce. Di norma, troppa funzionalità è stipata in una classe del genere. In questo caso, è utile spostare parte della funzionalità in una classe separata. Ne parleremo più dettagliatamente nella sezione sulle tecniche di refactoring.

Metodo lungo

Questo "odore" sorge quando uno sviluppatore aggiunge nuove funzionalità a un metodo: "Perché dovrei inserire un controllo dei parametri in un metodo separato se posso scrivere il codice qui?", "Perché ho bisogno di un metodo di ricerca separato per trovare il massimo elemento in un array? Teniamolo qui. Il codice sarà più chiaro in questo modo", e altre idee sbagliate simili.

Esistono due regole per il refactoring di un metodo lungo:

  1. Se hai voglia di aggiungere un commento quando scrivi un metodo, dovresti mettere la funzionalità in un metodo separato.
  2. Se un metodo richiede più di 10-15 righe di codice, è necessario identificare le attività e le attività secondarie che esegue e provare a inserire le attività secondarie in un metodo separato.

Esistono alcuni modi per eliminare un metodo lungo:

  • Sposta parte della funzionalità del metodo in un metodo separato
  • Se le variabili locali ti impediscono di spostare parte della funzionalità, puoi spostare l'intero oggetto in un altro metodo.

Utilizzo di molti tipi di dati primitivi

Questo problema si verifica in genere quando il numero di campi in una classe aumenta nel tempo. Ad esempio, se memorizzi tutto (valuta, data, numeri di telefono, ecc.) in tipi primitivi o costanti anziché in piccoli oggetti. In questo caso, una buona pratica sarebbe quella di spostare un raggruppamento logico di campi in una classe separata (classe di estrazione). È inoltre possibile aggiungere metodi alla classe per elaborare i dati.

Troppi parametri

Questo è un errore abbastanza comune, specialmente in combinazione con un metodo lungo. Di solito, si verifica se un metodo ha troppe funzionalità o se un metodo implementa più algoritmi. Lunghi elenchi di parametri sono molto difficili da comprendere e l'utilizzo di metodi con tali elenchi è scomodo. Di conseguenza, è meglio passare un intero oggetto. Se un oggetto non dispone di dati sufficienti, è necessario utilizzare un oggetto più generale o suddividere la funzionalità del metodo in modo che ogni metodo elabori dati logicamente correlati.

Gruppi di dati

Gruppi di dati logicamente correlati appaiono spesso nel codice. Ad esempio, i parametri di connessione al database (URL, nome utente, password, nome dello schema, ecc.). Se non è possibile rimuovere un singolo campo da un elenco di campi, questi campi devono essere spostati in una classe separata (classe di estrazione).

Soluzioni che violano i principi OOP

Questi "odori" si verificano quando uno sviluppatore viola il corretto design OOP. Ciò accade quando lui o lei non comprende appieno le capacità OOP e non riesce a utilizzarle completamente o correttamente.

Mancato utilizzo dell'ereditarietà

Se una sottoclasse utilizza solo un piccolo sottoinsieme delle funzioni della classe genitore, allora odora della gerarchia sbagliata. Quando ciò accade, di solito i metodi superflui semplicemente non vengono sovrascritti o generano eccezioni. Una classe che eredita un'altra implica che la classe figlia utilizzi quasi tutte le funzionalità della classe genitore. Esempio di gerarchia corretta: Come funziona il refactoring in Java - 2Esempio di gerarchia errata: Come funziona il refactoring in Java - 3

Cambia istruzione

Cosa potrebbe esserci di sbagliato in una switchdichiarazione? È brutto quando diventa molto complesso. Un problema correlato è un gran numero di ifistruzioni nidificate.

Classi alternative con interfacce diverse

Più classi fanno la stessa cosa, ma i loro metodi hanno nomi diversi.

Campo temporaneo

Se una classe ha un campo temporaneo di cui un oggetto ha bisogno solo occasionalmente quando il suo valore è impostato ed è vuoto o, Dio non voglia, per il nullresto del tempo, allora il codice puzza. Questa è una decisione di progettazione discutibile.

Odori che rendono difficile la modifica

Questi odori sono più seri. Altri odori rendono principalmente più difficile la comprensione del codice, ma impediscono di modificarlo. Quando provi a introdurre nuove funzionalità, metà degli sviluppatori si ritira e metà impazzisce.

Gerarchie di ereditarietà parallele

Questo problema si manifesta quando la sottoclasse di una classe richiede la creazione di un'altra sottoclasse per una classe diversa.

Dipendenze uniformemente distribuite

Qualsiasi modifica richiede di cercare tutti gli usi (dipendenze) di una classe e apportare molte piccole modifiche. Un cambiamento: modifiche in molte classi.

Albero complesso delle modifiche

Questo odore è l'opposto del precedente: le modifiche interessano un gran numero di metodi in una classe. Di norma, tale codice ha una dipendenza a cascata: la modifica di un metodo richiede di correggere qualcosa in un altro, quindi nel terzo e così via. Una classe - molti cambiamenti.

"Odore di spazzatura"

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

Un gran numero di commenti in un metodo

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

Codice duplicato

Classi o metodi diversi utilizzano gli stessi blocchi di codice.

Classe pigra

Una classe assume pochissime funzionalità, sebbene fosse pianificata per essere grande.

Codice non utilizzato

Una classe, un metodo o una variabile non viene utilizzata nel codice ed è un peso morto.

Connettività eccessiva

Questa categoria di odori è caratterizzata da un gran numero di relazioni ingiustificate nel codice.

Metodi esterni

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

Intimità inappropriata

Una classe dipende dai dettagli di implementazione di un'altra classe.

Lunghe chiamate in classe

Una classe ne chiama un'altra, che richiede dati da una terza, che riceve dati da una quarta e così via. Una catena di chiamate così lunga significa un'elevata dipendenza dall'attuale struttura di classe.

Classe di task-dealer

Una classe è necessaria solo per inviare un'attività a un'altra classe. Forse dovrebbe essere rimosso?

Tecniche di refactoring

Di seguito discuteremo le tecniche di refactoring di base che possono aiutare a eliminare gli odori di codice descritti.

Estrai una classe

Una classe esegue troppe funzioni. Alcuni di loro devono essere spostati in un'altra classe. Ad esempio, supponiamo di avere una Humanclasse che memorizza anche un indirizzo di casa e ha un metodo che restituisce 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();
    }
 }
È buona norma inserire le informazioni sull'indirizzo e il metodo associato (comportamento dell'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();
    }
 }

Estrarre un metodo

Se un metodo ha alcune funzionalità che possono essere isolate, dovresti inserirlo 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");
        }
    }
Calcoliamo ciascuna delle tre 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 di ogni metodo è diventato molto più breve e più facile da capire.

Passando un intero oggetto

Quando un metodo viene chiamato con parametri, a volte potresti vedere codice come questo:

 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;
 }
ha employeeMethod2 righe intere dedicate alla ricezione di valori e alla loro memorizzazione in variabili primitive. A volte tali costrutti possono richiedere fino a 10 righe. È molto più semplice passare l'oggetto stesso e utilizzarlo per estrarre i dati necessari:

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

Semplice, breve e conciso.

Raggruppare logicamente i campi e spostarli in un campo separato classDespiteè il fatto che gli esempi sopra sono molto semplici e, quando li guardi, molti di voi potrebbero chiedere: "Chi fa questo?", molti sviluppatori commettono tali errori strutturali a causa della negligenza, riluttanza a rifattorizzare il codice, o semplicemente un atteggiamento di "va abbastanza bene".

Perché il refactoring è efficace

Come risultato di un buon refactoring, un programma ha un codice di facile lettura, la prospettiva di alterarne la logica non è spaventosa e l'introduzione di nuove funzionalità non diventa l'inferno dell'analisi del codice, ma è invece un'esperienza piacevole per un paio di giorni . Non dovresti eseguire il refactoring se sarebbe più facile scrivere un programma da zero. Ad esempio, supponiamo che il tuo team stimi che il lavoro necessario per comprendere, analizzare e rifattorizzare il codice sarà maggiore rispetto all'implementazione della stessa funzionalità da zero. O se il codice da refactoring presenta molti problemi di cui è difficile eseguire il debug. Saper migliorare la struttura del codice è fondamentale nel lavoro di un programmatore. E imparare a programmare in Java è fatto meglio su CodeGym, il corso online che enfatizza la 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 :)

Risorse per immergerti ulteriormente nel refactoring

Il libro più famoso sul refactoring è "Refactoring. Migliorare la progettazione del codice esistente" di Martin Fowler. C'è anche un'interessante pubblicazione sul refactoring, basata su un libro precedente: "Refactoring Using Patterns" di Joshua Kerievsky. A proposito di modelli... Durante il refactoring, è sempre molto utile conoscere i modelli di progettazione di base. Questi ottimi libri ti aiuteranno in questo: A proposito di pattern... Durante il refactoring, è sempre molto utile conoscere i pattern di progettazione di base. Questi ottimi libri ti aiuteranno in questo:
  1. "Design Patterns" di Eric Freeman, Elizabeth Robson, Kathy Sierra e Bert Bates, dalla serie Head First
  2. "L'arte del codice leggibile" di Dustin Boswell e Trevor Foucher
  3. "Code Complete" di Steve McConnell, che stabilisce i principi per un codice bello ed elegante.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION