Po co deklarować zmienne blisko miejsca ich użycia?

10

Słyszałem, jak ludzie mówią, że zmienne powinny być deklarowane tak blisko ich użycia, jak to możliwe. Nie rozumiem tego

Na przykład ta zasada sugeruje, że powinienem to zrobić:

foreach (var item in veryLongList) {
  int whereShouldIBeDeclared = item.Id;
  //...
}

Ale z pewnością oznacza to, że intprzy każdej iteracji powstają koszty ogólne związane z tworzeniem nowego . Czy nie lepiej byłoby użyć:

int whereShouldIBeDeclared;
foreach (var item in veryLongList) {
  whereShouldIBeDeclared = item.Id;
  //...
}

Czy mógłby ktoś wyjaśnić?

James
źródło
3
Byłby to cholernie słaby język, który traktowałby te dwie sprawy inaczej.
Paul Tomblin,
5
Zaczynasz od fałszywej przesłanki. Proszę zobaczyć moją odpowiedź na to pytanie: stackoverflow.com/questions/6919655/…
CesarGon
8
Jeśli tak myślisz, nie jesteś w stanie zoptymalizować, a nawet ogólnie rozważyć wpływ na wydajność. Implementacje językowe są inteligentne, a jeśli uważasz, że nie są, udowodnij to twardymi danymi uzyskanymi dzięki obiektywnym, realistycznym testom porównawczym.
4
Jeśli dwa przykłady kodu mają znaczącą różnicę semantyczną, robią różne rzeczy. Powinieneś użyć tego, który robi to, co chcesz zrobić. Reguła dotycząca deklarowania zmiennych dotyczy tylko przypadków, w których nie ma to znaczenia semantycznego.
David Schwartz
4
Rozważ przeciwny koniec skali - wszystko jest zmienną globalną. Z pewnością „deklarowane prawie użycie” to lepszy koniec tego spektrum?
JBRWilkinson,

Odpowiedzi:

27

Jest to jedna z wielu stylów i niekoniecznie najważniejsza z wszystkich możliwych reguł, które możesz wziąć pod uwagę. Twój przykład, ponieważ zawiera int, nie jest zbyt przekonujący, ale z pewnością możesz mieć w tej pętli kosztowny do zbudowania obiekt i być może dobry argument na rzecz konstruowania obiektu poza pętlą. Nie oznacza to jednak, że jest to dobry argument przeciwko tej regule, ponieważ po pierwsze, istnieje mnóstwo innych miejsc, które można zastosować, które nie wymagają konstruowania drogich obiektów w pętli, a po drugie, dobry optymalizator (i otagowałeś C #, więc masz dobry optymalizator) może wyciągnąć inicjalizację z pętli.

Prawdziwym powodem tej reguły jest również powód, dla którego nie rozumiesz, dlaczego jest to reguła. Ludzie pisali funkcje o długości setek, a nawet tysięcy linii i pisali je w edytorach zwykłego tekstu (myślę w Notatniku) bez wsparcia Visual Studio. W tym środowisku zadeklarowanie zmiennej setki linii poza miejscem jej użycia oznaczało, że osoba czytająca

if (flag) limit += factor;

nie miałam wielu wskazówek na temat tego, jaka była flaga, limit i czynnik. Aby pomóc w tym celu, przyjęto konwencje nazewnictwa, takie jak notacja węgierska, a także zasady, takie jak deklarowanie rzeczy w pobliżu miejsca ich użycia. Oczywiście w dzisiejszych czasach chodzi przede wszystkim o refaktoryzację, a funkcje są zwykle krótsze niż strona, co utrudnia uzyskanie bardzo dużej odległości między tym, co deklaruje się a tym, gdzie są używane. Działasz w przedziale od 0 do 20 i spierasz się, że w tym konkretnym przypadku może 7 jest w porządku, podczas gdy facet, który wprowadził regułę, UWIELBIAŁby, aby uzyskać 7 linii i próbował przekonać kogoś z 700. I dalej co więcej, w Visual Studio możesz najechać myszką na dowolny przedmiot i zobaczyć jego typ, czy jest to zmienna składowa itd. Oznacza to, że trzeba zobaczyć linię deklarującą, że jest zmniejszona.

Nadal jest to dość dobra zasada, która w dzisiejszych czasach jest dość trudna do złamania, i której nikt nigdy nie zalecał jako powód do pisania wolnego kodu. Bądź przede wszystkim rozsądny.

Kate Gregory
źródło
Dzięki za odpowiedź. Ale z pewnością bez względu na typ danych na każdym etapie jest tworzona nowa instancja, niezależnie od tego, jak to zrobię? Po prostu w drugim przypadku nie pytamy za każdym razem o nowe odniesienie do pamięci. A może spóźniłem się? I czy mówisz, że optymalizator C # automatycznie poprawi mój kod, gdy i tak się kompiluje? Nie wiedziałem tego!
James,
2
Koszt utworzenia int jest niewielki. Jeśli konstruujesz coś skomplikowanego, koszty ogólne byłyby większe.
Kate Gregory
17
Nie chodzi tylko o to, aby zobaczyć jego typ i tym podobne. To także kwestia życia. Jeśli zmienna „wibble” zostanie zadeklarowana 30 linii przed pierwszym użyciem, istnieje 30 linii, w których błędne użycie „wibble” może spowodować błąd. Jeśli zostanie zadeklarowany bezpośrednio przed użyciem, użycie „wibble” w tych 30 poprzednich wierszach nie spowoduje błędu. Zamiast tego spowoduje błąd kompilatora.
Mike Sherrill „Cat Recall”
W takim przypadku nowa instancja nie jest tworzona w każdej pętli. Pojedyncza zmienna najwyższego poziomu jest tworzona i używana dla każdej iteracji (patrz IL). Ale to szczegół implementacji.
thecoop,
„w programie Visual Studio można najechać myszką na wszystko i zobaczyć” itp. Istnieje również opcja Przejdź do definicji, która ma F12niezbędny skrót .
StuperUser,
15

Zdefiniowanie zmiennej w pętli powoduje, że jest ona widoczna tylko dla tej pętli. Ma to co najmniej 3 zalety dla czytelnika:

  1. Definicja zmiennej i wszelkie powiązane komentarze są łatwe do znalezienia
  2. Czytelnik wie, że ta zmienna nie jest używana gdzie indziej (nie należy oczekiwać zależności)
  3. Kiedy kod jest zapisywany lub edytowany, nie ma szans, że możesz użyć tej samej nazwy zmiennej poza pętlą, aby odnieść się do tej zmiennej, w przeciwnym razie możesz popełnić błąd.

Jeśli chodzi o bit wydajności, kompilator jest inteligentny, aby wygenerować definicję poza pętlą w wygenerowanym zoptymalizowanym kodzie. Zmienna nie będzie tworzona przy każdej iteracji pętli.

Bez szans
źródło
4

Ludzie mówią, że jest tak blisko ich użycia, jak to możliwe. Nie twierdzą, że powinieneś to robić cały czas, ponieważ w niektórych przypadkach deklarowanie zmiennych o najmniejszym zasięgu spowoduje pewne obciążenie. Głównymi przyczynami tego stwierdzenia jest Czytelność i podawanie zmiennych najmniejszy możliwy zakres.

niezmienny
źródło
4

Chociaż pomaga to w czytelności, czytelność nie jest w tym przypadku najważniejszym czynnikiem, a współczesne IDE nie eliminują potrzeby stosowania tej reguły.

Podstawowym problemem są niezainicjowane zmienne. Jeśli zadeklarujesz zmienną zbyt daleko od jej inicjalizacji, otworzy się ona na wszelkiego rodzaju potencjalne problemy. Może się zdarzyć, że przypadkowo pracujesz z tym, co zdarzyło się tam wcześniej w pamięci RAM, lub wynikiem obliczeń wyższych w funkcji, lub fałszywej inicjalizacji (np. 0), którą ktoś wprowadził tylko po to, by kompilator nie narzekał. Ludzie wstawią kod pomiędzy twoją deklaracją a użytkowaniem, nie wiedząc o twoich domniemanych warunkach wstępnych dla tej zmiennej. W najgorszych przypadkach takie użycie zadziała w testach, ale nie powiedzie się w terenie.

Zadeklarowanie zmiennych w możliwie najmniejszym zakresie i zainicjowanie ich do właściwej wartości w punkcie deklaracji pozwoli uniknąć wielu problemów związanych z konserwacją. Fakt, że wymusza lepszą czytelność, to po prostu miły efekt uboczny.

Karl Bielefeldt
źródło
1

To nie jest „konieczność”. To tylko opinia, idę coś zrobić. Na przykład lubię deklarować wszystkie zmienne w pierwszych wierszach metody, aby móc skomentować, co zrobię z tymi zmiennymi (oczywiście, jeśli nie są licznikami). Inne osoby, jak słyszałeś, lubią umieszczać je tak blisko ich użycia, jak to możliwe (jak w drugim przykładzie, który napisałeś). W każdym razie, pierwszy podany przez ciebie przykład jest z pewnością „błędem” (w tym sensie, że spowoduje to narzut, jak rozumiesz).

Musisz po prostu wybrać swoją drogę i podążać nią.

Aurelio De Rosa
źródło
2
To nie tylko opinia, prawda? Czy badania inżynierii oprogramowania nie udokumentowały związku między czasem na żywo a liczbą błędów od co najmniej lat osiemdziesiątych?
Mike Sherrill „Cat Recall”
1

Twoje dwa przykłady są funkcjonalnie innym kodem, nie można ich zamieniać. (Twoje uproszczone przykłady pozostawiają to rozróżnienie bez różnicy, ale w nietrywialnym kodzie robi różnicę). Reguła, którą umieszczasz, jest zawsze podporządkowana względom określania zakresu, jak wskazuje „... jak to możliwe”.

Kylben
źródło