Bardziej wydajne jest użycie if-return-return lub if-else-return?

141

Załóżmy, że mam ifoświadczenie z rozszerzeniem return. Z punktu widzenia wydajności powinienem użyć

if(A > B):
    return A+1
return A-1

lub

if(A > B):
    return A+1
else:
    return A-1

Czy powinienem preferować jeden czy inny język, używając języka skompilowanego (C) czy języka skryptowego (Python)?

Jorge Leitao
źródło
11
W języku kompilowanym nie musisz się zbytnio martwić o wydajność. Kompilator to rozwiązuje. Powinieneś napisać swój kod, abyś mógł go przeczytać. (Nadal musisz martwić się wydajnością swoich algorytmów, a niechlujne użycie typów itp. Wpłynie na wydajność - po prostu nie martw się zbytnio o swój styl). Nie wiem jednak o Pythonie.
AMS
5
Poleganie na kompilatorze w celu uporządkowania kodu jest niebezpiecznym krokiem - i wymaga niezawodnego kompilatora. Lepiej, jeśli wiesz, co ma robić Twój kod!
Andrew,
1
Jeśli to, co robisz, jest określone przez specyfikację, nie sądzę, aby istniał powód, aby wątpić w kompilator. Zostało napisane, że to ludzie znacznie mądrzejsi od ciebie i jest o wiele bardziej prawdopodobne, że popełniłeś błąd niż oni.
będzie
7
Jak można to zamknąć z powodu opinii? Może to być opinia, gdy już wiesz, że nie ma między nimi różnicy w wydajności. Nie, i jestem pewien, że wiele osób też nie.
Jorge Leitao
1
Chociaż pytanie jest dość popularne, nie można na nie udzielić dokładnej odpowiedzi bez uwzględnienia konkretnego języka, w przeciwnym razie udzielenie odpowiedzi w każdym języku byłoby zbyt długie dla tego formatu.
Emile Bergeron

Odpowiedzi:

195

Ponieważ returninstrukcja kończy wykonywanie bieżącej funkcji, obie formy są równoważne (chociaż druga jest prawdopodobnie bardziej czytelna niż pierwsza).

Wydajność obu form jest porównywalna, podstawowy kod maszynowy musi wykonać skok, jeśli ifwarunek i tak jest fałszywy.

Zwróć uwagę, że Python obsługuje składnię, która pozwala na użycie tylko jednej returninstrukcji w twoim przypadku:

return A+1 if A > B else A-1
Frédéric Hamidi
źródło
32
C też to obsługuje. return (A>B)?A+1:A-1;Jednak pisanie takiego kodu nie przynosi absolutnie żadnych korzyści . Wszystko, co osiągnęliśmy, to uczynić kod zaciemnionym, nieczytelnym, aw niektórych przypadkach bardziej podatnym na niejawne promocje typu.
Lundin,
47
@Lundin zaciemniony? nieczytelne? Tylko dla tych, którzy nie znają operatora trójskładnikowego.
glglgl
6
@Lundin Postępowanie zgodnie z tą argumentacją <jest złą praktyką, ponieważ -1 < 1udaje nieoczekiwany wynik.
glglgl
3
@glglgl: Nie, ponieważ ludzie oczekują, że operator?: będzie zachowywał się jak if-else, co nie jest prawdą. Gdyby ktoś napisał taki kod -1 < 1u, w co wątpię, z łatwością wykryłby błąd. Sporo ludzi napisałoby jednak jakąś wersję kodu, który opublikowałem. Widziałem takie błędy zbyt często w kodzie produkcyjnym, aby zaufać operatorowi?:. Z zasady, jeśli język daje ci dwa różne sposoby zrobienia tego samego, użyj tylko jednego z nich, nie wybieraj losowo żadnego z nich w zależności od nastroju.
Lundin
6
@Lundin to argument za ostrożnością?: W C, ale wydaje się, że mówisz, że dotyczy to również Pythona. Czy możesz wskazać jakieś przykłady, w których użycie trójargumentu w Pythonie prowadzi do nieoczekiwanych rezultatów?
lvc
33

Z przewodnika stylistycznego Chromium :

Nie używaj innego po zwrocie:

# Bad
if (foo)
  return 1
else
  return 2

# Good
if (foo)
  return 1
return 2

return 1 if foo else 2
skeller88
źródło
1
Dzięki. +1. Czy mogę zapytać, dlaczego nie używać innego po powrocie?
Tim
1
if-else jest funkcjonalnie równoważne, ale jest rozwlekłe. Inne jest niepotrzebne.
skeller88
17
Zdziwiłem się, bo pierwszy wydaje się wyraźniejszy, a przez to lepszy.
Tim
4
Możesz przedstawić rozsądne argumenty za jednym z nich. Najważniejszą rzeczą w tej decyzji IMO jest zachowanie spójności w ramach bazy kodowej.
skeller88
2
prawdopodobnie zauważysz w większości przypadków, że if-else-returngałęzie prawie nigdy nie są równe (jeśli tak, to i tak powinieneś refaktoryzować; albo używając switchkonstrukcji, albo dla Pythona, wyliczając dyktafon / używając wywoływalnego / itp.). Dlatego prawie wszystkie if-else-returnsą przypadkami klauzul ochronnych i te są zawsze testowalne (kpią z testowanego wyrażenia) bez rozszerzenia else.
cowbert
5

Odnośnie stylu kodowania:

Większość standardów kodowania bez względu na język zakazuje wielu instrukcji powrotu z jednej funkcji jako zła praktyka.

(Chociaż osobiście powiedziałbym, że istnieje kilka przypadków, w których wiele instrukcji powrotu ma sens: parsery protokołu tekstu / danych, funkcje z rozbudowaną obsługą błędów itp.)

Konsensus wszystkich tych branżowych standardów kodowania jest taki, że wyrażenie powinno być zapisane jako:

int result;

if(A > B)
{
  result = A+1;
}
else
{
  result = A-1;
}
return result;

Jeśli chodzi o wydajność:

Powyższy przykład i dwa przykłady w pytaniu są całkowicie równoważne pod względem wydajności. We wszystkich tych przypadkach kod maszynowy musi porównać A> B, następnie rozgałęzić się do obliczenia A + 1 lub A-1, a następnie zapisać wynik w rejestrze procesora lub na stosie.

EDYTOWAĆ :

Źródła:

  • MISRA-C: przepis 14.7 z 2004 r., Który z kolei cytuje ...:
  • IEC 61508-3. Część 3, tabela B.9.
  • IEC 61508-7. C.2.9.
Lundin
źródło
37
Czy na pewno religia pojedynczego powrotu zainfekowała większość standardów kodowania? To byłoby przerażające.
Daniel Fischer,
7
Powiedziałbym, że przez większość czasu ta reguła nie ma sensu. Uważam, że kod jest bardziej czytelny i łatwiejszy do śledzenia, a zwroty w odpowiednich punktach. Ale to tylko ja. Jednak myślałem o standardach kodowania według firmy / projektu, a nie o rzeczach takich jak MISRA, gdzie poza tym idiotyczne zalecenia mogą czasami mieć jakąś wartość. Mam nadzieję, że większość nie zaakceptowała idei pojedynczego punktu wyjścia.
Daniel Fischer,
3
@DanielFischer: W standardzie kodowania C opartym na MISRA, który zaprojektowałem dla mojej firmy, mam zasadę „Funkcja powinna mieć tylko jeden punkt wyjścia na końcu funkcji, chyba że jeden punkt wyjścia tworzy kod mniej czytelne ”. Jest to więc MISRA-C, ale z wyjątkiem reguły. Jeśli napiszesz zaawansowaną funkcję parsera, która może zwrócić powiedzmy 10 różnych błędów, poziom zagnieżdżonych nawiasów klamrowych sprawi, że kod będzie całkowicie nieczytelny - w takim przypadku rozsądniej byłoby natychmiast wrócić po napotkaniu błędu.
Lundin
6
Zobacz to pytanie SO do dyskusji i dalszych linków do dalszych dyskusji na temat pojedynczego punktu wyjścia. Poza tym, że reguła pojedynczego punktu wyjścia jest przestarzała i nadmiernie „inżynierska”, Python specjalnie promuje widok „mieszkanie jest lepsze niż zagnieżdżony” , a umieszczanie return tam , gdzie jest to jasne, jest idiomatycznym sposobem na zrobienie tego w Pythonie.
John Y
1
@percebus Całkowicie się zgadzam, a cykliczna złożoność jest dobrym argumentem przeciwko jednokrotnemu zwrotowi. I kilka razy podsłuchiwałem w tej sprawie komitet MISRA, na przykład zobacz to . Przynajmniej reguła została obniżona do doradczej w MISRA-C: 2012.
Lundin,
3

Z każdym rozsądnym kompilatorem nie powinno się zauważać żadnej różnicy; powinny być skompilowane do identycznego kodu maszynowego, ponieważ są równoważne.

Oliver Charlesworth
źródło
2

Jest to kwestia stylu (lub preferencji), ponieważ tłumaczowi to nie przeszkadza. Osobiście postarałbym się nie robić ostatniej instrukcji funkcji, która zwraca wartość na poziomie wcięcia innym niż podstawa funkcji. Inne w przykładzie 1 przesłania, choćby nieznacznie, gdzie znajduje się koniec funkcji.

Preferuję:

return A+1 if (A > B) else A-1

Ponieważ jest zgodny zarówno z dobrą konwencją posiadania pojedynczej instrukcji return jako ostatniej instrukcji w funkcji (jak już wspomniano), jak i dobrym paradygmatem programowania funkcjonalnego polegającym na unikaniu pośrednich wyników w stylu imperatywnym.

W przypadku bardziej złożonych funkcji wolę podzielić funkcję na wiele podfunkcji, aby uniknąć przedwczesnych zwrotów, jeśli to możliwe. W przeciwnym razie wracam do używania imperatywnej zmiennej stylu o nazwie rval. Staram się nie używać wielu instrukcji return, chyba że funkcja jest trywialna lub instrukcja return przed końcem jest wynikiem błędu. Przedwczesny powrót podkreśla fakt, że nie możesz iść dalej. W przypadku złożonych funkcji, które są zaprojektowane do rozgałęziania się na wiele podfunkcji, próbuję je zakodować jako instrukcje case (na przykład sterowane dyktowaniem).

Niektóre plakaty wspominały o szybkości działania. Szybkość działania jest dla mnie drugorzędna, ponieważ jeśli potrzebujesz szybkości wykonywania, Python nie jest najlepszym językiem do użycia. Używam Pythona, ponieważ jego efektywność kodowania (tj. Pisania kodu wolnego od błędów) jest dla mnie ważna.

Stephen Ellwood
źródło
1
Jeśli użytkownik ma zamiar głosować negatywnie na moją odpowiedź, byłbym wdzięczny za komentarz wyjaśniający, dlaczego myślał, że się myliłem.
Stephen Ellwood
Prawdopodobnie przedtem byłbym tylko wierszem, aby uzyskać 1 wiersz na instrukcję dla celów czytelności. var n = 1 if (A > B) else -1 return A+n
perbus
@percebus w niektórych przypadkach zgodziłbym się, czy nazwa zmiennej może wzmocnić znaczenie. Na przykład: 'code' move_x = 1 if my_x <opponent_x else -1 # rusz w kierunku przeciwnika
Stephen Ellwood
Przy okazji, zagłosowałem za twoją odpowiedzią. Jeśli widzisz, moja odpowiedź jest raczej podobna
postrzegam
2

Osobiście unikam elseblokad, jeśli to możliwe. Zobacz kampanię Anti-if

Poza tym nie pobierają „dodatkowej” opłaty za linię, wiesz: str

„Proste jest lepsze niż złożone” i „Czytelność jest najważniejsza”

delta = 1 if (A > B) else -1
return A + delta
postrzegać
źródło
2
Dlaczego głosowanie negatywne? Jest odpowiedzią „pytoniczną”. Możesz nie uznać tego za preferowaną odpowiedź. Ale nie jest to nieważne.
Przestrzegam
3
Głosowałem za twoją odpowiedzią, ponieważ dla mnie jest ona czytelna i prosta. Osobiście uważam, że obraźliwe jest to, że ktoś głosuje negatywnie na mnie, nie wyjaśniając mi, dlaczego moja odpowiedź jest aktywnie negatywna.
Stephen Ellwood
1
Nie słyszałem wcześniej o kampanii Anti-if, ale rozumiem, dlaczego „ifs” może być niebezpieczne. Zawsze staram się ograniczyć ilość kodu zawartego w instrukcji if i próbuję przepisać drzewa elif, aby używały dict. To jednak staje się trochę nie na temat.
Stephen Ellwood
1
@StephenEllwood Używanie dicts do unikania różnic jest bardzo złym pomysłem pod względem wydajności.
Bachsau
@Bachsau Prawdopodobnie masz rację. Nigdy nie musiałem martwić się o wydajność, ponieważ wszystkie moje skrypty działają w kilka sekund. Dla mnie czytelność zwykle przewyższa wydajność. Ponieważ nie jestem programistą na pełny etat; są tylko środkiem do celu.
Stephen Ellwood
1

Wersja A jest prostsza i dlatego bym jej użył.

A jeśli włączysz wszystkie ostrzeżenia kompilatora w Javie, otrzymasz ostrzeżenie o drugiej wersji, ponieważ jest to niepotrzebne i powoduje złożoność kodu.

juergen d
źródło
1

Wiem, że pytanie jest otagowane jako python, ale wspomina o językach dynamicznych, więc pomyślałem, że powinienem wspomnieć, że w języku ruby ​​instrukcja if faktycznie ma typ zwracany, więc możesz zrobić coś takiego

def foo
  rv = if (A > B)
         A+1
       else
         A-1
       end
  return rv 
end

Lub dlatego, że ma również niejawny zwrot po prostu

def foo 
  if (A>B)
    A+1
  else 
    A-1
  end
end

co całkiem dobrze rozwiązuje problem stylu polegający na tym, że nie ma wielu zwrotów.

Jamie Cook
źródło