Sostituire Temp con Query: una tecnica di refactoring

Sostituire Temp con Query: una tecnica di refactoring per un codice più pulito e testabile.

Le variabili temporanee sono tra le fonti più comuni di complessità non necessaria nel codice software. Si accumulano in metodi lunghi, assegnano nomi vaghi ai valori calcolati e rendono più difficile estrarre, testare o riutilizzare la logica che contengono. Il refactoring "Replace Temp with Query", catalogato da Martin Fowler in " Refactoring: Improving the Design of Existing Code" , affronta direttamente questo problema: invece di memorizzare un valore calcolato in una variabile locale, si estrae il calcolo in un metodo denominato, una query, e lo si richiama ovunque sia necessario il valore.

Il risultato è un codice che comunica l'intento anziché nasconderlo. Il calcolo non è più sepolto in un'assegnazione di variabile all'inizio di un lungo metodo; ha un nome, una posizione e la possibilità di essere testato in isolamento. Questo articolo illustra la tecnica completa, cosa sono le variabili temporanee, quando diventano problematiche, come eseguire il refactoring passo dopo passo in Java, Python e TypeScript, quando applicarlo e quando no, e come si collega alle tecniche correlate presenti nel catalogo del refactoring.

Ristruttura il tuo codice con sicurezza

SMART TS XL Tracce in cui vengono utilizzate variabili temporanee, calcoli duplicati e metodi di query estratti.

Saperne di più

Che cos'è una variabile temporanea (Temp) nella programmazione?

Una variabile temporanea, comunemente chiamata ` temp` , è una variabile locale all'interno di una funzione o di un metodo che memorizza un risultato intermedio da utilizzare nello stesso ambito. Viene calcolata una sola volta, memorizzata in una variabile con nome e quindi richiamata in seguito nella stessa funzione. La variabile esiste solo per la durata della chiamata alla funzione; non è accessibile dall'esterno e non viene memorizzata nello stato dell'oggetto.

python

# Python: base_price is a temp variable
def calculate_total(quantity, item_price):
    base_price = quantity * item_price   # temp: computed once, used below
    if base_price > 1000:
        return base_price * 0.95
    return base_price * 0.98

Giava

// Java: basePrice is a temp variable
double basePrice = quantity * itemPrice;   // temp
if (basePrice > 1000) {
    return basePrice * 0.95;
}
return basePrice * 0.98;

dattiloscritto

// TypeScript: basePrice is a temp variable
const basePrice = quantity * itemPrice;    // temp
if (basePrice > 1000) return basePrice * 0.95;
return basePrice * 0.98;

Le variabili temporanee non sono intrinsecamente negative. Hanno usi legittimi: catturare il risultato di un'operazione dispendiosa che sarebbe uno spreco ripetere, suddividere un calcolo complesso in più fasi in passaggi più leggibili o memorizzare valori che si accumulano durante le iterazioni di un ciclo. Il problema sorge quando le variabili temporanee vengono utilizzate in modo automatico per semplici valori derivati ​​che sarebbero più chiari se fossero metodi con nome, oppure quando si accumulano in un metodo lungo e costringono chi legge il codice a tenere traccia di più valori intermedi attivi simultaneamente.

Che cos'è il refactoring nell'ingegneria del software?

Il refactoring è il processo di ristrutturazione del codice esistente senza modificarne il comportamento osservabile. L'obiettivo è migliorare la qualità interna del codice: leggibilità, testabilità, manutenibilità e modularità. Un'operazione di refactoring non aggiunge funzionalità né corregge bug; modifica la struttura del codice preservandone la funzione.

Sostituire Temp con Query è una delle tecniche di refactoring presenti in un catalogo di diverse decine di tecniche descritte da Martin Fowler. Appartiene a una famiglia di tecniche che si occupano di metodi diventati troppo lunghi o troppo complessi:

Tecnica di refactoringCosa fa
Sostituisci Temp con QueryEstrae il calcolo di una variabile temporanea in un metodo denominato
Metodo di estrazioneEstrae un blocco di codice in un nuovo metodo denominato
Temp in lineaSostituisce una semplice variabile temporanea con la sua espressione direttamente
Dividi variabile temporaneaSepara una variabile temporanea riutilizzata per scopi diversi in variabili distinte.
Sostituisci il ciclo con la pipelineSostituisce un ciclo imperativo con una pipeline funzionale (map, filter, reduce)
Introduzione della variabile esplicativaIntroduce un temp denominato per chiarire un'espressione complessa

Queste tecniche non vengono utilizzate isolatamente. La sostituzione di Temp con Query è descritta da Fowler come un passaggio fondamentale prima dell'estrazione del metodo: se un metodo ha variabili temporanee, estrarne una parte in un nuovo metodo diventa difficile perché tali variabili temporanee potrebbero essere utilizzate sia prima che dopo la sezione estratta. Eliminare prima le variabili temporanee trasformandole in query libera il percorso per l'estrazione.

Che cos'è "Sostituisci Temp con Query"?

La tecnica di refactoring "Replace Temp with Query" trasforma una variabile temporanea locale in una chiamata a un metodo. Invece di calcolare un valore e assegnarlo a una variabile locale, si estrae il calcolo in un metodo privato, `query`, che restituisce il valore calcolato quando viene chiamato. Ovunque venisse utilizzata la variabile temporanea, si sostituisce il riferimento con una chiamata al metodo `query`.

L'esempio canonico tratto da Refactoring di Fowler :

Prima:

Giava

double basePrice = _quantity * _itemPrice;
if (basePrice > 1000)
    return basePrice * 0.95;
else
    return basePrice * 0.98;

Dopo:

Giava

if (basePrice() > 1000)
    return basePrice() * 0.95;
else
    return basePrice() * 0.98;

private double basePrice() {
    return _quantity * _itemPrice;
}

Il metodo di interrogazione basePrice() Ora è un calcolo autonomo e denominato. Può essere chiamato da qualsiasi altro metodo della classe, testato in modo indipendente, sovrascritto nelle sottoclassi e compreso senza dover prima leggere il metodo chiamante.

Il problema con le variabili temporanee

Frammentano la logica attraverso un metodo

Una variabile temporanea suddivide un calcolo in due parti distinte: l'assegnazione (dove viene calcolato il valore) e l'utilizzo (dove viene letto). In un metodo breve, questa suddivisione non crea problemi. In un metodo che ha raggiunto le trenta o cinquanta righe, l'assegnazione e l'utilizzo possono essere separati da molte righe di altra logica. Chi legge deve scorrere verso l'alto per trovare l'assegnazione, memorizzare il significato e scorrere di nuovo verso l'utilizzo. Ogni variabile temporanea aggiuntiva aumenta questo sovraccarico cognitivo.

Bloccano il metodo di estrazione

Il problema pratico più significativo con le variabili temporanee (temp) è che bloccano altre operazioni di refactoring. Si consideri un metodo con un ramo condizionale complesso che trarrebbe vantaggio dall'essere estratto in un metodo separato. Se il ramo utilizza una variabile temporanea assegnata in precedenza nel metodo, l'estrazione richiede di passare la variabile temporanea come parametro, di renderla una variabile di istanza o di ricalcolarne il valore all'interno del metodo estratto. Nessuna di queste opzioni è elegante. Eliminare prima la variabile temporanea sostituendola con una query rimuove completamente questo ostacolo.

Invitano al riutilizzo e alla mutazione

Le variabili temporanee vengono talvolta riutilizzate per scopi diversi all'interno dello stesso metodo, una pratica che Fowler chiama "intreccio di variabili temporanee". Una variabile denominata temp or result Un valore che viene riassegnato più volte non fornisce alcuna informazione semantica e induce attivamente in errore i lettori riguardo a ciò che rappresenta in un dato momento. Anche i valori temporanei a scopo singolo possono accumularsi fino a quando l'ambito di un metodo non risulta ingombrato da valori intermedi che i lettori devono tenere traccia simultaneamente.

Procedura dettagliata: come applicare la sostituzione temporanea con query

La trasformazione si articola in quattro fasi che possono essere applicate in sicurezza in qualsiasi linguaggio:

Passaggio 1: Verificare che la variabile temporanea sia stata assegnata una sola volta e non venga mai modificata. Se la variabile temporanea viene riassegnata in seguito nel metodo, dividerla prima utilizzando la funzione Dividi variabile temporanea.

Passaggio 2: Estrarre la parte destra dell'assegnazione in un metodo privato. Assegna al metodo un nome che descriva cosa calcola, non come lo calcola. basePrice() è meglio che calculateQuantityTimesPrice().

Passaggio 3: sostituire ogni riferimento alla variabile temporanea con una chiamata al nuovo metodo. La maggior parte degli IDE può farlo automaticamente: fare clic con il pulsante destro del mouse sulla variabile temporanea → Refactor → Inline Variable, quindi Extract Method sull'espressione inline.

Passaggio 4: Eliminare la dichiarazione della variabile temporanea. Se l'estrazione è completa, la variabile temporanea non dovrebbe più avere riferimenti e può essere rimossa.

Java: esempio completo e funzionante

Giava

// Before: Order class with temporary variables
public class Order {
    private int quantity;
    private double itemPrice;

    public double getPrice() {
        double basePrice    = quantity * itemPrice;        // temp 1
        double discountFactor;                             // temp 2
        if (basePrice > 1000)
            discountFactor = 0.95;
        else
            discountFactor = 0.98;
        return basePrice * discountFactor;
    }
}

Giava

// After: temps extracted to query methods
public class Order {
    private int quantity;
    private double itemPrice;

    public double getPrice() {
        return basePrice() * discountFactor();
    }

    private double basePrice() {
        return quantity * itemPrice;
    }

    private double discountFactor() {
        return basePrice() > 1000 ? 0.95 : 0.98;
    }
}

Il metodo getPrice() ora si legge come un'unica espressione che comunica chiaramente il calcolo. Ogni query estratta può essere letta, testata ed estesa indipendentemente. Nota che discountFactor() chiamate basePrice(), questo è corretto perché basePrice() Si tratta di un calcolo puro, senza effetti collaterali, quindi chiamarlo due volte non introduce alcun rischio.

Python: sostituire Temp con Property

In Python, l'equivalente naturale di un metodo di query è un @property, che consente di chiamare il metodo senza parentesi e si legge in modo identico a un accesso ad attributo:

python

# Before: temporary variables in a method
class Order:
    def __init__(self, quantity, item_price):
        self.quantity   = quantity
        self.item_price = item_price

    def get_price(self):
        base_price     = self.quantity * self.item_price  # temp
        discount       = 0.95 if base_price > 1000 else 0.98  # temp
        return base_price * discount

python

# After: temps replaced with properties (query methods in Python)
class Order:
    def __init__(self, quantity, item_price):
        self.quantity   = quantity
        self.item_price = item_price

    def get_price(self):
        return self.base_price * self.discount_factor

    @property
    def base_price(self):
        return self.quantity * self.item_price

    @property
    def discount_factor(self):
        return 0.95 if self.base_price > 1000 else 0.98

utilizzando @property si intende self.base_price legge in modo identico a una variabile di istanza, rendendo il codice chiamante self.base_price * self.discount_factor completamente naturale. Ogni proprietà è testabile in modo indipendente:

python

def test_base_price():
    order = Order(10, 150)
    assert order.base_price == 1500

def test_discount_factor_high_value():
    order = Order(10, 150)   # base_price = 1500 > 1000
    assert order.discount_factor == 0.95

def test_get_price():
    order = Order(10, 150)
    assert order.get_price() == 1500 * 0.95

Questo livello di testabilità è impossibile con la versione basata su temp: i calcoli interni base_price and discount_factor non sono accessibili dall'esterno del metodo.

TypeScript: Metodi di query e getter

TypeScript supporta sia le query basate su metodi che i metodi getter per le proprietà, replicando i modelli disponibili rispettivamente in Java e Python:

dattiloscritto

// Before: temporary variables
class Order {
    constructor(private quantity: number, private itemPrice: number) {}

    getPrice(): number {
        const basePrice = this.quantity * this.itemPrice;  // temp
        const discount  = basePrice > 1000 ? 0.95 : 0.98; // temp
        return basePrice * discount;
    }
}

dattiloscritto

// After: TypeScript getters replace temps
class Order {
    constructor(private quantity: number, private itemPrice: number) {}

    getPrice(): number {
        return this.basePrice * this.discountFactor;
    }

    private get basePrice(): number {
        return this.quantity * this.itemPrice;
    }

    private get discountFactor(): number {
        return this.basePrice > 1000 ? 0.95 : 0.98;
    }
}

Assegnare un nome appropriato ai metodi di interrogazione

Il nome del metodo di query svolge il ruolo più importante. Un'estrazione con un nome inadeguato è peggiore della variabile temporanea che sostituisce, perché crea un'indirezione opaca: chi la chiama deve consultare la definizione del metodo per comprenderne il funzionamento, vanificandone lo scopo.

I nomi appropriati per i metodi di query seguono questi principi:

Indica cosa rappresenta, non come viene calcolato. basePrice() comunica il concetto aziendale. getQuantityTimesItemPrice() descrive il calcolo, non il concetto. La distinzione è importante quando il calcolo cambia, il nome del concetto basePrice() rimane stabile anche se la formula cambia.

Utilizzare sintagmi nominali per esprimere i valori. I metodi di query restituiscono valori; non sono comandi. discountFactor(), totalAmount(), isEligible() seguire la convenzione di denominazione di ciò che viene restituito. calculateDiscount(), processAmount() seguire la convenzione imperativa dei comandi, che può creare confusione per i metodi che si limitano a calcolare e restituire un valore.

Le query booleane dovrebbero essere formulate come domande. isHighValue(), hasDiscount(), meetsThreshold() Comunicare che il valore di ritorno è booleano e che chi chiama sta ponendo una domanda a cui si può rispondere con un sì o un no. bool 変数名 (La denominazione delle variabili booleane, una query nei dati di Search Console) riflette esattamente questa preoccupazione: le variabili e i metodi booleani necessitano di nomi che ne rendano chiaro il significato al momento dell'utilizzo.

Mantieni i nomi stabili tra i metodi correlati. If basePrice() è usato da discountFactor(), la coerenza nella denominazione dice ai lettori che discountFactor dipende basePriceUna denominazione incoerente compromette questa documentazione implicita.

Quando applicare Sostituire Temp con Query

Applica questo refactoring quando:

  • Il temporaneo viene assegnato una sola volta e non viene mai riassegnato.
  • Il calcolo è un'espressione pura: legge da campi o parametri ma non modifica lo stato esterno, non chiama servizi di rete e non dipende dal tempo o dalla casualità.
  • Il calcolo è abbastanza complesso da rendere più leggibile la sua denominazione, oppure è abbastanza semplice da rendere la variabile temporanea solo un elemento superfluo.
  • Stai per applicare il metodo Extract a un blocco che utilizza la temperatura

Lo scenario ideale più comune è quello di un valore derivato: un prezzo, un totale, uno sconto, una stringa formattata, una classificazione condizionale. Si tratta di valori derivati ​​interamente dai campi dell'oggetto, senza effetti collaterali, che per loro natura appartengono alle proprietà dell'oggetto piuttosto che essere calcolati come operazioni intermedie all'interno di un metodo.

Quando non applicare Sostituire Temp con Query

Operazioni sensibili alle prestazioni. Se il calcolo è oneroso, ad esempio una query al database, una chiamata di rete o un ciclo O(n²), chiamare due volte il metodo di query introduce un costo doppio. L'oggetto temporaneo esiste proprio per evitare questo. In questi casi, è possibile lasciare l'oggetto temporaneo attivo oppure memorizzare il risultato del metodo di query (memorizzandolo nella cache dopo la prima chiamata):

python

# Memoized property: computed once, cached
from functools import cached_property

class Order:
    @cached_property
    def expensive_validation(self):
        return self.external_service.validate(self.data)  # called once, cached

Operazioni con effetti collaterali. Se la variabile temporanea contiene il risultato di un'operazione che dovrebbe essere eseguita una sola volta (generazione di un ID univoco, registrazione, scrittura su un file), trasformarla in una query eseguirebbe l'operazione a ogni chiamata. Questo modifica il comportamento del programma, non solo la sua struttura. Non applicare questo refactoring alle variabili temporanee con effetti collaterali.

Temperature che si accumulano nel corso delle iterazioni del ciclo. Un temp che è l'accumulatore in un for cappio, total += item.price, non è un candidato per la sostituzione di Temp con Query. Non è un valore derivato; è uno stato che si accumula nel corso delle iterazioni. Considera invece la sostituzione del ciclo con Pipeline se il problema risiede nel ciclo.

Tecniche di refactoring correlate

La sostituzione di Temp con Query appartiene a una famiglia di tecniche che, nel loro insieme, eliminano la complessità non necessaria nei metodi. Comprendere questa famiglia di tecniche aiuta gli sviluppatori a scegliere la tecnica più adatta al problema specifico:

Il metodo Extract è il complemento più comune. La sostituzione di Temp con Query spesso rende possibile l'utilizzo di Extract Method, eliminando le variabili che altrimenti richiederebbero un passaggio di parametri macchinoso tra la parte estratta e il resto del metodo.

Inline Temp è l'opposto dell'introduzione di un'istruzione temp: sostituisce un'istruzione temp con la sua espressione direttamente nel codice. Utilizzare Inline Temp quando l'istruzione temp non aggiunge chiarezza e la sua espressione è già leggibile.

La divisione di una variabile temporanea si applica quando una singola variabile temporanea viene riutilizzata per più scopi nello stesso metodo. Suddividerla in variabili separate con nomi che riflettano ciascuno scopo, quindi applicare la sostituzione della variabile temporanea con una query a una qualsiasi delle variabili temporanee monouso risultanti.

L'approccio "Introduce Explaining Variable" è l'opposto: se un'espressione complessa è difficile da leggere, l'introduzione di una variabile temporanea con un nome descrittivo può migliorarne la chiarezza. Questa tecnica e "Replace Temp with Query" sono in contrasto tra loro e lo sviluppatore deve valutare quale approccio migliori il codice specifico in questione.

Sostituisci il ciclo con la pipeline affronta uno schema comune in cui un ciclo con un accumulatore temporaneo può essere sostituito da un'operazione di pipeline concatenata (map, filter, reduce), che è più dichiarativa e più facile da leggere.

Come SMART TS XL Supporta il refactoring su larga scala

Sostituire Temp con Query è un refactoring locale: trasforma una variabile in un metodo. In una codebase di dimensioni significative, la domanda più utile non è "come applico questo refactoring?" ma "in quale punto dell'intera codebase dovrei applicarlo e cosa ne sarà influenzato?".

SMART TS XL fornisce l'analisi strutturale cross-codebase che rende sistematica la risposta a questa domanda. Identifica dove lo stesso calcolo viene eseguito come variabile temporanea in più posizioni, il modello che Replace Temp with Query è progettato per consolidare in un singolo metodo di query denominato. Traccia come viene utilizzato un metodo di query ristrutturato una volta estratto, rendendo visibile l'ambito del refactoring prima che venga effettuato. E funziona tra i linguaggi: per i sistemi aziendali in cui i programmi COBOL, i servizi Java e le pipeline Python operano tutti sugli stessi dati, analisi statica del codice and analisi d'impatto identificare dove lo stesso calcolo logico appare in forme diverse in linguaggi diversi, la forma più profonda del problema che Replace Temp with Query affronta a livello di singolo linguaggio.

Per i team che lavorano su modernizzazione dell'eredità, SMART TS XL'S visualizzazione delle dipendenze Consente di visualizzare dove vengono utilizzati i componenti ristrutturati prima di modificarli, garantendo che l'estrazione di un calcolo in un metodo di query non comprometta il funzionamento dei chiamanti che si aspettavano la struttura originale.

Variabili temporanee e codice autodocumentante

La decisione di sostituire una variabile temporanea con una query è in definitiva una decisione su cosa il codice dovrebbe comunicare. Le variabili temporanee comunicano l'implementazione: questo è il calcolo che ho eseguito per ottenere questo valore. I metodi di query comunicano il dominio: questo è ciò che significa questo valore. In una classe Order, basePrice() comunica al lettore che questo concetto esiste nel dominio. double x = quantity * itemPrice descrive al lettore un'operazione aritmetica.

Con l'evoluzione del codice, i concetti di dominio necessitano di una collocazione stabile. Un calcolo incorporato in un oggetto temporaneo può cambiare, essere duplicato in più metodi o essere frainteso dal prossimo sviluppatore che lo legge. Un metodo di query con un nome specifico può essere trovato, testato, documentato e fatto evolvere intenzionalmente. Questa stabilità, in tutti i punti in cui il calcolo è necessario e per tutti gli sviluppatori che ci lavoreranno, è ciò che rende la sostituzione di un oggetto temporaneo con una query più di un semplice cambio di sintassi. È una decisione su come la codebase comunica il problema che risolve.