Asenda Temp päringuga: refaktoreerimise tehnika

Asenda Temp päringuga: puhtama ja testitavama koodi refaktoreerimise tehnika

Ajutised muutujad on tarkvarakoodi ühed levinumad ebavajaliku keerukuse allikad. Need kuhjuvad pikkadesse meetoditesse, annavad arvutatud väärtustele ebamäärased nimed ja raskendavad neis oleva loogika ekstraheerimist, testimist või taaskasutamist. Martin Fowleri poolt raamatus „Refactoring: Improving the Design of Existing Code“ kataloogitud meetod „ Asenda ajutine muutuja päringuga“ (Replace Temp with Query refactoring ) lahendab selle probleemi otse: arvutatud väärtuse salvestamise asemel kohalikku muutujasse ekstraheeritakse arvutus nimega meetodisse ehk päringusse ja kutsutakse seda välja kõikjal, kus väärtust vaja on.

Tulemuseks on kood, mis edastab kavatsust selle varjamise asemel. Arvutus ei ole enam maetud muutuja omistamisse pika meetodi alguses; sellel on nimi, asukoht ja võimalus seda eraldi testida. See artikkel käsitleb kogu tehnikat: mis on ajutised muutujad, millal neist saavad probleemid, kuidas refaktoreerimist samm-sammult Javas, Pythonis ja TypeScriptis teostada, millal seda rakendada ja millal mitte ning kuidas see on seotud refaktorimiskataloogis leiduvate seotud tehnikatega.

Refaktoreeri oma koodi enesekindlalt

SMART TS XL jäljed, kus kasutatakse ajutisi muutujaid, dubleeritud arvutusi ja ekstraheeritud päringumeetodeid.

Lisateavet

Mis on ajutine muutuja (Temp) programmeerimises?

Ajutine muutuja, mida tavaliselt nimetatakse temp-iks , on funktsiooni või meetodi sees olev lokaalne muutuja, mis salvestab vahetulemuse sama ulatuse piires kasutamiseks. See arvutatakse üks kord, hoitakse nimega muutujas ja seejärel viidatakse sellele hiljem samas funktsioonis. Muutuja eksisteerib ainult funktsioonikutse eluea jooksul; see pole väljaspool seda ligipääsetav ja seda ei salvestata objekti olekus.

püüton

# 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

Java

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

masinakiri

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

Ajutised väärtused ei ole oma olemuselt halvad. Neil on õigustatud kasutusvõimalused: kalli operatsiooni tulemuse jäädvustamine, mille kordamine oleks raiskav, keerulise mitmeastmelise arvutuse jagamine loetavateks etappideks või tsükli iteratsioonide jooksul akumuleeruvate väärtuste hoidmine. Probleem tekib siis, kui ajutisi väärtusi kasutatakse refleksiivselt lihtsate tuletatud väärtuste jaoks, mis oleksid nimetatud meetoditena selgemad, või kui need akumuleeruvad pika meetodi jooksul ja sunnivad lugejaid jälgima mitut samaaegselt aktiivset vaheväärtust.

Mis on refaktoreerimine tarkvaratehnikas?

Refaktoreerimine on olemasoleva koodi ümberstruktureerimise protsess ilma selle jälgitavat käitumist muutmata. Eesmärk on parandada koodi sisemist kvaliteeti: selle loetavust, testitavust, hooldatavust ja modulaarsust. Refaktoreerimine ei lisa funktsioone ega paranda vigu; see muudab koodi struktuuri, säilitades samal ajal selle funktsioonid.

„Asenda temp päringuga” on üks refaktoriseerimise tehnikatest Martin Fowleri kirjeldatud mitmekümne tehnika kataloogis. See kuulub tehnikate perekonda, mis tegelevad liiga pikaks või liiga keeruliseks kasvanud meetoditega:

Refaktoreerimise tehnikaMida see teeb
Asenda „Ajutine” sõnaga „Päringu”Ekstraheerib ajutise muutuja arvutuse nimetatud meetodiks.
Ekstraheerimise meetodEkstraheerib koodiploki uude nimega meetodisse
Sisseehitatud temperatuurAsendab lihtsa temp-i otse selle avaldisega
Ajutise muutuja jagamineEraldab erinevatel eesmärkidel taaskasutatava temperatuuri erinevateks muutujateks
Asenda silmus torujuhtmegaAsendab imperatiivse tsükli funktsionaalse torujuhtmega (kaardistamine, filtreerimine, vähendamine)
Selgitava muutuja tutvustamineTutvustab nimega temp-funktsiooni keerulise avaldise selgitamiseks

Neid tehnikaid ei kasutata isoleeritult. Fowler kirjeldab ajutise asendamist päringuga (Replace Temp with Query) kui olulist sammu enne ekstraheerimismeetodit (Extract Method): kui meetodil on ajutised muutujad, muutub selle osa uude meetodisse ekstraheerimine keeruliseks, kuna neid ajutisi muutujaid võidakse kasutada nii enne kui ka pärast ekstraheeritud osa. Ajutiste muutujate esmane kõrvaldamine päringuteks muutmise teel vabastab tee ekstraheerimiseks.

Mis on ajutise asendamine päringuga?

„Asenda ajutine muutuja päringuga” on ümberfaktorimistehnika, mis teisendab lokaalse ajutise muutuja meetodikutseks. Väärtuse arvutamise ja selle lokaalsele muutujale määramise asemel ekstraheeritakse arvutus privaatsesse meetodisse ehk päringusse (query), mis kutsumisel tagastab arvutatud väärtuse. Kutsudes ajutist muutujat (temp), asendatakse viide alati päringumeetodi kutsumisega.

Kanooniline näide Fowleri refaktoringust :

Enne:

Java

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

Pärast:

Java

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

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

Päringu meetod basePrice() on nüüd nimega, iseseisev arvutus. Seda saab kutsuda klassi mis tahes muust meetodist, testida iseseisvalt, alamklassides üle kirjutada ja mõista ilma kutsuvat meetodit eelnevalt lugemata.

Ajutiste muutujate probleem

Nad killustavad loogikat meetodi ulatuses

Ajutine muutuja jagab arvutuse kaheks eraldi osaks: omistamine (kus väärtus arvutatakse) ja kasutamine (kus seda loetakse). Lühikese meetodi puhul on see jaotus kahjutu. Meetodis, mis on kasvanud kolmekümne või viiekümne rea pikkuseks, võivad omistamine ja kasutamine olla eraldatud paljude teiste loogikaliinidega. Lugeja peab omistamise leidmiseks üles kerima, tähenduse töömälus hoidma ja kasutuseni tagasi kerima. Iga täiendav ajutine muutuja suurendab seda kognitiivset koormust.

Nad blokeerivad ekstraktsioonimeetodi

Kõige olulisem praktiline probleem temp-muutujatega on see, et need blokeerivad teisi refaktoriseerimisi. Vaatleme meetodit keerulise tingimusliku haruga, millel oleks kasulik omaette meetodisse ekstraheerimine. Kui haru kasutab meetodis varem määratud temp-muutujat, nõuab ekstraheerimine kas temp-muutuja edastamist parameetrina, temp-muutuja muutmist eksemplari muutujaks või selle väärtuse uuesti arvutamist ekstraheeritud meetodi sees. Ükski neist valikutest pole puhas. Temp-muutuja esmane kõrvaldamine päringuga asendades kõrvaldab selle takistuse täielikult.

Nad kutsuvad esile taaskasutamist ja muteerimist

Ajutisi muutujaid kasutatakse sama meetodi piires mõnikord erinevatel eesmärkidel, seda nimetab Fowler „ajutiste muutujate sasipuntraks“. Muutuja nimega temp or result mis on mitu korda ümber määratud, ei anna semantilist teavet ja eksitab lugejaid aktiivselt selle kohta, mida see igal ajahetkel esindab. Isegi ühe eesmärgiga ajutised väärtused võivad kuhjuda, kuni meetodi ulatus on täis vaheväärtusi, mida lugejad peavad samaaegselt jälgima.

Samm-sammult: kuidas rakendada funktsiooni „Asenda ajutine päringuga”

Teisendus läbib neli sammu, mida saab ohutult rakendada mis tahes keeles:

1. samm: Veenduge, et ajutine muutuja määratakse täpselt üks kord ja seda ei muudeta kunagi. Kui ajutine muutuja määratakse meetodis hiljem uuesti, jagage see kõigepealt funktsiooniga „Split Temporary Variable” (Ajutise muutuja jagamine).

2. samm: Ekstraheeri omistamise parem pool privaatsesse meetodisse. Anna meetodile nimi, mis kirjeldab, mida see arvutab, mitte kuidas. basePrice() on parem kui calculateQuantityTimesPrice().

3. samm: Asendage iga viide temp-meetodile uue meetodi kutsumisega. Enamik IDE-sid suudab seda automaatselt teha: paremklõpsake temp-meetodil → Refactor → Inline Variable ja seejärel Extract Method reasisesel avaldisel.

4. samm: kustutage muutuja „temp” deklaratsioon. Kui ekstraheerimine on lõppenud, ei tohiks muutujal „temp” enam viiteid olla ja selle saab eemaldada.

Java: Täielikult toimiv näide

Java

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

Java

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

Meetod getPrice() loetakse nüüd ühe avaldisena, mis edastab arvutuse selgelt. Iga ekstraheeritud päringut saab lugeda, testida ja laiendada eraldi. Pange tähele, et discountFactor() kõned basePrice(), see on õige, sest basePrice() on puhas arvutus ilma kõrvalmõjudeta, seega selle kaks korda kutsumine ei too kaasa mingit riski.

Python: Asenda Temp atribuudiga Property

Pythonis on päringumeetodi loomulik vaste a @property, mis võimaldab meetodit kutsuda ilma sulgudeta ja loeb identselt atribuudiga access:

püüton

# 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

püüton

# 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

Kasutamine @property vahendid self.base_price loeb identselt eksemplari muutujaga, muutes kutsuva koodi self.base_price * self.discount_factor täiesti loomulik. Iga omadust saab iseseisvalt testida:

püüton

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

Selline testitavuse tase on tempopõhise versiooni puhul võimatu: sisemised arvutused base_price ja discount_factor ei ole meetodiväliselt ligipääsetavad.

TypeScript: päringumeetodid ja hankijad

TypeScript toetab nii meetodipõhiseid päringuid kui ka omaduste hankijaid, mis vastavad vastavalt Java ja Pythoni mustritele:

masinakiri

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

masinakiri

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

Päringumeetodite nimetamine hästi

Päringumeetodi nimi teeb ära kõige olulisema töö. Halvasti nimetatud ekstraktsioon on halvem kui ajutine meetod, mille see asendas, sest see loob läbipaistmatu kaudsuse: kutsujad peavad meetodi toimimise mõistmiseks navigeerima meetodi definitsioonini, mis nurjab selle eesmärgi.

Head päringumeetodite nimed järgivad järgmisi põhimõtteid:

Nimeta, mida see esindab, mitte kuidas seda arvutatakse. basePrice() edastab ärikontseptsiooni. getQuantityTimesItemPrice() kirjeldab arvutust, mitte mõistet. Eristamine on oluline siis, kui arvutus muutub, mõiste nimi basePrice() jääb stabiilseks isegi siis, kui valem muutub.

Kasutage väärtuste jaoks nimisõnafraase. Päringumeetodid tagastavad väärtusi; need ei ole käsud. discountFactor(), totalAmount(), isEligible() järgi tagastatava nimetamise konventsiooni. calculateDiscount(), processAmount() järgige käskude imperatiivset konventsiooni, mis on segadusttekitav meetodite puhul, mis ainult arvutavad ja tagastavad.

Boole'i ​​päringuid tuleks lugeda küsimustena. isHighValue(), hasDiscount(), meetsThreshold() andke teada, et vastuseks on tõeväärtus ja et helistaja esitab jah/ei küsimuse. bool 変数名 (tõeväärtusmuutujate nimetamine, päring otsingukonsooli andmetes) peegeldab täpselt seda muret: tõeväärtusmuutujad ja -meetodid vajavad nimesid, mis teevad nende tähenduse kasutushetkel selgeks.

Hoidke nimed seotud meetodite puhul stabiilsena. If basePrice() kasutab discountFactor(), nimetamise järjepidevus ütleb lugejatele, et discountFactor sõltub basePriceEbajärjekindel nimetamine rikub seda varjatud dokumentatsiooni.

Millal rakendada Asenda ajutine päring päringuga

Rakenda seda refaktoreerimist järgmistel juhtudel:

  • Ajutine väärtus määratakse täpselt üks kord ja seda ei määrata kunagi ümber.
  • Arvutus on puhas avaldis: see loeb väljadelt või parameetritelt, kuid ei muuda välist olekut, ei kutsu võrguteenuseid ega sõltu ajast ega juhuslikkusest.
  • Arvutus on piisavalt keeruline, et selle nimetamine parandaks loetavust, või piisavalt lihtne, et tempo on lihtsalt segadus.
  • Sa hakkad rakendama meetodit Extract plokile, mis kasutab temp-i.

Kõige levinum ideaalstsenaarium on tuletatud väärtus: hind, kogusumma, allahindlus, vormindatud string, tingimuslik klassifikatsioon. Need on väärtused, mis on täielikult tuletatud objekti väljadest, ilma kõrvalmõjudeta ja mis kuuluvad loomulikult objekti omaduste hulka, mitte meetodi vahearvutuste hulka.

Millal mitte rakendada Asenda ajutine funktsioon päringuga

Jõudlustundlikud operatsioonid. Kui arvutus on kulukas, näiteks andmebaasipäring, võrgukõne, O(n²) tsükkel, siis päringumeetodi kaks korda kutsumine toob kaasa kahekordse kulu. Ajutine meetod on olemas just selle vältimiseks. Sellistel juhtudel tuleks kas ajutine meetod alles jätta või päringumeetod meelde jätta (tulemus vahemällu salvestada pärast esimest kutset):

püüton

# 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

Kõrvalmõjuga toimingud. Kui ajutine fail sisaldab toimingu tulemust, mis peaks käivituma ainult üks kord (unikaalse ID genereerimine, logimine, faili kirjutamine), siis selle päringuks muutmine käivitaks toimingu iga kutse korral. See muudab programmi käitumist, mitte ainult selle struktuuri. Ärge rakendage seda refaktoreerimist kõrvalmõjuga ajutistele failidele.

Ajutised väärtused, mis akumuleeruvad tsükli iteratsioonide jooksul. Temperatuur, mis on akumulaator a-s for loop, total += item.price, ei ole sobiv kandidaat käsule „Replace Temp with Query”. See ei ole tuletatud väärtus; see on olek, mis koguneb iteratsioonide jooksul. Kui probleemiks on tsükkel, kaaluge hoopis käsku „Replace Loop with Pipeline”.

Seotud refaktorimistehnikad

Asenda Temp päringuga (Replace Temp with Query) kuulub tehnikate perekonda, mis üheskoos kõrvaldavad meetodite tarbetu keerukuse. Selle perekonna mõistmine aitab arendajatel valida probleemi lahendamiseks õige tehnika:

Meetod „Extract” on kõige levinum kaaslane. „Asendades meetodi „Temp” meetodiga „Query”, saab meetodi „Extract” sageli võimalikuks, kustutades muutujad, mis muidu nõuaksid ebamugavat parameetrite edastamist ekstraheeritud osa ja ülejäänud meetodi vahel.

Inline Temp on temp-avaldise lisamise vastupidine: see asendab temp-avaldise selle avaldisega otse koodis. Kasutage Inline Temp-avaldist siis, kui temp-avaldis ei lisa selgust ja selle avaldis on juba loetav.

Funktsiooni „Ajutise muutuja jagamine” rakendatakse siis, kui ühte ajutist muutujat kasutatakse samas meetodis mitmel otstarbel. Jagage see eraldi muutujateks, mille nimed kajastavad iga eesmärki, ja seejärel rakendage saadud ühekordselt kasutatavatele ajutistele muutujatele funktsiooni „Asenda ajutine muutuja päringuga”.

Selitava muutuja kasutuselevõtt on vastupidine: kui keerulist avaldist on raske lugeda, võib kirjeldava nimega ajutise muutuja lisamine selgust parandada. See tehnika ja „Asenda ajutine muutuja päringuga” on vastuolus ning arendaja peab otsustama, milline suund kõnealust konkreetset koodi parandab.

Asenda silmus torujuhtmega käsitleb levinud mustrit, kus tempoakumulaatoriga tsükli saab asendada aheldatud torujuhtme operatsiooniga (map, filter, reduce), mis on deklaratiivsem ja hõlpsamini loetav.

Kuidas SMART TS XL Toetab ulatuslikku refaktoreerimist

Asenda Temp päringuga on lokaalne refaktoreerimine: see teisendab ühe muutuja ühes meetodis. Märkimisväärse suurusega koodibaasis ei ole kasulikum küsimus mitte "kuidas ma seda refaktoreerimist rakendan?", vaid "kus kogu koodibaasis peaksin ma seda rakendama ja mida see mõjutab?".

SMART TS XL pakub koodibaasidevahelist struktuurianalüüsi, mis muudab sellele küsimusele vastamise süstemaatiliseks. See tuvastab, kus sama arvutust tehakse ajutise muutujana mitmes kohas – muster, mis asendab ajutise päringuga (Replace Temp with Query), on loodud koondama üheks nimega päringumeetodiks. See jälgib, kuidas refaktoreeritud päringumeetodit pärast selle ekstraheerimist kasutatakse, muutes refaktoreerimise ulatuse nähtavaks enne selle tegemist. Ja see töötab eri keeltes: ettevõttesüsteemides, kus COBOL-programmid, Java-teenused ja Pythoni torujuhtmed töötavad kõik samade andmetega, staatilise koodi analüüs ja mõju analüüs tuvastada, kus sama loogiline arvutus esineb eri keeltes erineval kujul – see on probleemi sügavam vorm, mida „Replace Temp with Query” käsitleb ühe keele tasandil.

Meeskondadele, kes töötavad pärand moderniseerimine, SMART TS XL'S sõltuvuse visualiseerimine võimaldab enne ümberkujundatud komponentide muutmist näha, kus neid kasutatakse, tagades, et arvutuse päringumeetodisse eraldamine ei riku algset struktuuri eeldanud kutsujaid.

Ajutised muutujad ja ise dokumenteeriv kood

Otsus asendada ajutine muutuja päringuga on lõppkokkuvõttes otsus selle kohta, mida kood peaks edastama. Ajutised muutujad edastavad implementatsiooni: selle väärtuse saamiseks tegin selle arvutuse. Päringumeetodid edastavad domeeni: seda see väärtus tähendab. Order-klassis basePrice() annab lugejale teada, et see kontseptsioon valdkonnas eksisteerib. double x = quantity * itemPrice räägib lugejale aritmeetilisest tehtest.

Koodi arenedes vajavad valdkonnakontseptsioonid stabiilseid kodusid. Ajutisse meetodisse (temp) manustatud arvutus võib muutuda, seda saab dubleerida mitmes meetodis või järgmine arendaja, kes seda loeb, võib sellest valesti aru saada. Nimega päringumeetodit saab tahtlikult leida, testida, dokumenteerida ja arendada. See stabiilsus kõigis kohtades, kus arvutust vaja on, ja kõigi arendajate seas, kes sellega töötavad, teebki meetodist „Asenda ajutine päring” midagi enamat kui lihtsalt süntaksimuudatuse. See on otsus selle kohta, kuidas koodibaas edastab lahendatavat probleemi.