Uogólnij użycie zmiennych w kodzie

11

Chciałbym wiedzieć, czy dobrą praktyką jest generalizacja zmiennych (użyj jednej zmiennej do przechowywania wszystkich wartości).
Rozważ prosty przykład

 Strings querycre,queryins,queryup,querydel; 
    querycre = 'Create table XYZ ...';
    execute querycre ;
    queryins = 'Insert into XYZ ...';
    execute queryins ;
    queryup  = 'Update  XYZ set ...';
    execute queryup;
    querydel = 'Delete from XYZ ...';
    execute querydel ;

i

 Strings query; 
    query= 'Create table XYZ ... ';
    execute query ;
    query= 'Insert into XYZ ...';
    execute query ;
    query= 'Update  XYZ set ...';
    execute query ;
    query= 'Delete from XYZ ...';
    execute query ;

W pierwszym przypadku używam 4 łańcuchów, z których każdy przechowuje dane, aby wykonać czynności wymienione w ich przyrostkach.
W drugim przypadku tylko 1 zmienna do przechowywania wszystkich rodzajów danych.
Posiadanie różnych zmiennych ułatwia komuś innemu czytanie i zrozumienie go lepiej. Ale posiadanie zbyt wielu z nich utrudnia zarządzanie.

Czy zbyt wiele zmiennych utrudnia moją wydajność?

PS: proszę nie odpowiadaj na kod w przykładzie, aby po prostu przekazać to, co naprawdę mam na myśli.

Shirish11
źródło
Oczywiście ponownie używasz tej samej zmiennej ... ponieważ zdefiniowałeś ją w funkcji. Do tego służą funkcje.
zzzzBov

Odpowiedzi:

26

Zadanie sobie tego pytania jest dość silnym zapachem, że nie podążasz za OSUSZANIEM (nie powtarzaj się). Załóżmy, że masz to w hipotetycznym języku nawiasów klamrowych:

function doFoo() {
    query = "SELECT a, b, c FROM foobar WHERE baz = 23";
    result = runQuery(query);
    print(result);

    query = "SELECT foo, bar FROM quux WHERE x IS NULL";
    result = runQuery(query);
    print(result);

    query = "SELECT a.foo, b.bar FROM quux a INNER JOIN quuux b ON b.quux_id = a.id ORDER BY date_added LIMIT 10";
    result = runQuery(query);
    print(result);
}

Przeanalizuj to w:

function runAndPrint(query) {
    result = runQuery(query);
    print(result);
}

function doFoo() {
    runAndPrint("SELECT a, b, c FROM foobar WHERE baz = 23");
    runAndPrint("SELECT foo, bar FROM quux WHERE x IS NULL");
    runAndPrint("SELECT a.foo, b.bar FROM quux a INNER JOIN quuux b ON b.quux_id = a.id ORDER BY date_added LIMIT 10");
}

Zauważ, że odchodzi potrzeba podjęcia decyzji, czy użyć różnych zmiennych, i jak możesz teraz zmienić logikę uruchamiania zapytania i drukowania wyniku w jednym miejscu, zamiast trzykrotnego zastosowania tej samej modyfikacji. (Na przykład możesz zdecydować, że chcesz przepompować wynik zapytania przez system szablonów zamiast drukować go od razu).

tdammers
źródło
2
Uwielbiam zasadę DRY :)
artjom
1
@tdammers, czy dobrze jest mieć tylko 2 linie kodu w funkcji? Zastanów się, czy mam tę funkcję doFoo () {print (runQuery ("Selct a, b, c from XYZ"));}
Shirish11
1
Nie, stos wywołań nie zwiększa się - każde wywołanie runAndPrintwypycha jedną ramkę stosu, gdy ją wywołujesz, a następnie odsuwa ją po wyjściu z funkcji. Jeśli wywołasz go trzy razy, wykona trzy pary push / pop, ale stos nigdy nie rośnie o więcej niż jedną klatkę na raz. Powinieneś naprawdę martwić się o głębokość stosu wywołań dzięki funkcjom rekurencyjnym.
tdammers
3
A funkcje z zaledwie dwoma liniami kodu są doskonale w porządku: jeśli dwie linie tworzą jednostkę logiczną, to dwie linie. Napisałem wiele funkcji jednowierszowych, aby zachować trochę informacji w jednym miejscu.
tdammers
1
@JamesAnderson: To nieco wymyślony przykład, ale służy zilustrowaniu pewnej kwestii. Nie chodzi o to, ile masz linii kodu. To ile razy podajesz ten sam fakt. Na tym właśnie polega DRY, a także zasada Single Source Of Truth, zasada Thou Shalt Not Copy-Paste itp.
tdammers,
14

Zwykle jest to zła praktyka.

Ponowne użycie zmiennej może w ten sposób sprawić, że kod będzie mylący z odczytaniem zrozumienia.

Osoby czytające kod nie będą oczekiwać ponownego użycia zmiennej w taki sposób i nie będą wiedziały, dlaczego wartość ustawiona na początku ma inną wartość na końcu funkcji.

Opublikowane przez ciebie przykłady są bardzo proste i tak naprawdę nie cierpią z powodu tego problemu, ale nie są reprezentatywne dla jakiegoś kodu, który ponownie wykorzystuje zmienne (gdzie jest ustawiony na początku, jest ponownie używany gdzieś pośrodku - poza zasięgiem wzroku).

Podane przykłady nadają się do enkapsulacji w funkcjach, w których można przekazać zapytanie i wykonać je.

Oded
źródło
co z wydajnością systemu, na którą ma to wpływ?
Shirish11,
@ Shirish11 - Może. Zależy od kompilatora, języka, środowiska i innych zmiennych.
Oded
Zwykle kompilator dobrze to optymalizuje. Zawsze zależy to jednak od kompilatora / formatu płyty / konkretnego przypadku / konfiguracji.
deadalnix
7

Kod udokumentowany jest łatwiejszy do odczytania i utrzymania

Postępuj zgodnie z zasadą najmniejszego oczyszczenia i zasadą kodowania jako dokumentacji : użyj jednej zmiennej dla jednego celu, aby zarówno jej użycie było łatwe do zrozumienia, jak i kod łatwy do odczytania bez wyjaśnień.

Prawidłowo skonstruowany kod jest łatwiejszy (a więc tańszy) do (ponownego) użytkowania

Tutaj również wydaje się, że queryzawsze jest on używany do przygotowania instrukcji przed jej wykonaniem. Prawdopodobnie jest to znak, że chcesz zmienić część tego kodu na jedną (lub więcej) metod pomocniczych w celu przygotowania i wykonania zapytania (aby zachować zgodność z zasadą DRY ).

W ten sposób skutecznie:

  • użyj tylko jednej zmiennej w metodzie pomocnika, aby zidentyfikować zapytanie bieżącego kontekstu,
  • musisz wpisać mniej kodu za każdym razem, gdy chcesz ponownie wykonać zapytanie,
  • uczynić twój kod bardziej czytelnym dla innych.

Przykłady:

Rozważ to, wzięte z twojego przykładu, gdzie wersja refaktoryzowana jest oczywiście lepsza. Oczywiście Twój fragment kodu był tylko przykładem na potrzeby tego pytania, ale koncepcja nadal jest prawdziwa i skaluje się.

Twój przykład 1:

Strings querycre,queryins,queryup,querydel; 
    querycre = 'Create table XYZ ...';
    execute querycre ;
    queryins = 'Insert into XYZ ...';
    execute queryins ;
    queryup  = 'Update  XYZ set ...';
    execute queryup;
    querydel = 'Delete from XYZ ...';
    execute querydel ;

Twój przykład 2:

 Strings query; 
    query= 'Create table XYZ ...';
    execute query ;
    query= 'Insert into XYZ ...';
    execute query ;
    query= 'Update  XYZ set ...';
    execute query ;
    query= 'Delete from XYZ ...';
    execute query ;

Przykład 3 (Refaktoryzowany pseudo-kod):

def executeQuery(query, parameters...)
    statement = prepareStatement(query, parameters);
    execute statement;
end

// call point:
executeQuery('Create table XYZ ... ');
executeQuery('Insert into XYZ ...');
executeQuery('Update  XYZ set ...');
executeQuery('Delete from XYZ ...');

Korzyść wynika z regularnego ponownego użycia.

Anegdota osobista

Zaczynałem jako programista C pracujący z ograniczoną powierzchnią ekranu, więc ponowne użycie zmiennych miało sens zarówno dla skompilowanego kodu (wtedy), jak i dla umożliwienia odczytania większej ilości kodu na raz.

Jednak później przeszedłem na języki wyższego poziomu i nauczyłem się programowania funkcjonalnego, przyzwyczaiłem się używać niezmiennych zmiennych i niezmiennych referencji, o ile to możliwe, aby ograniczyć skutki uboczne.

Co będę z tego mieć?

Jeśli przyzwyczaisz się, że wszystkie dane wejściowe funkcji są niezmienne i zwrócisz nowy wynik (tak jak zrobiłaby to prawdziwa funkcja matematyczna), nabędziesz zwyczaju nie powielania sklepów.

W rezultacie prowadzi to do:

  • piszesz krótkie funkcje,
  • z jasno określonymi celami,
  • łatwiejsze do zrozumienia,
  • do ponownego wykorzystania,
  • rozszerzyć (czy to przez dziedziczenie OO, czy poprzez funkcjonalne tworzenie łańcuchów),
  • i dokument (jako już samodokumentujący).

Nie twierdzę, że nie ma tu żadnej korzyści ze stanu zmienności, po prostu wskazuję, w jaki sposób nawyk może rozwinąć się na tobie i jak wpływa na czytelność kodu.

Haylem
źródło
2

W zakresie projektowania kodu

Zasadniczo można ponownie wykorzystywać zmienne do przechowywania różnych wartości - w końcu dlatego nazywane są zmiennymi, ponieważ wartość w nich przechowywana jest różna - o ile wartość jest nie tylko tego samego typu, ale także oznacza to samo . Na przykład, oczywiście, można ponownie użyć currentQueryzmiennej tutaj:

for currentQuery in queries:
    execute query;

Naturalnie istnieje pętla więc mieć do ponownego użycia zmiennej, ale nawet jeśli nie było pętli byłoby w porządku. Jeśli wartość nie oznacza tego samego, użyj oddzielnej zmiennej.

W szczególności opisywany kod nie wygląda zbyt dobrze - powtarza się . O wiele lepiej jest używać wywołań metod pętli lub pomocników (lub obu). Osobiście bardzo rzadko widuję kod produkcyjny, który wygląda na twoją pierwszą lub drugą wersję, ale w takich przypadkach myślę, że druga wersja (zmienne ponowne użycie) była bardziej powszechna.

Pod względem wydajności

Zależy to od używanego języka, kompilatora i systemu uruchomieniowego, ale ogólnie nie powinno być żadnej różnicy - w szczególności kompilatory dla rejestrów opartych na stosie (jak popularny x86 / x86-64) i tak po prostu użyj dowolnej wolnej pamięci stosu lub rejestru, które mogą, jako celu przypisania, całkowicie ignorując, czy chcesz tę samą zmienną, czy nie.

Na przykład gcc -O2generuje dokładnie ten sam plik binarny, a jedyną znaną mi różnicą wydajności jest wielkość tabeli symboli podczas kompilacji - całkowicie nieistotna, chyba że cofniesz się w czasie do lat 60.

Kompilator Java wygeneruje kod bajtowy, który potrzebuje więcej miejsca dla 1. wersji, ale jitter JVM i tak go usunie, więc podejrzewam, że praktycznie nie będzie zauważalnego wpływu na wydajność, nawet jeśli potrzebujesz wysoce zoptymalizowanego kodu.

Dąb
źródło
0

Myślę, że ponowne użycie zmiennej jest w porządku przez większość czasu.

Dla mnie przez większość czasu po prostu ponownie używam zmiennej zapytania. Prawie zawsze wykonuję zapytanie zaraz po. Kiedy nie wykonuję zapytania od razu, zwykle używam innej nazwy zmiennej.

Echo mówi Przywróć Monikę
źródło
-1

Może zwiększyć użycie stosu, jeśli twój kompilator jest szczególnie głupi. Osobiście nie sądzę, aby oddzielna zmienna dla każdego zapytania w ogóle zwiększała czytelność, nadal trzeba spojrzeć na ciąg zapytania, aby zobaczyć, co robi.

James
źródło
Właśnie podałem prosty przykład, aby czytelnikom łatwiej było zrozumieć, czego szukam. Mój kod jest znacznie bardziej złożony niż ten.
Shirish11
-2

W tym przykładzie wybrałbym drugi przykład. To, co robisz, jest jasne zarówno dla czytelnika, jak i optymalizatorów. Pierwszy przykład jest nieco bardziej odpowiedni i przy nieco bardziej skomplikowanym kodzie użyłbym go, ale zrób to tak:

{
    String query = 'Create table XYZ ...';
    execute query;
}
{
    String query = 'Insert table XYZ ...';
    execute query;
}
And so on...

(W tym momencie mogę rozważyć rozwiązanie tdammers .)

Problem z pierwszym przykładem dotyczy querycrecałego bloku, który może być obszerny. Może to wprowadzić w błąd osobę czytającą kod. Może także mylić optymalizatory, które mogą pozostawić niepotrzebne zapisy w pamięci, więc będą querycredostępne później, jeśli zajdzie taka potrzeba (co nie jest). Ze wszystkimi nawiasami klamrowymi queryjest przechowywany tylko w rejestrze, jeśli to.

W przypadku takich zwrotów, jak „Utwórz tabelę” i „uruchom”, nie wygląda mi to na dodatkowe zapisanie w pamięci, więc zaryzykuję kod za pomyłkę w czytaniu. Ale warto o tym pamiętać, jeśli piszesz kod, w którym prędkość ma znaczenie.

RalphChapin
źródło
Nie zgadzam się. Jeśli wolisz drugi przykład ze względu na przejrzystość, powinieneś go przełożyć na kolejne wywołania metody pomocniczej. niesie więcej znaczenia i wymaga mniej kodu.
haylem
@haylem: W naprawdę prostym przypadku, takim jak ten, dodajesz metodę pomocniczą, którą musi przeczytać osoba czytająca kod. (I ktoś może mieć problem z metodą pomocniczą i musi wymyślić wszystkie miejsca, z których jest wywoływany.) Mniej przejrzysta, o tej samej ilości kodu. W bardziej skomplikowanym przypadku wybrałbym moje rozwiązanie, a następnie tdammera . Odpowiedziałem na to pytanie głównie po to, aby wskazać (wprawdzie niejasne, ale interesujące) problemy, które nie są w pełni wykorzystywane przez ludzi i optymalizatorów.
RalphChapin
@haylem: ty i tdammer podacie prawidłowe rozwiązanie. Myślę, że w niektórych przypadkach może to być przesada.
RalphChapin