Zmienne tymczasowe a wymagania dotyczące długości linii

10

Czytałem Refaktoryzację Martina Fowlera . Ogólnie jest znakomity, ale jedna z rekomendacji Fowlera wydaje się sprawiać trochę kłopotów.

Fowler zaleca zastąpienie zmiennych tymczasowych zapytaniem, więc zamiast tego:

double getPrice() {
    final int basePrice = _quantity * _itemPrice;
    final double discountFactor;
    if (basePrice > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice * discountFactor;
}

wybierasz metodę pomocnika:

double basePrice() {
    return _quantity * _itemPrice;
}

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice() * discountFactor;
}

Ogólnie zgadzam się, z tym wyjątkiem, że jednym z powodów, dla których używam zmiennych tymczasowych jest to, że linia jest zbyt długa. Na przykład:

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

Gdybym spróbował to wpisać, linia byłaby dłuższa niż 80 znaków.

Alternatywnie otrzymuję łańcuchy kodów, które same nie są o wiele łatwiejsze do odczytania:

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

Jakie są strategie pogodzenia tych dwóch kwestii?

Kevin Burke
źródło
10
80 znaków to około 1/3 mojego 1 monitora. czy jesteś pewien, że trzymanie się linii 80 znaków nadal jest dla Ciebie opłacalne?
jk.
10
tak, patrz na przykład programmers.stackexchange.com/questions/604/…
Kevin Burke
Twój $hosti $uriprzykład jest w pewnym sensie wymyślony - chyba że host był odczytywany z ustawienia lub innych danych wejściowych, wolałbym, aby znajdowały się w tej samej linii, nawet jeśli zawija się lub nie wychodzi.
Izkata
5
Nie trzeba być tak dogmatycznym. Książka jest listą technik, które można zastosować, gdy pomagają, a nie zestaw reguł, które musisz stosować wszędzie, za każdym razem. Chodzi o to, aby twój kod był łatwiejszy w utrzymaniu i łatwiejszy do odczytania. Jeśli refaktor tego nie zrobi, nie użyjesz go.
Sean McSomething
Chociaż uważam, że limit 80 znaków jest trochę nadmierny, podobny limit (100?) Jest rozsądny. Na przykład często lubię programować na monitorach zorientowanych portretowo, więc bardzo długie linie mogą być denerwujące (przynajmniej jeśli są wspólne).
Thomas Eding,

Odpowiedzi:

16

Jak
1. Istnieją ograniczenia długości linii, dzięki czemu można zobaczyć + zrozumieć więcej kodów. Nadal są ważne.
2. Podkreśl osąd w sprawie ślepej konwencji .
3. Unikaj zmiennych temp, chyba że optymalizujesz pod kątem wydajności .
4. Unikaj używania głębokich wcięć do wyrównywania w instrukcjach wieloliniowych.
5. Podziel długie instrukcje na wiele linii wzdłuż granic pomysłów :

// prefer this
var distance = Math.Sqrt(
    Math.Pow(point2.GetX() - point1.GetX(), 2) + // x's
    Math.Pow(point2.GetY() - point1.GetY(), 2)   // y's
);

// over this
var distance = Math.Sqrt(Math.Pow(point2.GetX() -
    point1.GetX(), 2) + Math.Pow(point2.GetY() -
    point1.GetY(), 2)); // not even sure if I typed that correctly.

Rozumowanie
Głównym źródłem moich (debugujących) problemów ze zmiennymi tymczasowymi jest ich zmienność. Mianowicie, założę, że są jedną wartością podczas pisania kodu, ale jeśli funkcja jest złożona, jakiś inny fragment kodu zmienia swój stan w połowie. (Lub odwrotnie, gdzie stan zmiennej pozostaje taki sam, ale wynik zapytania zmienił się).

Rozważ trzymanie się zapytań, chyba że optymalizujesz wydajność . Pozwala to zachować logikę użytą do obliczenia tej wartości w jednym miejscu.

Podane przykłady (Java i ... PHP?) Pozwalają na stosowanie instrukcji wieloliniowych. Jeśli linie stają się długie, rozbij je. Źródło jquery doprowadza to do skrajności. (Pierwsza instrukcja biegnie do wiersza 69!) Nie, że niekoniecznie się zgadzam, ale istnieją inne sposoby na uczynienie twojego kodu czytelnym niż używanie zmiennych tymczasowych.

Kilka przykładów
1. Przewodnik po stylu PEP 8 dla Pythona (nie najładniejszy przykład)
2. Paul M Jones w Przewodniku po stylu gruszkowym (środek argumentu drogowego)
3. Długość linii Oracle + konwencje owijania (przydatne strategie do przechowywania 80 znaków)
4. MDN Java Practices (podkreśla osąd programisty nad konwencją)

Zachary Yates
źródło
1
Inną częścią problemu jest to, że zmienna tymczasowa często przeżywa swoją wartość. Nie jest to problem w blokach o małym zasięgu, ale w większych, tak, duży problem.
Ross Patterson
8
Jeśli martwi Cię modyfikacja tymczasowa, ustaw na niej const.
Thomas Eding,
3

Myślę, że najlepszym argumentem za użyciem metod pomocniczych zamiast zmiennych tymczasowych jest czytelność dla ludzi. Jeśli jako człowiek masz więcej problemów z odczytaniem łańcucha metod pomocniczych niż tymczasowe varialbe, nie widzę powodu, dla którego powinieneś je wyciągać.

(Proszę popraw mnie jeżeli się mylę)

mhr
źródło
3

Nie sądzę, że musisz ściśle przestrzegać 80 znaków lub że zawsze należy wyodrębnić lokalną zmienną temp. Ale należy badać długie kolejki i lokalnych pracowników tymczasowych, aby uzyskać lepsze sposoby wyrażania tego samego pomysłu. Zasadniczo wskazują, że dana funkcja lub linia jest zbyt skomplikowana i musimy ją rozbić. Musimy jednak zachować ostrożność, ponieważ złe dzielenie zadania na części tylko komplikuje sytuację. Więc mam rozbić rzeczy na łatwe do zregenerowania i proste elementy.

Pozwól mi spojrzeć na zamieszczone przez ciebie przykłady.

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

Moje spostrzeżenie jest takie, że wszystkie wywołania interfejsu API twilio zaczynają się od „https://api.twilio.com/2010-04-1/”, a zatem istnieje bardzo oczywista funkcja wielokrotnego użytku:

$uri = twilioURL("Accounts/$accountSid/Usage/Records/AllTime")

W rzeczywistości uważam, że jedynym powodem do wygenerowania adresu URL jest wysłanie żądania, więc zrobiłbym:

$response = TwilioApi::makeRequest("Accounts/$accountSid/Usage/Records/AllTime")

W rzeczywistości wiele adresów URL zaczyna się od „Accounts / $ accountSid”, więc prawdopodobnie wyodrębnię to również:

$response = TwilioApi::makeAccountRequest($accountSid, "Usage/Records/AllTime")

A jeśli zmienimy twilio api w obiekt, który posiada numer konta, możemy zrobić coś takiego:

$response = $twilio->makeAccountRequest("Usage/Records/AllTime")

Korzystanie z obiektu $ twilio ma tę zaletę, że ułatwia testowanie jednostkowe. Mogę nadać temu obiektowi inny obiekt $ twilio, który tak naprawdę nie odwołuje się do twilio, który będzie szybszy i nie zrobi dziwnych rzeczy dla twilio.

Spójrzmy na drugi

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

Tutaj pomyślałbym o:

$params = MustacheOptions::buildFromParams($bagcheck->getParams());

lub

$params = MustacheOptions::build($bagcheck->getFlatParams());

lub

$params = MustacheOptions::build(flatParams($backCheck));

W zależności od tego, który z nich jest bardziej przydatny.

Winston Ewert
źródło
1

Właściwie nie zgadzam się z wybitnym panem Fowlerem w tej sprawie w sprawie ogólnej.

Zaletą wyodrębnienia metody z wcześniej wprowadzonego kodu jest ponowne użycie kodu; kod w metodzie jest teraz oddzielony od początkowego użycia i można go teraz używać w innych miejscach kodu bez kopiowania i wklejania (co wymagałoby wprowadzania zmian w wielu miejscach, gdyby ogólna logika skopiowanego kodu kiedykolwiek musiała się zmienić) .

Jednak równą, często większą wartością konceptualną jest „ponowne wykorzystanie wartości”. Pan Fowler nazywa te wyodrębnione metody w celu zastąpienia zmiennych tymczasowych „zapytaniami”. Co jest bardziej wydajne; sprawdzanie bazy danych za każdym razem, gdy potrzebujesz konkretnej wartości, lub zapytanie i zapisywanie wyniku (zakładając, że wartość jest na tyle statyczna, że ​​nie spodziewałbyś się jej zmiany)?

W przypadku prawie wszystkich obliczeń wykraczających poza względnie trywialne w twoim przykładzie, w większości języków tańsze jest przechowywanie wyniku jednego obliczenia niż jego dalsze obliczanie. Dlatego ogólne zalecenie ponownego obliczania na żądanie jest nieuczciwe; kosztuje więcej czasu programisty i więcej czasu procesora, a także oszczędza trywialną ilość pamięci, która w większości nowoczesnych systemów jest najtańszym zasobem tych trzech.

Teraz metoda pomocnicza w połączeniu z innym kodem może być „leniwa”. Po pierwszym uruchomieniu zainicjuje zmienną. Wszystkie dalsze wywołania zwracałyby tę zmienną, dopóki metoda nie otrzyma wyraźnego polecenia ponownego obliczenia. Może to być parametr metody lub flaga ustawiona przez inny kod, który zmienia dowolną wartość obliczania tej metody:

double? _basePrice; //not sure if Java has C#'s "nullable" concept
double basePrice(bool forceCalc)
{
   if(forceCalc || !_basePrice.HasValue)
      return _basePrice = _quantity * _itemPrice;
   return _basePrice.Value;
}

Teraz, dla tego trywialnego obliczenia, które wykonuje jeszcze więcej pracy niż jest zapisane, dlatego ogólnie zalecałbym trzymanie się zmiennej temp; jednak w przypadku bardziej skomplikowanych obliczeń, których generalnie nie chcesz uruchamiać wiele razy i których potrzebujesz w wielu miejscach w kodzie, jest to sposób, w jaki to zrobiłbyś.

KeithS
źródło
1

Metody pomocnicze mają swoje miejsce, ale musisz uważać na zapewnienie spójności danych i niepotrzebnego zwiększania zakresu zmiennych.

Na przykład twój własny przykład cytuje:

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;      <--- first call
    else discountFactor = 0.98;
    return basePrice() * discountFactor;                <--- second call
}

Najwyraźniej obie _quantityi _itemPricesą zmienne globalne (lub na poziomie klasy najmniejszej), a więc nie ma możliwości, żeby być modyfikowany pozagetPrice()

Dlatego istnieje możliwość, że pierwsze połączenie basePrice()zwróci inną wartość niż drugie połączenie!

Dlatego sugerowałbym, że funkcje pomocnicze mogą być przydatne do izolowania złożonej matematyki, ale w miejsce zmiennych lokalnych należy zachować ostrożność.


Trzeba także unikać reductio ad absurdum - czy należy obliczyć discountFactormetodę obliczeniową ? Twój przykład staje się:

double getPrice()
{
    final double basePrice      = calculateBasePrice();
    final double discountFactor = calculateDiscount( basePrice );

    return basePrice * discountFactor;
}

Partycjonowanie powyżej pewnego poziomu powoduje, że kod jest mniej czytelny.

Andrzej
źródło
+1 za uczynienie kodu mniej czytelnym. Nadmierne partycjonowanie może ukryć problem biznesowy, który próbuje rozwiązać kod źródłowy. Mogą występować specjalne przypadki, w których kupon jest stosowany w getPrice (), ale jeśli jest on ukryty głęboko w łańcuchu wywołań funkcji, reguła biznesowa jest również ukryta.
Reactgular
0

Jeśli akurat pracujesz w języku o nazwanych parametrach (ObjectiveC, Python, Ruby itp.), Zmienne temp są mniej przydatne.

Jednak w przykładzie basePrice wykonanie zapytania może zająć trochę czasu i możesz zapisać wynik w zmiennej tymczasowej do wykorzystania w przyszłości.

Jednak podobnie jak ty używam zmiennych temp do rozważenia przejrzystości i długości linii.

Widziałem także, że programiści wykonują następujące czynności w PHP. Jest interesujący i świetny do debugowania, ale jest trochę dziwny.

$rs = DB::query( $query = "SELECT * FROM table" );
if (DEBUG) echo $query;
// do something with $rs
Dimitry
źródło
0

Uzasadnieniem tego zalecenia jest to, że chcesz mieć możliwość korzystania z tego samego wstępnego obliczenia w innym miejscu swojej aplikacji. Zobacz temat Zastąp temperaturę zapytaniem w katalogu wzorców refaktoryzacji:

Nową metodę można następnie zastosować w innych metodach

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

           http://i.stack.imgur.com/mKbQM.gif

    if (basePrice() > 1000)
        return basePrice() * 0.95;
    else
        return basePrice() * 0.98;
...
double basePrice() {
    return _quantity * _itemPrice;
}

Dlatego w przykładzie hosta i identyfikatora URI zastosowałbym to zalecenie tylko wtedy, gdy planuję ponownie użyć tego samego identyfikatora URI lub definicji hosta.

W takim przypadku ze względu na przestrzeń nazw nie zdefiniuję globalnej metody uri () lub host (), ale nazwę z większą ilością informacji, taką jak twilio_host () lub archive_request_uri ().

Następnie w przypadku problemu z długością linii widzę kilka opcji:

  • Utwórz zmienną lokalną, np uri = archive_request_uri().

Uzasadnienie: W bieżącej metodzie chcesz, aby identyfikator URI był wymieniony. Definicja URI jest nadal podzielona na czynniki.

  • Zdefiniuj metodę lokalną, np uri() { return archive_request_uri() }

Jeśli często korzystasz z rekomendacji Fowlera, będziesz wiedział, że metoda uri () ma ten sam wzorzec.

Jeśli z powodu wyboru języka musisz uzyskać dostęp do metody lokalnej za pomocą „ja”. Poleciłbym pierwsze rozwiązanie, dla zwiększenia ekspresji (w Pythonie zdefiniowałbym funkcję uri w bieżącej metodzie).

Marc-Emmanuel Coupvent des Gra
źródło