Piszę program w Javie, w którym w pewnym momencie muszę załadować hasło do mojego magazynu kluczy. Dla zabawy starałem się, aby moje hasło w Javie było jak najkrótsze, wykonując następujące czynności:
//Some code
....
KeyManagerFactory keyManager = KeyManagerFactory.getInstance("SunX509");
Keystore keyStore = KeyStore.getInstance("JKS");
{
char[] password = getPassword();
keyStore.load(new FileInputStream(keyStoreLocation), password);
keyManager.init(keyStore, password);
}
...
//Some more code
Teraz wiem, że w tym przypadku to trochę głupie. Jest kilka innych rzeczy, które mogłem zrobić, większość z nich faktycznie lepiej (nie mogłem w ogóle użyć zmiennej).
Byłem jednak ciekawy, czy istnieje przypadek, w którym robienie tego nie było tak głupie. Jedyną rzeczą, o której mogę pomyśleć, jest to, czy chcesz ponownie użyć wspólnych nazw zmiennych, takich jak count
, lub temp
, ale dobre konwencje nazewnictwa i krótkie metody sprawiają, że jest to mało prawdopodobne.
Czy istnieje przypadek, w którym zastosowanie bloków tylko w celu zmniejszenia zakresu zmiennej ma sens?
Odpowiedzi:
Po pierwsze, rozmawiając z podstawową mechaniką:
W zakresie C ++ == dożywotnie b / c destruktory są wywoływane przy wyjściu z zakresu. Ponadto, ważne rozróżnienie w C / C ++ możemy deklarować obiekty lokalne. W środowisku wykonawczym dla C / C ++ kompilator ogólnie przydziela ramkę stosu dla metody, która jest tak duża, jak może być potrzebna, z wyprzedzeniem, zamiast przydzielać więcej miejsca na stosie przy wejściu do każdego zakresu (który deklaruje zmienne). Kompilator zwija się lub spłaszcza zakresy.
Kompilator C / C ++ może ponownie wykorzystać przestrzeń pamięci stosu dla lokalizacji lokalnych, które nie powodują konfliktu w czasie użytkowania (zazwyczaj użyje analizy rzeczywistego kodu w celu ustalenia tego, a nie zakresu, b / c, który jest dokładniejszy niż zakres!).
Wspominam o składni Java C / C ++ b / c, tj. Nawiasach klamrowych i określaniu zakresu, przynajmniej częściowo pochodzi z tej rodziny. A także dlatego, że C ++ pojawił się w komentarzach.
Natomiast nie można mieć obiektów lokalnych w Javie: wszystkie obiekty są obiektami sterty, a wszystkie obiekty lokalne / formale są albo zmiennymi referencyjnymi, albo typami pierwotnymi (btw, to samo dotyczy statyki).
Co więcej, w Javie zakres i czas życia nie są dokładnie zrównane: bycie w / poza zakresem jest przeważnie koncepcją czasu kompilacji dotyczącą dostępności i konfliktów nazw; tak naprawdę nic się nie dzieje w Javie po wyjściu z zakresu w odniesieniu do czyszczenia zmiennych. Wyrzucanie elementów bezużytecznych Java określa (punkt końcowy) żywotność obiektów.
Również mechanizm kodu bajtowego Javy (dane wyjściowe kompilatora Java) ma tendencję do promowania zmiennych użytkownika zadeklarowanych w ograniczonych zakresach do najwyższego poziomu metody, ponieważ nie ma funkcji zasięgu na poziomie kodu bajtowego (jest to podobne do obsługi C / C ++ stos). W najlepszym wypadku kompilator może ponownie wykorzystywać lokalne gniazda zmiennych, ale (w przeciwieństwie do C / C ++) tylko wtedy, gdy ich typ jest taki sam.
(Jednak, aby mieć pewność, że podstawowy kompilator JIT w środowisku wykonawczym może ponownie użyć tego samego miejsca do przechowywania stosu dla dwóch lokali o różnych typach (i różnych szczelinach), z wystarczającą analizą kodu bajtowego.)
Mówiąc o przewadze programisty, chciałbym zgodzić się z innymi, że (prywatna) metoda jest lepszą konstrukcją, nawet jeśli zostanie użyta tylko raz.
Nadal jednak nie ma nic złego w używaniu go do dekonfliktu nazw.
Zrobiłem to w rzadkich okolicznościach, gdy tworzenie indywidualnych metod jest ciężarem. Na przykład pisząc interpreter za pomocą dużej
switch
instrukcji w pętli, mogę (w zależności od czynników) wprowadzić osobny blok dla każdegocase
z nich, aby poszczególne przypadki były bardziej od siebie oddzielone, zamiast uczynić każdą sprawę inną metodą (każda który jest wywoływany tylko raz).(Zauważ, że jako kod w blokach poszczególne przypadki mają dostęp do instrukcji „break;” i „kontynuuj;” dotyczących otaczającej pętli, podczas gdy metody wymagałyby zwracania boolanów i użycia warunkowych w celu uzyskania dostępu do tych kontroli sprawozdania.)
źródło
{ int x = 42; String s="blah"; } { long y = 0; }
.long
Zmienna ponownie użyje szczelin obu zmiennychx
is
wszystkich powszechnie używanych kompilatorów (javac
iecj
).Moim zdaniem łatwiej byłoby wyciągnąć blok z własnej metody. Jeśli zostawiłeś ten blok, mam nadzieję, że zobaczysz komentarz wyjaśniający, dlaczego umieszczasz ten blok w pierwszej kolejności. W tym momencie po prostu czystsze jest posiadanie wywołania metody, w
initializeKeyManager()
którym zmienna hasła jest ograniczona do zakresu metody i pokazuje twoje zamiary za pomocą bloku kodu.źródło
Mogą być przydatne w Rust, z surowymi zasadami zaciągania pożyczek. I ogólnie w językach funkcjonalnych, w których ten blok zwraca wartość. Ale w językach takich jak Java? Chociaż pomysł wydaje się elegancki, w praktyce jest rzadko używany, więc zamieszanie związane z jego widzeniem zmiażdży potencjalną klarowność ograniczenia zakresu zmiennych.
Jest jednak jeden przypadek, w którym uważam to za przydatne - w
switch
oświadczeniu! Czasami chcesz zadeklarować zmienną wcase
:To jednak zawiedzie:
switch
instrukcje są dziwne - są instrukcjami kontrolnymi, ale z powodu ich upadku nie wprowadzają zakresów.Tak więc - możesz po prostu użyć bloków, aby samodzielnie utworzyć zakresy:
źródło
Ogólnie dobrą praktyką jest krótkie metody. Zmniejszenie zakresu niektórych zmiennych może być znakiem, że twoja metoda jest dość długa, wystarczająco, aby sądzić, że zakres zmiennych musi zostać zmniejszony. W takim przypadku prawdopodobnie warto utworzyć osobną metodę dla tej części kodu.
Moim zdaniem problematyczne są przypadki, gdy ta część kodu wykorzystuje więcej niż garść argumentów. Są sytuacje, w których możesz skończyć z małymi metodami, które wymagają wielu parametrów (lub wymagają obejścia, aby zasymulować zwracanie wielu wartości). Rozwiązaniem tego konkretnego problemu jest utworzenie kolejnej klasy, która przechowuje różne zmienne, których używasz, i implementuje wszystkie kroki, o których myślisz w swoich stosunkowo krótkich metodach: jest to typowy przypadek użycia wzorca konstruktora .
To powiedziawszy, wszystkie te wytyczne kodowania mogą być czasami stosowane luźno. Czasami zdarzają się przypadki, w których kod może być bardziej czytelny, jeśli trzymasz go w nieco dłuższej metodzie, zamiast dzielić go na małe pod-metody (lub wzorce konstruktora). Zazwyczaj działa to w przypadku problemów, które są średniej wielkości, dość liniowe i gdzie zbyt duże rozdzielenie faktycznie utrudnia czytelność (szczególnie, jeśli trzeba przeskakiwać między zbyt wieloma metodami, aby zrozumieć, co robi kod).
Może to mieć sens, ale jest bardzo rzadkie.
Oto twój kod:
Tworzysz
FileInputStream
, ale nigdy go nie zamykasz.Jednym ze sposobów rozwiązania tego jest użycie instrukcji try-with-resources . Obejmuje zakres
InputStream
, i możesz również użyć tego bloku, aby zmniejszyć zakres innych zmiennych, jeśli chcesz (w granicach rozsądku, ponieważ te linie są powiązane):(Nawiasem mówiąc, często lepiej jest używać
getDefaultAlgorithm()
zamiast tegoSunX509
.)Ponadto, mimo że porady dotyczące rozdzielania fragmentów kodu na osobne metody są ogólnie uzasadnione. Rzadko warto, jeśli dotyczy tylko 3 wierszy (jeśli tak, to przypadek, w którym utworzenie oddzielnej metody utrudnia czytelność).
źródło
Moim skromnym zdaniem, generalnie jest to coś, czego należy unikać głównie w przypadku kodu produkcyjnego, ponieważ generalnie nie kusi cię to, z wyjątkiem sporadycznych funkcji wykonujących różne zadania. Zwykle robię to w kodzie złomu używanym do testowania rzeczy, ale nie znajduję pokusy, aby to zrobić w kodzie produkcyjnym, w którym wcześniej zastanawiałem się, co powinna zrobić każda funkcja, ponieważ wtedy funkcja będzie naturalnie mieć bardzo ograniczony zakres w odniesieniu do jego stanu lokalnego.
Nigdy tak naprawdę nie widziałem przykładów użycia anonimowych bloków w ten sposób (nie obejmujących warunkowych,
try
bloku dla transakcji itp.) W celu znacznego ograniczenia zakresu w funkcji, która nie nasuwa pytania, dlaczego nie może podzielić dalej na prostsze funkcje o zmniejszonych zakresach, jeśli faktycznie skorzystało ono praktycznie z prawdziwego punktu widzenia SE z anonimowych bloków. Zwykle jest to eklektyczny kod, który robi wiele luźno powiązanych lub bardzo niezwiązanych ze sobą rzeczy, w których najbardziej kusi nas, aby po to sięgnąć.Na przykład, jeśli próbujesz to zrobić, aby ponownie użyć zmiennej o nazwie
count
, sugeruje to, że liczysz dwie różne rzeczy. Jeśli nazwa zmiennej ma być tak krótka jakcount
, to sensowne jest dla mnie powiązanie jej z kontekstem funkcji, która potencjalnie może po prostu liczyć jeden rodzaj rzeczy. Następnie możesz natychmiast spojrzeć na nazwę funkcji i / lub dokumentację, zobaczyćcount
i od razu wiedzieć, co to znaczy w kontekście tego, co robi funkcja, bez analizowania całego kodu. Często nie znajduję dobrego argumentu dla funkcji liczącej dwie różne rzeczy, które ponownie wykorzystują tę samą nazwę zmiennej w sposób, który sprawia, że anonimowe zakresy / bloki są tak atrakcyjne w porównaniu z alternatywami. To nie tak sugeruje, że wszystkie funkcje muszą liczyć tylko jedną rzecz. JA'korzyści inżynieryjne dla funkcji wykorzystującej tę samą nazwę zmiennej do zliczenia dwóch lub więcej rzeczy i używania anonimowych bloków w celu ograniczenia zakresu każdej pojedynczej liczby. Jeśli funkcja jest prosta i przejrzysta, to nie koniec świata, aby mieć dwie różnie nazwane zmienne zliczające, przy czym pierwsza może mieć kilka linii widoczności więcej, niż jest to wymagane. Takie funkcje zasadniczo nie są źródłem błędów pozbawionych takich anonimowych bloków, aby jeszcze bardziej ograniczyć minimalny zakres zmiennych lokalnych.Nie jest to sugestia dla zbytecznych metod
Nie ma to sugerować, że wymusisz tworzenie metod w celu zmniejszenia zasięgu. Jest to zapewne tak samo złe lub złe, i to, co sugeruję, nie powinno wymagać bardziej niezręcznych prywatnych metod „pomocnika” niż anonimowych zakresów. To za dużo myśli o obecnym kodzie i jak zmniejszyć zakres zmiennych, niż o koncepcyjnym rozwiązaniu problemu na poziomie interfejsu w sposób zapewniający naturalnie czystą, krótką widoczność lokalnych stanów funkcji bez głębokiego zagnieżdżania bloków i ponad 6 poziomów wcięcia. Zgadzam się z Bruno, że możesz utrudnić czytelność kodu, mocno wciskając 3 wiersze kodu do funkcji, ale zaczyna się to od założenia, że rozwijasz funkcje, które tworzysz w oparciu o istniejącą implementację, zamiast projektować funkcje bez angażowania się w implementacje. Jeśli robisz to w ten drugi sposób, nie ma potrzeby anonimowych bloków, które nie służą żadnemu celowi poza ograniczeniem zakresu zmiennej w ramach danej metody, chyba że próbujesz gorliwie zmniejszyć zakres zmiennej tylko o kilka mniej linii nieszkodliwego kodu gdzie egzotyczne wprowadzenie tych anonimowych bloków prawdopodobnie wnosi tyle samo intelektualnego obciążenia, ile usuwają.
Próbując jeszcze bardziej zmniejszyć minimalne zakresy
Jeśli warto było ograniczyć zakresy zmiennych lokalnych do absolutnego minimum, wówczas powinna istnieć szeroka akceptacja takiego kodu:
... ponieważ powoduje to minimalną widoczność stanu, nawet nie tworząc zmiennych odnoszących się do nich w pierwszej kolejności. Nie chcę być tak dogmatyczny, ale tak naprawdę uważam, że pragmatycznym rozwiązaniem jest unikanie anonimowych bloków, gdy jest to możliwe, tak jak unikanie monstrualnej linii kodu powyżej i jeśli wydają się one absolutnie konieczne w kontekście produkcyjnym z perspektywa poprawności i utrzymywania niezmienników w obrębie funkcji, to zdecydowanie uważam, że sposób organizacji kodu w funkcje i projektowania interfejsów jest warty ponownego zbadania. Oczywiście, jeśli twoja metoda ma 400 linii długości i zasięg zmiennej jest widoczny dla 300 linii kodu więcej niż jest to potrzebne, może to być prawdziwy problem inżynieryjny, ale niekoniecznie jest to problem do rozwiązania za pomocą anonimowych bloków.
Jeśli nic więcej, korzystanie z anonimowych bloków w całym miejscu jest egzotyczne, a nie idiomatyczne, a egzotyczny kod niesie ryzyko, że zostanie znienawidzony przez innych, jeśli nie przez ciebie, lata później.
Praktyczna przydatność ograniczenia zakresu
Ostateczną użytecznością ograniczenia zakresu zmiennych jest umożliwienie poprawnego zarządzania stanem i utrzymania go w prawidłowy sposób oraz umożliwienia łatwego uzasadnienia tego, co robi dana część bazy kodu - w celu utrzymania niezmienników koncepcyjnych. Jeśli lokalne zarządzanie stanem jednej funkcji jest tak złożone, że musisz siłą zmniejszyć zakres za pomocą anonimowego bloku kodu, który nie ma być sfinalizowany i dobry, to znowu jest to dla mnie znak, że sama funkcja musi zostać ponownie zbadana . Jeśli masz trudności z rozumowaniem zarządzania stanem zmiennych w lokalnym zakresie funkcji, wyobraź sobie trudność z rozumowaniem zmiennych prywatnych dostępnych dla każdej metody całej klasy. Nie możemy używać anonimowych bloków, aby zmniejszyć ich widoczność. Dla mnie pomaga zacząć od akceptacji, że zmienne będą miały nieco szerszy zakres, niż powinny mieć w wielu językach, pod warunkiem, że nie wymknie się to z sytuacji, w której będziesz miał trudności z utrzymaniem niezmienników. To nie jest coś do rozwiązania z anonimowymi blokami, ponieważ uważam to za akceptowalne z pragmatycznego punktu widzenia.
źródło
Myślę, że motywacja jest wystarczającym powodem do wprowadzenia bloku, tak.
Jak zauważył Casey, blok jest „zapachem kodu”, który wskazuje, że lepiej wyodrębnić blok do funkcji. Ale nie możesz.
John Carmark napisał notatkę na temat kodu wbudowanego w 2007 r. Podsumowując
Pomyśl więc , dokonaj wyboru celowo i (opcjonalnie) zostaw dowód opisujący twoją motywację do dokonania wyboru.
źródło
Moja opinia może być kontrowersyjna, ale tak, myślę, że z pewnością są sytuacje, w których użyłbym tego rodzaju stylu. Jednak „tylko ograniczenie zakresu zmiennej” nie jest poprawnym argumentem, ponieważ już to robisz. A robienie czegoś tylko po to, aby to zrobić, nie jest ważnym powodem. Zauważ też, że to wyjaśnienie nie ma znaczenia, jeśli Twój zespół już rozstrzygnął, czy tego rodzaju składnia jest konwencjonalna, czy nie.
Jestem przede wszystkim programistą C #, ale słyszałem, że w Javie zwracam hasło,
char[]
aby umożliwić skrócenie czasu pozostawania hasła w pamięci. W tym przykładzie nie pomoże, ponieważ śmieciarz może zbierać tablicę od momentu, gdy nie jest używana, więc nie ma znaczenia, czy hasło opuszcza zakres. Nie dyskutuję, czy opłaca się wyczyścić tablicę po jej zakończeniu, ale w tym przypadku, jeśli chcesz to zrobić, określenie zakresu zmiennej ma sens:Jest to bardzo podobne do instrukcji try-with-resources , ponieważ określa zasięg zasobu i wykonuje po nim finalizację. Jeszcze raz zauważ, że nie argumentuję, aby posługiwać się hasłami w ten sposób, tylko, że jeśli zdecydujesz się to zrobić, ten styl jest rozsądny.
Powodem tego jest to, że zmienna nie jest już ważna. Utworzyłeś go, użyłeś go i unieważniłeś jego stan, aby nie zawierał żadnych istotnych informacji. Nie ma sensu używać zmiennej po tym bloku, więc rozsądne jest jej zawężenie.
Innym przykładem, o którym mogę myśleć, jest sytuacja, gdy masz dwie zmienne o podobnej nazwie i znaczeniu, ale pracujesz z jedną, a potem z drugą, i chcesz je rozdzielić. Napisałem ten kod w C #:
Możesz argumentować, że mogę po prostu zadeklarować
ILGenerator il;
na początku metody, ale nie chcę również ponownie używać zmiennej dla różnych obiektów (trochę funkcjonalne podejście). W takim przypadku bloki ułatwiają rozdzielenie wykonywanych zadań, zarówno pod względem składniowym, jak i wizualnym. Mówi także, że po bloku jestem skończonyil
i nic nie powinno mieć do niego dostępu.Argumentem przeciwko temu przykładowi są metody. Może tak, ale w tym przypadku kod nie jest tak długi, a rozdzielenie go na różne metody wymagałoby również przekazania wszystkich zmiennych używanych w kodzie.
źródło