Zastąp Temp zapytaniem: technika refaktoryzacji

Zastąp Temp zapytaniem: technika refaktoryzacji dla czystszego i bardziej testowalnego kodu

Zmienne tymczasowe należą do najczęstszych źródeł niepotrzebnej złożoności kodu oprogramowania. Kumulują się w długich metodach, nadają niejasne nazwy wartościom obliczeniowym i utrudniają wyodrębnianie, testowanie lub ponowne wykorzystywanie zawartej w nich logiki. Refaktoryzacja „Zastąp zmienną tymczasową zapytaniem”, skatalogowana przez Martina Fowlera w książce „ Refactoring: Improving the Design of Existing Code” , rozwiązuje ten problem bezpośrednio: zamiast przechowywać wartość obliczeniową w zmiennej lokalnej, wyodrębnia się obliczenie do nazwanej metody, zapytania, i wywołuje je wszędzie tam, gdzie wartość jest potrzebna.

Rezultatem jest kod, który komunikuje intencję, zamiast ją ukrywać. Obliczenia nie są już ukryte w przypisaniu zmiennej na początku długiej metody; mają nazwę, lokalizację i możliwość testowania w izolacji. Ten artykuł omawia całą technikę, czym są zmienne tymczasowe, kiedy stają się problemami, jak krok po kroku przeprowadzić refaktoryzację w Javie, Pythonie i TypeScript, kiedy ją stosować, a kiedy nie, oraz jak łączy się ona z powiązanymi technikami w katalogu refaktoryzacji.

Refaktoryzuj swój kod pewnie

SMART TS XL ślady, w których wykorzystano zmienne tymczasowe, zduplikowane obliczenia i wyodrębnione metody zapytań.

Ucz się więcej

Czym jest zmienna tymczasowa (Temp) w programowaniu?

Zmienna tymczasowa, powszechnie nazywana temp , to zmienna lokalna wewnątrz funkcji lub metody, która przechowuje wynik pośredni do wykorzystania w tym samym zakresie. Jest ona obliczana jednorazowo, przechowywana w zmiennej nazwanej, a następnie odwoływana później w tej samej funkcji. Zmienna istnieje tylko przez czas trwania wywołania funkcji; nie jest dostępna poza nią i nie jest przechowywana w stanie obiektu.

pyton

# 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

Jawa

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

maszynopis

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

Zmienne tymczasowe same w sobie nie są złe. Mają uzasadnione zastosowania: przechwytywanie wyniku kosztownej operacji, której powtarzanie byłoby nieekonomiczne, rozbijanie złożonych, wieloetapowych obliczeń na czytelne etapy lub przechowywanie wartości kumulujących się w kolejnych iteracjach pętli. Problem pojawia się, gdy zmienne tymczasowe są używane zwrotnie do prostych wartości pochodnych, które byłyby bardziej przejrzyste jako metody nazwane, lub gdy kumulują się w długiej metodzie i zmuszają czytelników do śledzenia wielu jednocześnie aktywnych wartości pośrednich.

Czym jest refaktoryzacja w inżynierii oprogramowania?

Refaktoryzacja to proces restrukturyzacji istniejącego kodu bez zmiany jego obserwowalnego zachowania. Celem jest poprawa wewnętrznej jakości kodu: jego czytelności, testowalności, łatwości utrzymania i modułowości. Refaktoryzacja nie dodaje funkcjonalności ani nie naprawia błędów; zmienia strukturę kodu, zachowując jego funkcjonalność.

Zastąpienie Temp zapytaniem to jedna z refaktoryzacji w katalogu kilkudziesięciu technik opisanych przez Martina Fowlera. Należy ona do rodziny technik, które zajmują się metodami, które stały się zbyt długie lub zbyt złożone:

Technika refaktoryzacjiCo to robi
Zamień Temp na QueryWyodrębnia obliczenia zmiennej tymczasowej do nazwanej metody
Metoda ekstrakcjiWyodrębnia blok kodu do nowej nazwanej metody
Temperatura w liniiZastępuje bezpośrednio prosty element tymczasowy jego wyrażeniem
Podziel zmienną tymczasowąOddziela dane tymczasowe, które są ponownie wykorzystywane do różnych celów, na osobne zmienne
Zamień pętlę na potokZastępuje pętlę imperatywną potokiem funkcjonalnym (mapowanie, filtrowanie, redukcja)
Wprowadzenie zmiennej objaśniającejWprowadza nazwaną zmienną tymczasową w celu wyjaśnienia złożonego wyrażenia

Techniki te nie są stosowane w izolacji. Fowler opisuje „Zastąp zmienną tymczasową zapytaniem” jako kluczowy krok przed ekstrakcją metody: jeśli metoda zawiera zmienne tymczasowe, wyodrębnienie jej części do nowej metody staje się trudne, ponieważ zmienne tymczasowe mogą być używane zarówno przed, jak i po wyodrębnionej sekcji. Wyeliminowanie zmiennych tymczasowych poprzez przekształcenie ich w zapytania otwiera drogę do ekstrakcji.

Czym jest polecenie „Zastąp temp. zapytaniem”?

Zamień zmienną tymczasową na zapytanie to technika refaktoryzacji, która przekształca lokalną zmienną tymczasową w wywołanie metody. Zamiast obliczać wartość i przypisywać ją do zmiennej lokalnej, wyodrębniasz obliczenia do prywatnej metody – zapytania – która po wywołaniu zwraca obliczoną wartość. W każdym przypadku użycia zmiennej tymczasowej, referencję zastępujesz wywołaniem metody zapytania.

Kanoniczny przykład z książki Fowlera „ Refactoring ”:

Przed:

Jawa

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

Po:

Jawa

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

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

Metoda zapytania basePrice() jest teraz nazwanym, samodzielnym obliczeniem. Można je wywołać z dowolnej innej metody w klasie, przetestować niezależnie, nadpisać w podklasach i zrozumieć bez wcześniejszego odczytania metody wywołującej.

Problem ze zmiennymi tymczasowymi

Fragmentują logikę w obrębie metody

Zmienna tymczasowa dzieli obliczenie na dwie oddzielne części: przypisanie (gdzie wartość jest obliczana) i użycie (gdzie jest odczytywana). W krótkiej metodzie ten podział jest nieszkodliwy. W metodzie, która rozrosła się do trzydziestu lub pięćdziesięciu wierszy, przypisanie i użycie mogą być rozdzielone wieloma wierszami innej logiki. Czytelnik musi przewinąć w górę, aby znaleźć przypisanie, zapisać znaczenie w pamięci roboczej i przewinąć z powrotem do użycia. Każda dodatkowa zmienna tymczasowa zwiększa ten narzut poznawczy.

Metoda ekstrakcji bloku

Najpoważniejszym praktycznym problemem związanym z zmienną tymczasową jest to, że blokuje ona inne refaktoryzacje. Rozważmy metodę ze złożoną gałęzią warunkową, która mogłaby skorzystać na wyodrębnieniu do własnej metody. Jeśli gałąź używa zmiennej tymczasowej, która została przypisana wcześniej w metodzie, wyodrębnienie wymaga albo przekazania zmiennej tymczasowej jako parametru, uczynienia zmiennej tymczasowej zmienną instancji, albo ponownego obliczenia jej wartości w wyodrębnionej metodzie. Żadna z tych opcji nie jest prosta. Wyeliminowanie zmiennej tymczasowej poprzez zastąpienie jej zapytaniem całkowicie usuwa tę przeszkodę.

Zapraszają do ponownego użycia i mutacji

Zmienne tymczasowe są czasami ponownie wykorzystywane do różnych celów w ramach tej samej metody. Fowler nazywa to „plątaniną zmiennych tymczasowych”. Zmienna o nazwie temp or result Wielokrotnie przypisywane wartości nie dostarczają żadnych informacji semantycznych i celowo wprowadzają czytelników w błąd co do tego, co reprezentują w danym momencie. Nawet wartości tymczasowe o pojedynczym przeznaczeniu mogą się kumulować, aż zakres metody zostanie zaśmiecony wartościami pośrednimi, które czytelnicy muszą śledzić jednocześnie.

Krok po kroku: Jak zastosować opcję „Zamień Temp” na zapytanie

Transformacja składa się z czterech kroków, które można bezpiecznie zastosować w dowolnym języku:

Krok 1: Upewnij się, że zmienna tymczasowa została przypisana dokładnie raz i nigdy nie uległa mutacji. Jeśli zmienna tymczasowa zostanie ponownie przypisana później w metodzie, najpierw podziel ją za pomocą opcji „Podziel zmienną tymczasową”.

Krok 2: Wyodrębnij prawą stronę zadania do metody prywatnej. Nadaj metodzie nazwę opisującą, co oblicza, a nie w jaki sposób. basePrice() jest lepszy niż calculateQuantityTimesPrice().

Krok 3: Zastąp każde odwołanie do zmiennej temp wywołaniem nowej metody. Większość środowisk IDE może to zrobić automatycznie: kliknij prawym przyciskiem myszy zmienną temp → Refaktoryzuj → Zmienna wbudowana, a następnie Wyodrębnij metodę w wyrażeniu wbudowanym.

Krok 4: Usuń deklarację zmiennej tymczasowej. Jeśli ekstrakcja została zakończona, zmienna tymczasowa nie powinna już zawierać żadnych odniesień i można ją usunąć.

Java: Pełny przykład działania

Jawa

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

Jawa

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

Sposób getPrice() Teraz odczytuje się jako pojedyncze wyrażenie, które jasno komunikuje obliczenia. Każde wyodrębnione zapytanie można odczytać, przetestować i rozszerzyć niezależnie. Należy pamiętać, że discountFactor() Połączenia basePrice(), to jest poprawne, ponieważ basePrice() jest czystym obliczeniem bez efektów ubocznych, więc jego dwukrotne wywołanie nie niesie ze sobą żadnego ryzyka.

Python: Zamień Temp na Property

W Pythonie naturalnym odpowiednikiem metody zapytania jest @property, która pozwala na wywołanie metody bez nawiasów i odczytuje ją identycznie jak dostęp do atrybutu:

pyton

# 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

pyton

# 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

Korzystanie z @property znaczy self.base_price odczytuje się identycznie jak zmienna instancji, co powoduje, że kod wywołujący self.base_price * self.discount_factor całkowicie naturalne. Każda właściwość jest niezależnie testowana:

pyton

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

Ten poziom testowalności jest niemożliwy w przypadku wersji opartej na danych tymczasowych: obliczenia wewnętrzne base_price oraz discount_factor nie są dostępne spoza metody.

TypeScript: metody zapytań i gettery

TypeScript obsługuje zarówno zapytania oparte na metodach, jak i metody pobierania właściwości, co jest zgodne ze wzorcami dostępnymi odpowiednio w językach Java i Python:

maszynopis

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

maszynopis

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

Dobre nazewnictwo metod zapytań

Nazwa metody zapytania wykonuje najważniejszą pracę. Źle nazwana ekstrakcja jest gorsza niż zastępowana przez nią metoda tymczasowa, ponieważ tworzy nieprzejrzyste przekierowanie: osoby wywołujące muszą przejść do definicji metody, aby zrozumieć jej działanie, co niweczy jej cel.

Dobre nazwy metod zapytań spełniają następujące zasady:

Podaj nazwę tego, co to przedstawia, a nie sposobu, w jaki jest obliczane. basePrice() komunikuje koncepcję biznesową. getQuantityTimesItemPrice() Opisuje obliczenie, a nie koncepcję. Rozróżnienie to ma znaczenie, gdy zmienia się obliczenie, nazwa koncepcji basePrice() pozostaje stabilny nawet jeśli zmieni się formuła.

Użyj fraz rzeczownikowych do określenia wartości. Metody zapytań zwracają wartości; nie są poleceniami. discountFactor(), totalAmount(), isEligible() postępuj zgodnie z konwencją nazywania zwracanych informacji. calculateDiscount(), processAmount() stosować konwencję imperatywną poleceń, co może być mylące w przypadku metod, które wyłącznie wykonują obliczenia i zwracają wyniki.

Zapytania boolowskie należy odczytywać jako pytania. isHighValue(), hasDiscount(), meetsThreshold() przekazać, że zwracana wartość jest wartością logiczną i że osoba wywołująca zadaje pytanie z odpowiedzią tak/nie. bool 変数名 (nazewnictwo zmiennych logicznych, zapytanie w danych Search Console) odzwierciedla właśnie tę obawę: zmienne i metody logiczne wymagają nazw, które jasno wyjaśniają ich znaczenie w momencie użycia.

Zachowaj stabilność nazw w powiązanych metodach. If basePrice() jest używany przez discountFactor()spójność nazewnictwa mówi czytelnikom, że discountFactor zależy basePriceNiespójne nazewnictwo psuje tę dorozumianą dokumentację.

Kiedy zastosować Zamień Temp na Zapytanie

Zastosuj tę refaktoryzację, gdy:

  • Praca tymczasowa jest przypisywana tylko raz i nigdy nie jest ponownie przypisywana
  • Obliczenia są czystym wyrażeniem: odczytują dane z pól lub parametrów, ale nie modyfikują stanu zewnętrznego, nie wywołują usług sieciowych ani nie zależą od czasu lub losowości
  • Obliczenia są na tyle skomplikowane, że nadanie im nazwy ułatwiłoby ich odczytanie, lub na tyle proste, że wartość temp. jest tylko bałaganem
  • Zamierzasz zastosować metodę Extract do bloku, który używa temp.

Najczęstszym idealnym scenariuszem jest wartość pochodna: cena, suma, rabat, sformatowany ciąg znaków, klasyfikacja warunkowa. Są to wartości pochodzące wyłącznie z pól obiektu, bez efektów ubocznych, które naturalnie należą do właściwości obiektu, a nie do pośrednich obliczeń wewnątrz metody.

Kiedy nie stosować zastąpienia Temp zapytaniem

Operacje wrażliwe na wydajność. Jeśli obliczenia są kosztowne, np. zapytanie do bazy danych, wywołanie sieciowe, pętla O(n²), dwukrotne wywołanie metody zapytania generuje podwójny koszt. Zmienna tymczasowa istnieje właśnie po to, aby tego uniknąć. W takich przypadkach należy pozostawić zmienną tymczasową lub zapamiętać metodę zapytania (zapisać wynik w pamięci podręcznej po pierwszym wywołaniu):

pyton

# 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

Operacje uboczne. Jeśli w pamięci tymczasowej znajduje się wynik operacji, która powinna zostać wykonana tylko raz (generowanie unikalnego identyfikatora, rejestrowanie, zapis do pliku), przekształcenie jej w zapytanie spowoduje uruchomienie operacji przy każdym wywołaniu. Zmienia to zachowanie programu, a nie tylko jego strukturę. Nie należy stosować tej refaktoryzacji do pamięci tymczasowej powodującej skutki uboczne.

Wartości tymczasowe kumulujące się podczas iteracji pętli. Temperatura, która jest akumulatorem w for pętla, total += item.price, nie jest kandydatem do Zastąpienia Temp Zapytaniem. Nie jest to wartość pochodna; to stan, który kumuluje się w kolejnych iteracjach. Rozważ Zastąpienie Pętli Potokiem, jeśli problem leży w pętli.

Powiązane techniki refaktoryzacji

Zastąpienie metody Temp metodą Query należy do rodziny technik, które wspólnie eliminują niepotrzebną złożoność metod. Zrozumienie tej rodziny pomaga programistom wybrać odpowiednią technikę do danego problemu:

Metoda Ekstrakcji jest najczęściej spotykanym dodatkiem. Zastąpienie zmiennej Temp przez Query często umożliwia zastosowanie metody Ekstrakcji poprzez wyczyszczenie zmiennych, które w przeciwnym razie wymagałyby niewygodnego przekazywania parametrów między wyodrębnioną częścią a resztą metody.

Inline Temp to odwrotność wprowadzania temp: zastępuje temp jego wyrażeniem bezpośrednio w kodzie. Użyj Inline Temp, gdy temp nie wprowadza żadnej przejrzystości, a jego wyrażenie jest już czytelne.

Funkcja „Podziel zmienną tymczasową” ma zastosowanie, gdy pojedyncza zmienna tymczasowa jest ponownie wykorzystywana do wielu celów tą samą metodą. Podziel ją na osobne zmienne o nazwach odzwierciedlających każdy cel, a następnie zastosuj opcję „Zamień zmienną tymczasową na zapytanie” do dowolnej z powstałych jednorazowych zmiennych tymczasowych.

Wprowadzenie zmiennej objaśniającej działa w przeciwnym kierunku: jeśli złożone wyrażenie jest trudne do odczytania, wprowadzenie zmiennej tymczasowej o opisowej nazwie może poprawić przejrzystość. Ta technika i zastąpienie zmiennej tymczasowej zapytaniem są ze sobą sprzeczne, a programista musi ocenić, który kierunek poprawi konkretny kod.

Zamień pętlę na potok dotyczy powszechnego wzorca, w którym pętlę z akumulatorem tymczasowym można zastąpić operacją łańcuchową potoku (map, filter, reduce), który jest bardziej deklaratywny i łatwiejszy do odczytania.

W jaki sposób SMART TS XL Obsługuje refaktoryzację na dużą skalę

Zamień Temp na Query to lokalne refaktoryzowanie: transformuje jedną zmienną w jednej metodzie. W przypadku bazy kodu o znacznych rozmiarach, bardziej przydatne pytanie brzmi nie „jak zastosować to refaktoryzowanie?”, ale „gdzie w całej bazie kodu powinienem je zastosować i na co wpłynie moje działanie?”.

SMART TS XL Zapewnia analizę strukturalną obejmującą wiele baz kodu, która pozwala na systematyczne udzielenie odpowiedzi na to pytanie. Identyfikuje ona miejsca, w których to samo obliczenie jest wykonywane jako zmienna tymczasowa w wielu lokalizacjach – wzorzec, który funkcja „Zamień na zapytanie” ma na celu konsolidację w jedną nazwaną metodę zapytania. Śledzi sposób użycia zrefaktoryzowanej metody zapytania po jej wyodrębnieniu, dzięki czemu zakres refaktoryzacji jest widoczny przed jej wykonaniem. Działa również w różnych językach: w systemach korporacyjnych, w których programy COBOL, usługi Java i potoki Pythona działają na tych samych danych. statyczna analiza kodu oraz analiza wpływu zidentyfikować miejsca, w których to samo obliczenie logiczne pojawia się w różnych formach w różnych językach, głębszą formę problemu, który Zamień Temp na Zapytanie rozwiązuje na poziomie pojedynczego języka.

Dla zespołów pracujących nad modernizacja dziedziczna, SMART TS XL'S wizualizacja zależności umożliwia zobaczenie, gdzie wykorzystano przebudowane komponenty przed ich zmianą, zapewniając, że wyodrębnienie obliczeń do metody zapytania nie zepsuje wywołujących, którzy oczekiwali oryginalnej struktury.

Zmienne tymczasowe i kod samodokumentujący

Decyzja o zastąpieniu zmiennej tymczasowej zapytaniem jest ostatecznie decyzją o tym, co kod powinien komunikować. Zmienne tymczasowe komunikują implementację: to jest obliczenie, które wykonałem, aby uzyskać tę wartość. Metody zapytań komunikują domenę: to jest znaczenie tej wartości. W klasie Order, basePrice() informuje czytelnika, że ​​ta koncepcja istnieje w danej domenie. double x = quantity * itemPrice informuje czytelnika o działaniu arytmetycznym.

W miarę ewolucji kodu, koncepcje domenowe potrzebują stabilnych siedzib. Obliczenia osadzone w danych tymczasowych mogą ulec zmianie, zostać zduplikowane w wielu metodach lub niezrozumiane przez kolejnego programistę, który je przeczyta. Nazwaną metodę zapytania można znaleźć, przetestować, udokumentować i celowo rozwijać. Ta stabilność, we wszystkich miejscach, w których potrzebne są obliczenia, i wśród wszystkich programistów, którzy będą z nią pracować, sprawia, że ​​polecenie „Zamień dane tymczasowe na zapytanie” jest czymś więcej niż tylko zmianą składni. To decyzja o tym, jak baza kodu komunikuje problem, który rozwiązuje.