Czy powinienem ponownie używać zmiennych?

59

Czy powinienem ponownie używać zmiennych?

Wiem, że wiele najlepszych praktyk mówi, że nie powinieneś tego robić, jednak później, gdy inny programista debuguje kod i ma 3 zmienne, które wyglądają podobnie, jedyną różnicą jest to, że są tworzone w różnych miejscach w kodzie, może być zmieszany. Testowanie jednostkowe jest tego doskonałym przykładem.

Ja jednak nie wie, że najlepsze praktyki są przez większość czasu przed nim. Na przykład mówią, aby nie „zastępować” parametrów metody.

Najlepsze praktyki są nawet przeciwko zerowaniu poprzednich zmiennych (w Javie jest Sonar, który ostrzega podczas przypisywania nulldo zmiennej, że nie trzeba tego robić, aby wywołać moduł odśmiecania od wersji Java 6. Nie zawsze można kontrolować które ostrzeżenia są wyłączone; przez większość czasu domyślnie jest włączony).

IAdapter
źródło
11
Od wersji Java 6? Nigdy nie trzeba przypisywać wartości null do zmiennych w żadnej wersji Java. Kiedy zmienna wykracza poza zakres, zwalnia odwołanie do swojego obiektu.
Kevin Panko
35
ponowne użycie zmiennych jest jedną z pierwszych rzeczy, których używają zaciemniacze kodu
Lelouch Lamperouge
7
Zmienne ponowne użycie powoduje konflikt z wyborem przyzwoitych identyfikatorów dla zmiennych. We współczesnych językach tak naprawdę nie ma żadnych korzyści ze zmiennego ponownego użycia, które przewyższają korzyści z posiadania dobrych identyfikatorów.
Joren
4
Pamiętaj również, że nie każde ponowne użycie jest ponowne. Dawni programiści FORTRAN, nawet ci, którzy przeszli na inne języki, rutynowo używają I, J, K, L, M, N (lub i, j, k, l, m, n) dla wszystkich naszych pętli DO (dla- pętle) liczniki. Przyzwyczailiśmy się widzieć rzeczy takie jak SUMA = SUMA + A (I, K) * B (K, J) lub suma + = a [i] [k] * b [k] [j] ;.
John R. Strohm,
1
Ponowne użycie zmiennych może być uzasadnione podczas programowania mikrokontrolerów z bardzo ograniczonymi zasobami (kilka kB pamięci RAM).
m.Alin

Odpowiedzi:

130

Twój problem pojawia się tylko wtedy, gdy twoje metody są długie i wykonują wiele zadań w sekwencji. To sprawia, że ​​kod jest trudniejszy do zrozumienia (a więc i utrzymania) per se. Ponowne użycie zmiennych dodatkowo zwiększa ryzyko, czyniąc kod jeszcze trudniejszym do śledzenia i bardziej podatnym na błędy.

Najlepszą praktyką IMO jest stosowanie wystarczająco krótkich metod, które robią tylko jedną rzecz, eliminując cały problem.

Péter Török
źródło
co z testowaniem jednostkowym? na przykład muszę przetestować, czy po uruchomieniu metody po raz drugi nadal działa ona zgodnie z oczekiwaniami.
IAdapter
20
@Adapter, kod testu jednostkowego to inna kwestia, w której standardy mogą być bardziej rozluźnione niż w przypadku kodu produkcyjnego. Nadal sprawienie, by było czytelne i łatwe w utrzymaniu, ma wysoki priorytet. Możesz (i należy) wyodrębnić metody z testów jednostkowych, aby były krótkie i łatwe do odczytania (i aby wyeliminować potrzebę ponownego wykorzystywania zmiennych).
Péter Török
3
@IAdapter, dla scenariusza opisanego bym nie ponownie użyć zmiennej. Właściwą rzeczą byłoby coś takiego result1 = foo(); result2 = foo(); Assert.AreEqual(result1, result2);, że nie widzę wielu miejsc, w których trzeba by ponownie użyć zmiennej w tym przypadku.
JSB ձոգչ
1
@IAdapter for (int i = 0; i <2; i ++) {result = foo (); assertEquals (oczekiwany wynik, wynik);}
Bill K
2
+1 - to dobry zapach kodowy, kiedy należy robić mniejsze metody.
49

Zmienne ponowne użycie w metodzie jest mocnym znakiem, że powinieneś ją refaktoryzować / podzielić.

Moja odpowiedź brzmiałaby, że nie powinieneś ich ponownie używać, ponieważ jeśli to zrobisz, o wiele trudniej będzie je później refaktoryzować.

Thanos Papathanasiou
źródło
19

To zależy.

Niektóre zmienne mogą być tworzone właśnie w celu przechowywania pewnego rodzaju danych, które mogą ulec zmianie podczas wykonywania funkcji. Przychodzą mi na myśl kody zwrotne, na przykład:

void my_function() {
    HRESULT errorcode;
    errorcode = ::SomeWindowsApiFunction();
    if (FAILED(errorcode)) { /* handle error */ }
    errorcode = ::AnotherWindowsApiFunction();
    if (FAILED(errorcode)) { /* handle error */ }
}

Nazwa zmiennej wyjaśnia, co ta zmienna ma przechowywać. Myślę, że możliwe są inne przypadki użycia, takie jak ten, w których zmienna jest koncepcyjnie kontenerem, który jest logicznie używany przez różne instancje bardzo podobnych rzeczy w trakcie funkcji.

Zasadniczo należy to jednak robić tylko w okolicznościach, w których jest to absolutnie jasne dla potencjalnych czytelników kodu. W żadnym wypadku, z wyjątkiem skrajnej optymalizacji bez względu na czytelność kodu, zmienna nie może być ponownie użyta tylko dlatego, że typ pasuje.

Wszystko to w zasadzie wynika z dobrych praktyk w zakresie nazewnictwa zmiennych. Nazwy powinny mówić same za siebie. Jeśli trudno jest podać dokładny cel wszystkich ponownych zastosowań w krótkiej nazwie, najlepiej po prostu użyć odrębnych zmiennych.

Felix Dombek
źródło
15

Największym powodem, dla którego nie używam ponownie zmiennych (szczególnie w testach jednostkowych) jest to, że wprowadza niepotrzebną ścieżkę kodu, trudną do przetestowania i debugowania. Dobry test jednostkowy powinien być niezależny od innych testów, a kiedy ponownie użyjesz zmiennej poziomu klasy (instancji) w urządzeniu do testów jednostkowych, musisz upewnić się, że sprawdzasz ich stan przed każdym testem. Dobry test jednostkowy izoluje również defekty, więc teoretycznie każdy przypadek testowy (metoda) powinien zapewniać tylko 1 zachowanie dla testowanego systemu. Jeśli twoje metody testowe są zapisane w ten sposób, rzadko jest potrzeba lub korzyść z ponownego użycia zmiennej na poziomie metody. Wreszcie, w językach obsługujących zamykanie i przetwarzanie asynchroniczne, naprawdę trudno jest zrozumieć, co się do cholery dzieje, jeśli ponownie wykorzystujesz zmienne w całej metodzie.

sbrenton
źródło
więc powinienem napisać metody testowe takie jak -testSomething i potwierdzić dla wywołania jednej metody -testSomethingTwoInvocations i potwierdzić tylko drugie wywołanie?
IAdapter
2
Tak. Pomaga ustalić, czy nie powiodła się pierwsza czy druga iteracja. mspec może w tym pomóc, jego drzewo dziedziczenia będzie bardzo przydatne.
Bryan Boettcher
Jeśli używasz „zmiennej”, gdy masz na myśli „zmienną klasy” lub „zmienną instancji”, musisz wyrazić się jaśniej.
gnasher729,
13

Powinieneś użyć różnych zmiennych. Jeśli obawiasz się, że twój kolega będzie zdezorientowany, podaj im nazwiska, które wyraźnie odzwierciedlają jego role.
Ponowne użycie zmiennych jest prawdopodobnym źródłem nieporozumień w przyszłości; lepiej to teraz wyjaśnić.
W niektórych przypadkach można użyć tej samej nazwy zmiennej; na przykład iw prostej pętli liczenia. W takich przypadkach należy oczywiście upewnić się, że zmienne są w swoim zakresie.

EDYCJA: Ponowne użycie zmiennych jest czasem oznaką naruszenia zasady pojedynczej odpowiedzialności . Sprawdź, czy ponownie używana zmienna jest używana w tej samej roli. Jeśli tak, może wcale nie być ponownie użyte (chociaż nadal może być lepiej mieć dwie różne zmienne, aby ograniczyć zakres tych zmiennych). Jeśli jest używany w różnych rolach, masz naruszenie zasad SRP.

SL Barth
źródło
4

Jest jedna sytuacja, w której możesz chcieć ponownie użyć zmiennej, niezależnie od świetnych porad udzielonych przez inne odpowiedzi: kiedy twój kompilator potrzebuje pomocy.

W niektórych przypadkach kompilator może nie być wystarczająco sprytny, aby zdać sobie sprawę, że pewna zmienna przydzielona do rejestru nie jest już używana przez następną część kodu. Dlatego nie będzie ponownie wykorzystywać tego teoretycznie wolnego rejestru dla następnych zmiennych, a wygenerowany kod może być nieoptymalny.

Zauważ, że nie znam żadnych obecnych kompilatorów głównego nurtu, które nie wychwycą tej sytuacji poprawnie, więc nigdy, nigdy, nigdy tego nie rób, chyba że wiesz, że twój kompilator generuje nieoptymalny kod. Jeśli kompilujesz dla specjalnych systemów osadzonych z niestandardowymi kompilatorami, możesz nadal napotykać ten problem.

Joris Timmermans
źródło
17
-1, chyba że możesz wskazać rzeczywistą sytuację, w której tak się dzieje, a ponowne użycie zmiennej robi znaczącą różnicę. Jest to teoretyczne rozwiązanie problemu teoretycznego i niezbyt dobre. To, gdzie kompilator zdecyduje się wstawić zmienne, dotyczy kompilatora; jeśli twój kompilator generuje słaby kod, a jeśli to naprawdę robi różnicę, to napraw lub wymień kompilator.
Caleb
3
@Caleb - nie zawsze masz dostęp do kodu źródłowego kompilatora dla platformy, na której się rozwijasz, szczególnie dla zastrzeżonych platform wbudowanych. Ja też CZY powiedzieć w mojej odpowiedzi, że nie wiem o wszelkich aktualnych kompilatorów nurtu (albo tłumacze / VM w rzeczywistości), które nie poprawnie wykonać tę liveness analizę we wszystkich przypadkach, że szukałem w. Uważam jednak, że pracował na własnych systemach wbudowanych z niestandardowymi kompilatory, które były bardzo goły w przeszłości (10 lat temu lub tak). System miał bardzo ograniczone możliwości, podobnie jak kompilator, więc taka sytuacja miała miejsce.
Joris Timmermans
2
+1 Zrobiłem dokładnie takie ponowne wykorzystanie w poprzednim projekcie (wysoce zoptymalizowany kod mediów napisany w ANSI C). O ile pamiętam, różnica była łatwo widoczna w montażu i mierzalna w testach porównawczych. Nigdy tego nie robiłem i mam nadzieję, że nigdy nie będzie musiał tak ponownie wykorzystywać w Javie.
komar
@Caleb naprawianie lub zastępowanie kompilatora może nie być opcją z wielu powodów i po prostu musisz zadowolić się tym, co masz.
@ ThorbjørnRavnAndersen Czy można sobie wyobrazić, że ktoś może być zmuszony do korzystania z kiepskiego kompilatora, a ponadto wydajność może być tak krytyczna, że ​​trzeba będzie zgadywać kompilator i manipulować kodem w celu ponownego użycia rejestru? Tak. Czy to prawdopodobne ? Nie. MadKeithV zgadza się, że nie może dać przykładu z prawdziwego świata. Czy to najlepsza praktyka ? Czy to dobry styl ? Czy to wskaźnik jakości kodu ? Absolutnie nie. Czy to przydatna odpowiedź na pytanie w formie pisemnej? Z szacunkiem dla MadKeithV, ale nie sądzę, że tak jest.
Caleb
2

Powiedziałbym NIE.

Pomyśl o tym scenariuszu: Twój program się zawiesił i musisz dowiedzieć się, co się stało, sprawdzając zrzut pamięci ... widzisz różnicę? ;-)

fortran
źródło
Jeśli zrzut podstawowy pochodzi ze zoptymalizowanej wersji, zmienne mogą mimo to współdzielić pamięć. To może być mniej pomocne, niż mogłoby być.
Winston Ewert
oczywiście zoptymalizowana kompilacja nie jest bardzo przydatna do debugowania! ale w każdym razie nadal masz opcję użycia kompilacji debugowania ... A kiedy masz podłączony debuger, nie musisz iść krok po kroku od początku funkcji, aby zobaczyć, jak zmieniają się zmienne, ponieważ one nie! :-D
fortran
2

Nie, nie poprawiasz kodu uruchomionego przez maszynę (... kod asemblera). Pozostaw ponownie wykorzystanie pamięci kompilatora, której używa kompilator. Dość często będzie to rejestr, a ponowne użycie nic ci nie kupi. Skoncentruj się na ułatwieniu odczytu kodu.

Rawbert
źródło
0

To zależy. W języku dynamicznym, w którym typ opiera się na wartościach, a nie zmiennych, wtedy gdybym miał dobrze nazwany argument funkcji, która na przykład była łańcuchem. Zmiana algorytmu oznaczała, że ​​zawsze był używany po interpretacji jako liczba całkowita, a następnie jako pierwszy szkic mógłbym zrobić odpowiednik

scrote = int (scrote)

Ale w końcu chciałbym zmienić typ wysyłany do funkcji i zauważyć zmianę typu parametru.

Paddy3118
źródło
1
Umieszczenie wiersza w stylu „scrote = int (scrote)” w środku długiej partii kodu to świetny sposób na doprowadzenie programistów do konserwacji do szaleństwa.
jhocking 21.10.11
Problem jest bardziej prawdopodobny w przypadku wspomnianej „długiej partii kodu”.
Paddy3118,
Podobnie jak w zaakceptowanych uwagach dotyczących odpowiedzi, ponowne użycie zmiennych jest tak naprawdę problemem tylko w przypadku długich partii kodu. Zasadniczo w każdej sytuacji, w której napisałbyś coś w stylu „scrote = int (scrote)” wolałbym umieścić int (scrote) w nowej zmiennej lub lepiej przekazać int (scrote) do nowej funkcji.
jhocking
0

Najpierw spójrz na swoje środowisko programistyczne. Jeśli masz problemy z debugowaniem, ponieważ masz pięć zmiennych w różnych zakresach o identycznych nazwach, a debugger nie pokazuje, która z tych zmiennych jest potrzebna, nie używaj pięciu zmiennych o tej samej nazwie. Można to osiągnąć na dwa sposoby: użyj jednej zmiennej lub pięciu różnych nazw.

Twój debugger może również utrudniać debugowanie funkcji z wieloma zmiennymi. Jeśli funkcja z 20 zmiennymi jest trudniejsza do debugowania niż jedna z 16 zmiennymi, możesz rozważyć zastąpienie 5 zmiennych jedną. („Może rozważyć” to nie to samo, co „zawsze”).

Można używać jednej zmiennej w wielu miejscach, o ile zmienna ma zawsze ten sam cel. Na przykład, jeśli dziesięć wywołań funkcji zwraca kod błędu, który jest obsługiwany natychmiast dla każdego wywołania, użyj jednej zmiennej, a nie 10. Ale nie używaj tej samej zmiennej do zupełnie innych rzeczy. Jak użycie nazwy dla nazwy klienta i 10 linii później przy użyciu tej samej zmiennej dla nazwy firmy, to źle i sprawi, że będziesz mieć kłopoty. Gorzej, przy użyciu „nazwa klienta” dla nazwy klienta i 10 linii później przy użyciu tej samej zmiennej „nazwa klienta” dla nazwy firmy.

Co ważne, nic nie jest żelazną zasadą. Wszystko ma zalety i wady. Jeśli „najlepsze praktyki” sugerują jedną rzecz, a masz powody, by twierdzić, że w twojej konkretnej sytuacji jest to zły pomysł, nie rób tego.

gnasher729
źródło
0

Najpierw spójrz na swoje środowisko programistyczne. Jeśli masz problemy z debugowaniem, ponieważ masz pięć zmiennych w różnych zakresach o identycznych nazwach, a debugger nie pokazuje, która z tych zmiennych jest potrzebna, nie używaj pięciu zmiennych o tej samej nazwie. Można to osiągnąć na dwa sposoby: użyj jednej zmiennej lub pięciu różnych nazw.

Twój debugger może również utrudniać debugowanie funkcji z wieloma zmiennymi. Jeśli funkcja z 20 zmiennymi jest trudniejsza do debugowania niż jedna z 16 zmiennymi, możesz rozważyć zastąpienie 5 zmiennych jedną. („Może rozważyć” to nie to samo, co „zawsze”).

Można używać jednej zmiennej w wielu miejscach, o ile zmienna ma zawsze ten sam cel. Na przykład, jeśli dziesięć wywołań funkcji zwróci kod błędu, który jest obsługiwany natychmiast dla każdego wywołania, użyj jednej zmiennej, a nie 10. Jest z tym jeden problem: zazwyczaj kompilator powie ci, kiedy użyjesz niezainicjowanej zmiennej. Ale tutaj, jeśli odczytasz kod błędu po wywołaniu 6, ale zmienna tak naprawdę nie została zmieniona po wywołaniu 5, otrzymasz bezużyteczne dane bez ostrzeżenia kompilatora. Ponieważ zmienna została zainicjowana, a dane są teraz bezużyteczne.

Co ważne, nic nie jest żelazną zasadą. Wszystko ma zalety i wady. Jeśli „najlepsze praktyki” sugerują jedną rzecz, a masz powody, by twierdzić, że w twojej konkretnej sytuacji jest to zły pomysł, nie rób tego.

gnasher729
źródło
-2

Użyj zmiennych globalnych, gdy chcesz utrzymać stan lub zapisać stałe. Ponowne użycie zmiennych powoduje ponowne użycie tylko nazwy wskazującej lokalizację w pamięci. Nazwy opisowe przy inicjalizacji mogą tylko zapewnić Ci jasność (Zakładając, że Java i nie ma prymitywnego OCD).

Chyba że masz zamiar zaciemnić kod ręcznie lub drażnić innych programistów.

JunoA
źródło