Kiedy uruchomiłem ReSharper na moim kodzie, na przykład:
if (some condition)
{
Some code...
}
ReSharper dał mi powyższe ostrzeżenie (polecenie Invert „if”, aby zmniejszyć zagnieżdżanie) i zasugerował następującą korektę:
if (!some condition) return;
Some code...
Chciałbym zrozumieć, dlaczego tak jest lepiej. Zawsze myślałem, że użycie „return” w środku metody problematycznej, trochę jak „goto”.
ASSERT( exampleParam > 0 )
.Odpowiedzi:
Zwrot w środku metody niekoniecznie jest zły. Lepszym rozwiązaniem może być natychmiastowy powrót, jeśli pozwoli to na wyjaśnienie intencji kodu. Na przykład:
W takim przypadku, jeśli
_isDead
jest to prawda, możemy natychmiast wyjść z metody. Zamiast tego lepiej byłoby zbudować go w ten sposób:Wybrałem ten kod z katalogu refaktoryzacji . To specyficzne refaktoryzacja nazywa się: Zamień zagnieżdżone warunkowe klauzulami ochronnymi.
źródło
Jest to nie tylko estetyczne , ale także zmniejsza maksymalny poziom zagnieżdżenia w metodzie. Jest to ogólnie uważane za plus, ponieważ ułatwia zrozumienie metod (a w rzeczywistości wiele narzędzi do analizy statycznej zapewnia to jako jeden ze wskaźników jakości kodu).
Z drugiej strony sprawia, że twoja metoda ma wiele punktów wyjścia, co zdaniem innej grupy osób jest nie-nie.
Osobiście zgadzam się z ReSharper i pierwszą grupą (w języku, który ma wyjątki, głupotą jest omawianie „wielu punktów wyjścia”; prawie wszystko może rzucić, więc istnieje wiele potencjalnych punktów wyjścia we wszystkich metodach).
Jeśli chodzi o wydajność : obie wersje powinny być równoważne (jeśli nie na poziomie IL, to na pewno po zakończeniu fluktuacji kodem) w każdym języku. Teoretycznie zależy to od kompilatora, ale praktycznie każdy powszechnie używany obecnie kompilator jest w stanie obsłużyć znacznie bardziej zaawansowane przypadki optymalizacji kodu niż ten.
źródło
To trochę religijny argument, ale zgadzam się z ReSharper, że powinieneś preferować mniej zagnieżdżania. Uważam, że przewyższa to negatywne cechy wielu ścieżek powrotnych z funkcji.
Głównym powodem mniejszego zagnieżdżenia jest poprawa czytelności kodu i łatwości konserwacji . Pamiętaj, że wielu innych programistów będzie musiało przeczytać Twój kod w przyszłości, a kod z mniejszymi wcięciami jest ogólnie znacznie łatwiejszy do odczytania.
Warunki wstępne są doskonałym przykładem tego, gdzie można powrócić wcześniej na początku funkcji. Dlaczego na sprawdzenie reszty funkcji powinien mieć wpływ sprawdzanie warunków wstępnych?
Jeśli chodzi o negatywy dotyczące wielokrotnego powrotu z metody - debuggery są teraz dość potężne i bardzo łatwo jest dowiedzieć się, gdzie i kiedy konkretna funkcja powraca.
Posiadanie wielu zwrotów w funkcji nie wpłynie na pracę programisty konserwacji.
Słaba czytelność kodu będzie.
źródło
Jak wspomnieli inni, nie powinno być hitów wydajnościowych, ale są też inne względy. Oprócz tych ważnych obaw, może to również otworzyć cię na gotchas w niektórych okolicznościach. Załóżmy, że
double
zamiast tego masz do czynienia z :Porównaj to z pozornie równoważną inwersją:
Więc w pewnych okolicznościach to, co wydaje się być właściwie odwrócone,
if
może nie być.źródło
Pomysł powrotu po zakończeniu funkcji wrócił z dni, kiedy języki miały obsługę wyjątków. Umożliwiło to programom poleganie na możliwości umieszczenia kodu czyszczącego na końcu metody, a następnie upewnienie się, że zostanie wywołany, a inny programista nie ukryłby powrotu w metodzie, która spowodowała pominięcie kodu czyszczenia . Pominięty kod czyszczenia może spowodować wyciek pamięci lub zasobów.
Jednak w języku, który obsługuje wyjątki, nie zapewnia takich gwarancji. W języku obsługującym wyjątki wykonanie dowolnej instrukcji lub wyrażenia może spowodować przepływ sterowania, który spowoduje zakończenie metody. Oznacza to, że czyszczenie musi zostać wykonane przy użyciu słów kluczowych lub słów kluczowych.
W każdym razie mówię, że myślę, że wiele osób powołuje się na wytyczną dotyczącą „jedynego zwrotu na końcu metody”, nie rozumiejąc, dlaczego warto to robić, i że zmniejszenie zagnieżdżania w celu poprawy czytelności jest prawdopodobnie lepszym celem.
źródło
If you've got deep nesting, maybe your function is trying to do too many things.
=> to nie jest poprawna konsekwencja z poprzedniej frazy. Ponieważ tuż przed stwierdzeniem, że można przefakturować zachowanie A z kodem C na zachowanie A z kodem D. kod D jest czystszy, przyznany, ale „zbyt wiele rzeczy” odnosi się do zachowania, które NIE uległo zmianie. Dlatego te wnioski nie mają żadnego sensu.Chciałbym dodać, że istnieje nazwa dla tych odwróconych, jeśli… - Klauzula Strażnicza. Używam go, kiedy tylko mogę.
Nienawidzę czytać kodu tam, gdzie jest, jeśli na początku dwa ekrany kodu i nic więcej. Po prostu odwróć jeśli i wróć. W ten sposób nikt nie będzie tracić czasu na przewijanie.
http://c2.com/cgi/wiki?GuardClause
źródło
if
s lub nie. lolWpływa nie tylko na estetykę, ale także zapobiega zagnieżdżaniu kodu.
Może faktycznie działać jako warunek wstępny, aby upewnić się, że Twoje dane są również prawidłowe.
źródło
Jest to oczywiście subiektywne, ale myślę, że zdecydowanie poprawia się w dwóch punktach:
condition
wstrzymana.źródło
Wiele punktów powrotu stanowiło problem w C (iw mniejszym stopniu C ++), ponieważ zmusiło cię do zduplikowania kodu czyszczenia przed każdym z punktów powrotu. Po wyrzucaniu elementów bezużytecznych
try
|finally
budować iusing
blokować, naprawdę nie ma powodu, aby się ich bać.Ostatecznie sprowadza się to do tego, co Ty i Twoi koledzy uważacie za łatwiejsze do odczytania.
źródło
a loop that is not vectorizable due to a second data-dependent exit
Pod względem wydajności nie będzie zauważalnej różnicy między tymi dwoma podejściami.
Ale kodowanie to coś więcej niż wydajność. Jasność i łatwość konserwacji są również bardzo ważne. I w takich przypadkach, w których nie wpływa to na wydajność, liczy się tylko to.
Istnieją konkurencyjne szkoły myślenia, które podejście jest lepsze.
Jeden widok jest tym, o którym wspominali inni: drugie podejście zmniejsza poziom zagnieżdżenia, co poprawia przejrzystość kodu. Jest to naturalne w imperatywnym stylu: gdy nie masz już nic do roboty, równie dobrze możesz wrócić wcześniej.
Innym poglądem, z perspektywy bardziej funkcjonalnego stylu, jest to, że metoda powinna mieć tylko jeden punkt wyjścia. Wszystko w funkcjonalnym języku jest wyrażeniem. Więc jeśli instrukcje muszą zawsze zawierać klauzule else. W przeciwnym razie wyrażenie if nie zawsze miałoby wartość. Tak więc w funkcjonalnym stylu pierwsze podejście jest bardziej naturalne.
źródło
Klauzule ochronne lub warunki wstępne (jak zapewne widać) sprawdzają, czy określony warunek jest spełniony, a następnie przerywa działanie programu. Doskonale nadają się do miejsc, w których naprawdę interesuje Cię tylko jeden wynik
if
wypowiedzi. Zamiast więc mówić:Odwracasz warunek i łamiesz się, jeśli ten odwrócony warunek jest spełniony
return
nie jest tak brudny jakgoto
. Pozwala przekazać wartość pokazującą resztę kodu, której funkcja nie mogła uruchomić.Zobaczysz najlepsze przykłady zastosowania tego w warunkach zagnieżdżonych:
vs:
Kilka osób twierdzi, że pierwsza jest czystsza, ale oczywiście całkowicie subiektywna. Niektórzy programiści lubią wiedzieć, w jakich warunkach coś działa przez wcięcia, podczas gdy wolałbym raczej liniowy przepływ metod.
Ani razu nie zasugeruję, że preony zmienią twoje życie lub spowodują, że cię położysz, ale twój kod może być nieco łatwiejszy do odczytania.
źródło
Jest tu kilka dobrych punktów, ale wiele punktów zwrotnych może być również nieczytelnych , jeśli metoda jest bardzo długa. Biorąc to pod uwagę, jeśli zamierzasz użyć wielu punktów zwrotnych, po prostu upewnij się, że twoja metoda jest krótka, w przeciwnym razie premia za czytelność wielu punktów zwrotnych może zostać utracona.
źródło
Wydajność składa się z dwóch części. Masz wydajność, gdy oprogramowanie jest w produkcji, ale chcesz też mieć wydajność podczas programowania i debugowania. Ostatnią rzeczą, jakiej chce deweloper, jest „czekanie” na coś trywialnego. Ostatecznie skompilowanie tego z włączoną optymalizacją da podobny kod. Warto więc poznać te małe sztuczki, które się opłacają w obu scenariuszach.
Sprawa w pytaniu jest jasna, ReSharper ma rację. Zamiast zagnieżdżać
if
instrukcje i tworzyć nowy zakres w kodzie, ustawiasz jasną regułę na początku swojej metody. Zwiększa czytelność, będzie łatwiejsza w utrzymaniu i zmniejszy liczbę zasad, które należy przeszukać, aby znaleźć miejsce, do którego chcą się udać.źródło
Osobiście wolę tylko 1 punkt wyjścia. Jest to łatwe do osiągnięcia, jeśli twoje metody są krótkie i do rzeczy, i zapewnia przewidywalny wzorzec dla następnej osoby, która pracuje nad twoim kodem.
na przykład.
Jest to również bardzo przydatne, jeśli chcesz tylko sprawdzić wartości niektórych zmiennych lokalnych w funkcji przed jej wyjściem. Wszystko, co musisz zrobić, to umieścić punkt przerwania przy ostatnim powrocie i masz gwarancję, że go trafisz (chyba że zostanie zgłoszony wyjątek).
źródło
Wiele dobrych powodów, jak wygląda kod . Ale co z wynikami ?
Rzućmy okiem na kod C # i jego skompilowaną formę IL:
Ten prosty fragment kodu można skompilować. Możesz otworzyć wygenerowany plik .exe z ildasm i sprawdzić, jaki jest wynik. Nie opublikuję wszystkich elementów asemblera, ale opiszę wyniki.
Wygenerowany kod IL wykonuje następujące czynności:
Wygląda więc na to, że kod przeskoczy do końca. Co się stanie, jeśli wykonamy normalny kod zagnieżdżony?
Wyniki są dość podobne w instrukcjach IL. Różnica polega na tym, że wcześniej istniały skoki dla każdego warunku: jeśli false, przejdź do następnego fragmentu kodu, jeśli true, przejdź do końca. A teraz kod IL płynie lepiej i ma 3 skoki (kompilator nieco to zoptymalizował): 1. Pierwszy skok: gdy Długość wynosi 0 do części, w której kod przeskakuje ponownie (Trzeci skok) do końca. 2. Drugi: w środku drugiego warunku, aby uniknąć jednej instrukcji. 3. Po trzecie: jeśli drugi warunek jest fałszywy, przeskocz na koniec.
W każdym razie licznik programów zawsze będzie skakał.
źródło
Teoretycznie odwracanie
if
może prowadzić do lepszej wydajności, jeśli zwiększa wskaźnik trafień przewidywania gałęzi. W praktyce myślę, że bardzo trudno jest dokładnie wiedzieć, jak zachowa się przewidywanie gałęzi, szczególnie po kompilacji, więc nie robiłbym tego w codziennym rozwoju, chyba że piszę kod asemblera.Więcej na temat przewidywania oddziałów tutaj .
źródło
To jest po prostu kontrowersyjne. Nie ma „porozumienia między programistami” w kwestii wczesnego powrotu. O ile mi wiadomo, jest to zawsze subiektywne.
Można przedstawić argument dotyczący wydajności, ponieważ lepiej jest mieć napisane warunki, aby były one najczęściej prawdziwe; Można również argumentować, że jest to jaśniejsze. Z drugiej strony tworzy testy zagnieżdżone.
Nie sądzę, żebyś uzyskał rozstrzygającą odpowiedź na to pytanie.
źródło
Istnieje już wiele wnikliwych odpowiedzi, ale nadal chciałbym skierować się do nieco innej sytuacji: zamiast warunku wstępnego, który powinien być rzeczywiście nadpisany funkcji, pomyśl o inicjalizacji krok po kroku, w której muszę sprawdzić, czy każdy krok się powiódł, a następnie przejść do następnego. W takim przypadku nie można sprawdzić wszystkiego u góry.
Mój kod był naprawdę nieczytelny podczas pisania aplikacji hosta ASIO za pomocą ASIOSDK Steinberga, ponieważ postępowałem zgodnie z paradygmatem zagnieżdżania. Osiągnęło głębokość ośmiu poziomów i nie widzę tam wady projektowej, o czym wspomniał Andrew Bullock powyżej. Oczywiście mogłem spakować jakiś wewnętrzny kod do innej funkcji, a następnie zagnieździć tam pozostałe poziomy, aby był bardziej czytelny, ale wydaje mi się to dość losowe.
Zastępując zagnieżdżanie klauzulami ochronnymi, odkryłem nawet moje błędne przekonanie dotyczące części kodu czyszczącego, które powinno było nastąpić znacznie wcześniej w funkcji zamiast na końcu. Z zagnieżdżonymi gałęziami nigdy bym tego nie widział, można nawet powiedzieć, że doprowadziły do mojego błędnego przekonania.
Może to być kolejna sytuacja, w której odwrócone ifs mogą przyczynić się do wyraźniejszego kodu.
źródło
Unikanie wielu punktów wyjścia może prowadzić do wzrostu wydajności. Nie jestem pewien co do C #, ale w C ++ Optymalizacja nazwanych wartości zwrotnych (Copy Elision, ISO C ++ '03 12.8 / 15) zależy od posiadania pojedynczego punktu wyjścia. Ta optymalizacja pozwala uniknąć tworzenia kopii zwracanej przez kopię (w twoim przykładzie nie ma to znaczenia). Może to prowadzić do znacznego wzrostu wydajności w ciasnych pętlach, ponieważ zapisujesz konstruktor i destruktor za każdym razem, gdy funkcja jest wywoływana.
Jednak w 99% przypadków zapisanie dodatkowych wywołań konstruktora i destruktora nie jest warte utraty
if
wprowadzonych bloków czytelności (jak zauważyli inni).źródło
To kwestia opinii.
Moim normalnym podejściem byłoby unikanie pojedynczej linii ifs i powrót w trakcie metody.
Nie chciałbyś linii takich jak sugeruje to wszędzie w twojej metodzie, ale jest coś do powiedzenia na temat sprawdzania szeregu założeń u góry metody i wykonywania rzeczywistej pracy tylko wtedy, gdy wszystkie przejdą pomyślnie.
źródło
Moim zdaniem wczesny zwrot jest w porządku, jeśli zwracasz tylko wartość void (lub jakiś bezużyteczny kod powrotu, którego nigdy nie będziesz sprawdzać) i może poprawić czytelność, ponieważ unikasz zagnieżdżania, a jednocześnie wyraźnie zaznaczasz, że funkcja została wykonana.
Jeśli faktycznie zwracasz wartość returnValue - zagnieżdżanie jest zwykle lepszym sposobem, ponieważ zwracasz wartość returnValue tylko w jednym miejscu (na końcu - duh), i może sprawić, że twój kod będzie łatwiejszy w utrzymaniu w wielu przypadkach.
źródło
Nie jestem pewien, ale myślę, że R # stara się unikać dalekich skoków. Gdy masz IF-ELSE, kompilator robi coś takiego:
Warunek false -> daleko przeskocz do false_condition_label
true_condition_label: instrukcja1 ... instrukcja_n
false_condition_label: instrukcja1 ... instrukcja_n
blok końcowy
Jeśli warunek jest spełniony, nie ma skoku i nie ma rozwijanej pamięci podręcznej L1, ale skok do etykiety fałsz-warunek może być bardzo daleko i procesor musi rozwinąć swoją własną pamięć podręczną. Synchronizacja pamięci podręcznej jest droga. R # próbuje zastąpić dalekie skoki krótkimi skokami, w tym przypadku istnieje większe prawdopodobieństwo, że wszystkie instrukcje są już w pamięci podręcznej.
źródło
Myślę, że to zależy od tego, co wolisz, jak wspomniano, nie ma ogólnej zgody afaik. Aby zmniejszyć annoyment, możesz ograniczyć tego rodzaju ostrzeżenie do „Podpowiedzi”
źródło
Mój pomysł jest taki, że zwrot „w środku funkcji” nie powinien być tak „subiektywny”. Powód jest dość prosty, weź ten kod:
Może pierwszy „powrót” nie jest TAK intuicyjny, ale to naprawdę oszczędza. Jest zbyt wiele „pomysłów” na czyste kody, które po prostu wymagają więcej praktyki, aby stracić swoje „subiektywne” złe pomysły.
źródło
Istnieje kilka zalet tego rodzaju kodowania, ale dla mnie dużą wygraną jest to, że jeśli szybko wrócisz, możesz poprawić szybkość aplikacji. IE Wiem, że dzięki Warunkowi X mogę szybko wrócić z błędem. Najpierw usuwa się przypadki błędów i zmniejsza złożoność kodu. W wielu przypadkach, ponieważ potok procesora może być teraz czystszy, może zatrzymać awarie lub przełączenia potoku. Po drugie, jeśli jesteś w pętli, szybkie zerwanie lub powrót może zaoszczędzić sporo procesora. Niektórzy programiści używają niezmienników pętli do wykonania tego rodzaju szybkiego wyjścia, ale w tym przypadku możesz przerwać procesor procesora, a nawet stworzyć problem z wyszukiwaniem pamięci, co oznacza, że procesor musi zostać załadowany z zewnętrznej pamięci podręcznej. Ale w zasadzie uważam, że powinieneś robić to, co zamierzałeś, to znaczy, że pętla lub funkcja nie tworzą złożonej ścieżki kodu, aby zaimplementować abstrakcyjne pojęcie poprawnego kodu. Jeśli jedynym narzędziem, które masz, jest młotek, wtedy wszystko wygląda jak gwóźdź.
źródło