Nahrazení Temp za Query: Technika refaktoringu

Nahraďte Temp kódem Query: Technika refaktoringu pro čistší a testovatelnější kód

Dočasné proměnné patří mezi nejčastější zdroje zbytečné složitosti v softwarovém kódu. Hromadí se v dlouhých metodách, dávají vágní názvy vypočítaným hodnotám a ztěžují extrakci, testování nebo opětovné použití logiky, kterou obsahují. Refaktoring typu „Nahradit dočasné proměnné dotazem“, katalogizovaný Martinem Fowlerem v publikaci Refactoring: Improving the Design of Existing Code , to řeší přímo: namísto ukládání vypočítané hodnoty do lokální proměnné extrahujete výpočet do pojmenované metody, dotazu, a voláte ho, kdekoli je hodnota potřeba.

Výsledkem je kód, který komunikuje záměr, místo aby jej skrýval. Výpočet již není skryt v proměnné přiřazené na začátku dlouhé metody; má název, umístění a možnost testování izolovaně. Tento článek se zabývá kompletní technikou, co jsou dočasné proměnné, kdy se stanou problémovými, jak provést refaktoring krok za krokem v Javě, Pythonu a TypeScriptu, kdy jej použít a kdy ne a jak se propojuje s souvisejícími technikami v katalogu refaktoringu.

Refaktorujte svůj kód s jistotou

SMART TS XL stopy, kde se používají dočasné proměnné, duplicitní výpočty a extrahované metody dotazování.

Zjistěte VÍCE

Co je to dočasná proměnná (Temp) v programování?

Dočasná proměnná, běžně nazývaná temp , je lokální proměnná uvnitř funkce nebo metody, která ukládá mezivýsledek pro použití ve stejném oboru platnosti. Vypočítá se jednou, uchovává se v pojmenované proměnné a poté se na ni později odkazuje ve stejné funkci. Proměnná existuje pouze po dobu trvání volání funkce; není přístupná mimo ni a není uložena ve stavu objektu.

krajta

# 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

Jáva

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

strojopis

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

Dočasné proměnné nejsou ze své podstaty špatné. Mají legitimní využití: zachycení výsledku nákladné operace, jejíž opakování by bylo zbytečné, rozdělení složitého vícekrokového výpočtu do čitelných fází nebo uchovávání hodnot, které se hromadí v průběhu iterací cyklu. Problém nastává, když se dočasné proměnné používají reflexivně pro jednoduché odvozené hodnoty, které by byly srozumitelnější jako pojmenované metody, nebo když se hromadí v průběhu dlouhé metody a nutí čtenáře sledovat více současně aktivních mezilehlých hodnot.

Co je refaktoring v softwarovém inženýrství?

Refaktoring je proces restrukturalizace existujícího kódu beze změny jeho pozorovatelného chování. Cílem je zlepšit vnitřní kvalitu kódu: jeho čitelnost, testovatelnost, udržovatelnost a modularitu. Refaktoring nepřidává nové funkce ani neopravuje chyby; mění strukturu kódu a zároveň zachovává jeho funkčnost.

Replace Temp with Query je jednou z technik refaktoringu v katalogu několika desítek technik popsaných Martinem Fowlerem. Patří do rodiny technik, které se zabývají metodami, které se staly příliš dlouhými nebo příliš složitými:

Technika refaktorováníCo to dělá
Nahraďte dočasný dotaz dotazemExtrahuje výpočet dočasné proměnné do pojmenované metody.
Metoda extraktuExtrahuje blok kódu do nově pojmenované metody.
Teplota v potrubíNahradí jednoduchý dočasný výraz přímo jeho výrazem.
Rozdělit dočasnou proměnnouOdděluje dočasné objekty, které se znovu používají pro různé účely, do samostatných proměnných.
Nahraďte smyčku potrubímNahrazuje imperativní smyčku funkčním kanálem (mapování, filtrování, redukce)
Úvod Vysvětlení proměnnéZavádí pojmenovanou dočasnou funkci pro objasnění složitého výrazu.

Tyto techniky se nepoužívají izolovaně. Nahrazení dočasných proměnných dotazem popisuje Fowler jako zásadní krok před extrakcí metody: pokud metoda obsahuje dočasné proměnné, extrakce její části do nové metody se stává obtížnou, protože tyto dočasné proměnné mohou být použity před i po extrahované části. Eliminace dočasných proměnných jejich přeměnou na dotazy uvolní cestu pro extrakci.

Co je Nahradit dočasný dotaz dotazem?

Nahrazení dočasné proměnné dotazem je technika refaktoringu, která transformuje lokální dočasnou proměnnou na volání metody. Místo výpočtu hodnoty a jejího přiřazení lokální proměnné extrahujete výpočet do soukromé metody dotazu, která při volání vrací vypočítanou hodnotu. Kdekoli byla použita dočasná proměnná, nahradíte odkaz voláním metody dotazu.

Kanonický příklad z Fowlerova refaktoringu :

Před:

Jáva

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

Po:

Jáva

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

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

Metoda dotazu basePrice() je nyní pojmenovaný, samostatný výpočet. Lze jej volat z jakékoli jiné metody ve třídě, testovat nezávisle, přepisovat v podtřídách a chápat bez nutnosti nejprve číst volající metodu.

Problém s dočasnými proměnnými

Fragmentují logiku napříč metodou

Dočasná proměnná rozděluje výpočet na dvě oddělené části: přiřazení (kde se hodnota vypočítává) a použití (kde se čte). V krátké metodě je toto rozdělení neškodné. V metodě, která se rozrostla na třicet nebo padesát řádků, může být přiřazení a použití odděleno mnoha řádky jiné logiky. Čtenář se musí posunout nahoru, aby našel přiřazení, uchovat význam v pracovní paměti a posunout se zpět k použití. Každá další dočasná proměnná tuto kognitivní režii zvyšuje.

Metoda extrakci bloků

Nejvýznamnějším praktickým problémem s dočasnými proměnnými je, že blokují jiné refaktoringy. Uvažujme metodu se složitou podmíněnou větví, která by měla prospěch z extrakce do vlastní metody. Pokud větev používá dočasnou proměnnou, která byla přiřazena dříve v metodě, extrakce vyžaduje buď předání dočasné proměnné jako parametru, vytvoření proměnné instance nebo opětovný výpočet její hodnoty uvnitř extrahované metody. Žádná z těchto možností není čistá. Eliminace dočasné proměnné jejím nahrazením dotazem tuto překážku zcela odstraňuje.

Vybízejí k opětovnému použití a mutaci

Dočasné proměnné se někdy v rámci stejné metody znovu používají pro různé účely, což Fowler nazývá „spleť dočasných proměnných“. Proměnná s názvem temp or result ..., který je opakovaně přiřazen, neposkytuje žádné sémantické informace a aktivně uvádí čtenáře v omyl ohledně toho, co v daném bodě představuje. I jednoúčelové dočasné hodnoty se mohou hromadit, dokud není rozsah metody zahlcen mezilehlými hodnotami, které musí čtenáři sledovat současně.

Krok za krokem: Jak použít funkci Nahradit dočasný dotaz

Transformace probíhá ve čtyřech krocích, které lze bezpečně použít v jakémkoli jazyce:

Krok 1: Ověřte, že dočasná proměnná je přiřazena přesně jednou a nikdy není mutována. Pokud je dočasná proměnná později v metodě znovu přiřazena, nejprve ji rozdělte pomocí příkazu Rozdělit dočasnou proměnnou.

Krok 2: Extrahujte pravou stranu přiřazení do soukromé metody. Dejte metodě název, který popisuje, co počítá, ne jak. basePrice() je lepší než calculateQuantityTimesPrice().

Krok 3: Nahraďte každý odkaz na dočasnou proměnnou voláním nové metody. Většina IDE to dokáže automaticky: klikněte pravým tlačítkem myši na dočasnou proměnnou → Refaktorovat → Vložená proměnná a poté na vložený výraz vyberte Extrahovat metodu.

Krok 4: Smažte deklaraci dočasné proměnné. Pokud je extrakce dokončena, dočasná proměnná by nyní neměla mít žádné odkazy a lze ji odstranit.

Java: Úplný pracovní příklad

Jáva

// 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;
    }
}

Jáva

// 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;
    }
}

Metoda getPrice() nyní se čte jako jeden výraz, který jasně sděluje výpočet. Každý extrahovaný dotaz lze číst, testovat a rozšiřovat nezávisle. Všimněte si, že discountFactor() volá basePrice(), to je správně, protože basePrice() je čistý výpočet bez vedlejších účinků, takže jeho dvojité volání nepředstavuje žádné riziko.

Python: Nahraďte Temp vlastností

V Pythonu je přirozeným ekvivalentem metody dotazování @property, což umožňuje volání metody bez závorek a čte se identicky jako přístup k atributu:

krajta

# 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

krajta

# 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

Použití @property znamená self.base_price čte se identicky s proměnnou instance, takže volající kód self.base_price * self.discount_factor zcela přirozené. Každá vlastnost je nezávisle testovatelná:

krajta

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

Tato úroveň testovatelnosti není možná s dočasnou verzí: interní výpočty base_price a discount_factor nejsou přístupné zvenčí metody.

TypeScript: Metody dotazování a gettery

TypeScript podporuje jak dotazy založené na metodách, tak i metody pro získávání vlastností, což odpovídá vzorům dostupným v Javě a Pythonu:

strojopis

// 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;
    }
}

strojopis

// 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;
    }
}

Pojmenování metod dotazů Well

Název metody dotazu odvádí nejdůležitější práci. Špatně pojmenovaná extrakce je horší než dočasná metoda, kterou nahradila, protože vytváří neprůhlednou indirekční metodu: volající metody musí přejít k definici metody, aby pochopily, co dělá, což maří její účel.

Dobré názvy metod dotazování se řídí těmito principy:

Pojmenujte, co to představuje, ne jak se to vypočítává. basePrice() sděluje obchodní koncept. getQuantityTimesItemPrice() popisuje výpočet, nikoli koncept. Rozdíl je důležitý, když se výpočet změní, název konceptu basePrice() zůstává stabilní, i když se vzorec změní.

Pro hodnoty používejte jmenné fráze. Metody dotazů vracejí hodnoty; nejsou to příkazy. discountFactor(), totalAmount(), isEligible() dodržujte konvenci pojmenovávání vrácených hodnot. calculateDiscount(), processAmount() dodržovat imperativní konvenci příkazů, což je matoucí u metod, které čistě počítají a vracejí hodnotu.

Booleovské dotazy by se měly číst jako otázky. isHighValue(), hasDiscount(), meetsThreshold() sdělit, že návratová hodnota je booleovská hodnota a že volající se ptá na otázku ano/ne. bool 変数名 (booleovské pojmenování proměnných, dotaz v datech Search Console) přesně odráží tuto obavu: booleovské proměnné a metody potřebují názvy, které v okamžiku použití jasně vyjadřují jejich význam.

Udržujte názvy stabilní napříč souvisejícími metodami. If basePrice() je používán discountFactor(), konzistence pojmenování čtenářům říká, že discountFactor závisí na tom, basePriceNekonzistentní pojmenování narušuje tuto implicitní dokumentaci.

Kdy použít Nahradit dočasný dotaz dotazem

Tuto refaktorizaci použijte, když:

  • Dočasná hodnota je přiřazena přesně jednou a nikdy se nepřiřazuje znovu.
  • Výpočet je čistý výraz: čte z polí nebo parametrů, ale nemodifikuje externí stav, nevolá síťové služby ani nezávisí na čase nebo náhodnosti.
  • Výpočet je dostatečně složitý, aby jeho pojmenování pomohlo s čitelností, nebo dostatečně jednoduchý, aby dočasný údaj jen zkomplikoval situaci.
  • Chystáte se použít metodu Extract na blok, který používá dočasný

Nejběžnějším ideálním scénářem je odvozená hodnota: cena, celková částka, sleva, formátovaný řetězec, podmíněná klasifikace. Jedná se o hodnoty odvozené výhradně z polí objektu, bez vedlejších účinků, které přirozeně patří jako vlastnosti objektu, spíše než jako mezivýpočty uvnitř metody.

Kdy nepoužít Nahradit dočasný dotaz dotazem

Operace citlivé na výkon. Pokud je výpočet nákladný, databázový dotaz, síťové volání, smyčka O(n²), dvojité volání metody dotazu představuje dvojnásobné náklady. Dočasná metoda existuje právě proto, aby se tomu zabránilo. V těchto případech buď ponechte dočasnou metodu na místě, nebo metodu dotazu zapamatujte (uložte výsledek do mezipaměti po prvním volání):

krajta

# 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

Operace s vedlejšími účinky. Pokud dočasná proměnná obsahuje výsledek operace, která by se měla spustit pouze jednou (generování jedinečného ID, protokolování, zápis do souboru), její převedení na dotaz by spustil operaci při každém volání. Tím se změní chování programu, nejen jeho struktura. Nepoužívejte tuto refaktorizaci na dočasné proměnné s vedlejšími účinky.

Dočasné hodnoty, které se hromadí v průběhu iterací smyčky. Teplota, která je akumulátorem v for smyčka, total += item.price, není kandidátem na Replace Temp with Query. Nejedná se o odvozenou hodnotu; je to stav, který se hromadí v průběhu iterací. Pokud je problémem smyčka, zvažte místo toho Replace Loop with Pipeline.

Související techniky refaktoringu

Metoda Replace Temp za Query patří do skupiny technik, které společně eliminují zbytečnou složitost metod. Pochopení této skupiny pomáhá vývojářům vybrat správnou techniku ​​pro daný problém:

Nejběžnějším společníkem je metoda Extract . Nahrazení Temp za Query často umožňuje použití metody Extract tím, že vymaže proměnné, které by jinak vyžadovaly nepraktické předávání parametrů mezi extrahovanou částí a zbytkem metody.

Inline Temp je opakem zavedení dočasného výrazu: nahrazuje dočasný výraz jeho výrazem přímo v kódu. Inline Temp použijte, když dočasný výraz nepřidává jasnost a jeho výraz je již čitelný.

Možnost Rozdělit dočasnou proměnnou se použije, když je jedna dočasná proměnná znovu použita pro více účelů ve stejné metodě. Rozdělte ji na samostatné proměnné s názvy odrážejícími každý účel a poté na kteroukoli z výsledných jednorázových dočasných proměnných použijte možnost Nahradit dočasnou proměnnou dotazem.

Zavedení vysvětlující proměnné má opačný směr: pokud je složitý výraz obtížně čitelný, zavedení dočasné proměnné s popisným názvem může zlepšit jeho srozumitelnost. Tato technika a technika Nahrazení dočasné proměnné dotazem jsou v konfliktu a vývojář musí posoudit, který směr vylepšuje daný konkrétní kód.

Nahraďte smyčku potrubím řeší běžný vzorec, kdy smyčku s dočasným akumulátorem lze nahradit zřetězenou operací kanálu (map, filter, reduce), což je deklarativnější a snáze čitelné.

Jak SMART TS XL Podporuje refaktoring ve velkém měřítku

Replace Temp za Query je lokální refaktoring: transformuje jednu proměnnou v jedné metodě. V kódové základně jakékoli významnější velikosti není užitečnější otázkou „jak tuto refaktoringovou změnu aplikuji?“, ale „kam v celé kódové základně ji mám aplikovat a co to ovlivní?“.

SMART TS XL poskytuje strukturální analýzu napříč kódovými základnami, která systematizuje zodpovězení této otázky. Identifikuje, kde se stejný výpočet provádí jako dočasná proměnná na více místech, což je vzorec, který má metoda Replace Temp with Query sloučit do jedné pojmenované metody dotazu. Sleduje, jak se refaktorovaná metoda dotazu používá po jejím extrahování, a zviditelní tak rozsah refaktoringu ještě před jeho provedením. A funguje napříč jazyky: pro podnikové systémy, kde programy v COBOLu, služby Java a pipelines v Pythonu pracují se stejnými daty, statická analýza kódu a analýza dopadu identifikujte, kde se stejný logický výpočet objevuje v různých formách v různých jazycích, což je hlubší forma problému, kterou metoda Replace Temp with Query řeší na úrovni jednoho jazyka.

Pro týmy pracující na starší modernizace, SMART TS XLJe vizualizace závislostí Umožňuje vidět, kde jsou použity refaktorované komponenty před jejich změnou, a zajišťuje, že extrakce výpočtu do metody dotazu nenaruší volající, kteří očekávali původní strukturu.

Dočasné proměnné a samodokumentační kód

Rozhodnutí nahradit dočasnou proměnnou dotazem je v konečném důsledku rozhodnutím o tom, co má kód sdělovat. Dočasné proměnné sdělují implementaci: toto je výpočet, který jsem provedl, abych tuto hodnotu získal. Metody dotazu sdělují doménu: toto je význam této hodnoty. Ve třídě Order, basePrice() říká čtenáři, že tento koncept v dané oblasti existuje. double x = quantity * itemPrice vypráví čtenáři o aritmetické operaci.

S vývojem kódu potřebují koncepty domén stabilní domovy. Výpočet vložený do dočasné metody se může změnit, duplikovat v několika metodách nebo být špatně pochopen dalším vývojářem, který si ji přečte. Pojmenovanou metodu dotazu lze najít, otestovat, zdokumentovat a záměrně vyvíjet. Tato stabilita na všech místech, kde je výpočet potřeba, a napříč všemi vývojáři, kteří s ní budou pracovat, dělá z metody Replace Temp Query více než jen změnu syntaxe. Je to rozhodnutí o tom, jak kódová základna komunikuje problém, který řeší.