Czy używanie powrotu wewnątrz metody pustki jest złą praktyką?

92

Wyobraź sobie następujący kod:

void DoThis()
{
    if (!isValid) return;

    DoThat();
}

void DoThat() {
    Console.WriteLine("DoThat()");
}

Czy można użyć zwrotu w metodzie void? Czy ma to jakikolwiek spadek wydajności? Albo lepiej byłoby napisać taki kod:

void DoThis()
{
    if (isValid)
    {
        DoThat();
    }
}

źródło
1
A co z: void DoThis () {if (isValid) DoThat (); }
Dscoduc,
30
wyobraź sobie kod? Czemu? To jest tutaj! :-D
STW
To dobre pytanie, zawsze uważam, że używanie zwrotu jest dobrą praktyką; aby wyjść z metody lub funkcji. Zwłaszcza w metodzie wyszukiwania danych LINQ mającej wiele wyników IQueryable <T> i wszystkie z nich zależą od siebie. Jeśli jeden z nich nie przyniesie rezultatu, zaalarmuj i wyjdź.
Cheung

Odpowiedzi:

33

Jest jeszcze jeden ważny powód, dla którego warto używać strażników (w przeciwieństwie do kodu zagnieżdżonego): jeśli inny programista dodaje kod do twojej funkcji, pracuje w bezpieczniejszym środowisku.

Rozważać:

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }
}

przeciw:

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
}

Teraz wyobraź sobie, że inny programista dodaje linię: obj.DoSomethingElse ();

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }

    obj.DoSomethingElse();
}

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
    obj.DoSomethingElse();
}

Oczywiście jest to uproszczony przypadek, ale programista dodał awarię do programu w pierwszej (zagnieżdżonej instancji kodu). W drugim przykładzie (wczesne wyjście ze strażnikami), gdy miniesz strażnika, twój kod jest zabezpieczony przed niezamierzonym użyciem odwołania zerowego.

Jasne, świetny programista nie popełnia takich błędów (często). Ale lepiej zapobiegać niż leczyć - możemy napisać kod w sposób, który całkowicie eliminuje to potencjalne źródło błędów. Zagnieżdżanie zwiększa złożoność, dlatego najlepsze praktyki zalecają refaktoryzację kodu w celu ograniczenia zagnieżdżania.

Jason Williams
źródło
Tak, ale z drugiej strony kilka warstw zagnieżdżenia wraz z ich warunkami sprawia, że ​​kod jest jeszcze bardziej podatny na błędy, logika jest trudniejsza do śledzenia i - co ważniejsze - trudniejsza do debugowania. Płaskie funkcje są mniejszym złem, IMO.
Skrim
18
Ja argumentując na rzecz zmniejszonego gniazdowania! :-)
Jason Williams
Zgadzam się z tym. Ponadto z punktu widzenia refaktora łatwiej i bezpieczniej jest refaktoryzować metodę, jeśli obj stanie się strukturą lub coś, co możesz zagwarantować, że nie będzie miało wartości NULL.
Phil Cooper
18

Zła praktyka ??? Nie ma mowy. W rzeczywistości zawsze lepiej jest obsługiwać walidacje, wracając z metody najwcześniej, jeśli walidacja się nie powiedzie. W przeciwnym razie spowodowałoby to ogromną liczbę zagnieżdżonych instrukcji if i innych. Wczesne zakończenie poprawia czytelność kodu.

Sprawdź również odpowiedzi na podobne pytanie: Czy powinienem użyć instrukcji return / continue zamiast if-else?

Użytkownik SO
źródło
8

To nie jest zła praktyka (ze wszystkich powodów, które zostały już podane). Jednak im więcej zwrotów uzyskasz w metodzie, tym większe prawdopodobieństwo, że powinna zostać podzielona na mniejsze metody logiczne.

Mike Hall
źródło
8

Pierwszym przykładem jest użycie instrukcji guard. Z Wikipedii :

W programowaniu komputerowym strażnik jest wyrażeniem boolowskim, które musi mieć wartość true, jeśli wykonywanie programu ma być kontynuowane w danej gałęzi.

Myślę, że posiadanie grupy strażników na szczycie metody jest całkowicie zrozumiałym sposobem programowania. Zasadniczo oznacza to „nie wykonuj tej metody, jeśli którakolwiek z nich jest prawdziwa”.

Więc ogólnie chciałoby się to:

void DoThis()
{
  if (guard1) return;
  if (guard2) return;
  ...
  if (guardN) return;

  DoThat();
}

Myślę, że to o wiele bardziej czytelne niż wtedy:

void DoThis()
{
  if (guard1 && guard2 && guard3)
  {
    DoThat();
  }
}
cdmckay
źródło
3

Nie ma spadku wydajności, jednak drugi fragment kodu jest bardziej czytelny, a przez to łatwiejszy w utrzymaniu.

Russell
źródło
Russell, nie zgadzam się z twoją opinią, ale nie powinieneś być za nią przegłosowany. +1, aby to wyrównać. Btw, uważam, że test logiczny i powrót w jednym wierszu, po którym następuje pusty wiersz, jest wyraźnym wskazaniem, co się dzieje. np. pierwszy przykład Rodrigo.
Paul Sasik
Nie zgadzam się z tym. Zwiększenie zagnieżdżenia nie poprawia czytelności. Pierwszy fragment kodu używa instrukcji „guard”, która jest doskonale zrozumiałym wzorcem.
cdmckay
Ja też się nie zgadzam. Klauzule ochronne, które wcześnie wyłączają funkcję, są obecnie ogólnie uważane za dobrą rzecz w pomaganiu czytelnikowi w zrozumieniu implementacji.
Pete Hodgson
2

W tym przypadku twój drugi przykład to lepszy kod, ale nie ma to nic wspólnego z powrotem z funkcji void, po prostu dlatego, że drugi kod jest bardziej bezpośredni. Ale powrót z funkcji void jest całkowicie w porządku.

Imagista
źródło
0

Jest w porządku i nie ma „kary za wydajność”, ale nigdy, przenigdy nie pisz instrukcji „jeśli” bez nawiasów.

Zawsze

if( foo ){
    return;
}

Jest dużo bardziej czytelny; i nigdy nie założysz przypadkowo, że niektóre części kodu znajdują się w tej instrukcji, podczas gdy ich nie ma.

Noon Silk
źródło
2
czytelny jest subiektywny. imho, wszystko, co jest dodane do kodu, co jest niepotrzebne, czyni go mniej czytelnym ... (muszę czytać więcej, a potem zastanawiam się, dlaczego tam jest i tracę czas, próbując upewnić się, że czegoś nie brakuje) ... ale to moja subiektywna opinia
Charles Bretana
10
Lepszym powodem, aby zawsze uwzględniać szelki, jest mniej czytelność, a więcej bezpieczeństwo. Bez nawiasów klamrowych łatwo będzie komuś później naprawić błąd, który wymaga dodatkowych instrukcji w ramach if, nie zwracaj dostatecznej uwagi i dodawaj je bez dodawania nawiasów. Ryzyko to zostaje wyeliminowane przez zawsze zakładanie szelek.
Scott Dorman
2
Jedwabisty, proszę nacisnąć Enter przed {. To wyrównuje twój {z twoim }w tej samej kolumnie, co znacznie poprawia czytelność (znacznie łatwiej jest znaleźć odpowiednie klamry otwierające / zamykające).
Imagist
1
@Imagist Zostawię to do osobistych preferencji; i jest zrobione tak, jak lubię :)
Noon Silk
1
Jeśli każdy nawias zamykający zostanie dopasowany do nawiasu otwartego, który jest umieszczony na tym samym poziomie wcięcia , wówczas wizualne rozróżnienie, które ifinstrukcje wymagają nawiasów, będzie łatwe, a zatem posiadanie ifkontroli instrukcji pojedynczej instrukcji będzie bezpieczne. Wciśnięcie otwartego nawiasu klamrowego z powrotem do wiersza z ifzachowuje linię pionowej spacji w każdym wyrażeniu wielokrotnym if, ale będzie wymagało użycia niepotrzebnej linii w innym przypadku.
supercat
0

W tej sprawie nie zgodzę się z wami wszystkimi młodymi szarpaczami.

Używanie zwrotu w środku metody, pustej lub innej, jest bardzo złą praktyką, z powodów, które zostały wyartykułowane dość jasno, prawie czterdzieści lat temu, przez zmarłego Edsgera W. ”i kontynuuje w„ Structured Programming ”autorstwa Dahla, Dijkstry i Hoare'a.

Podstawowa zasada jest taka, że ​​każda struktura kontrolna i każdy moduł powinien mieć dokładnie jedno wejście i jedno wyjście. Wyraźny powrót w środku modułu łamie tę zasadę i znacznie utrudnia wnioskowanie o stanie programu, co z kolei znacznie utrudnia stwierdzenie, czy program jest poprawny, czy nie (co jest znacznie silniejszą właściwością niż „czy wydaje się działać, czy nie”).

„Oświadczenie GOTO uważane za szkodliwe” i „Programowanie strukturalne” zapoczątkowały rewolucję „programowania strukturalnego” w latach siedemdziesiątych. Te dwie części to powody, dla których mamy dziś jeśli-to-inaczej, podczas-do i inne jawne konstrukcje kontrolne, a także dlaczego instrukcje GOTO w językach wysokiego poziomu znajdują się na liście zagrożonych gatunków. (Osobiście uważam, że muszą znajdować się na liście gatunków wymarłych).

Warto zauważyć, że Message Flow Modulator, pierwsze oprogramowanie wojskowe, które ZAWSZE przeszło testy akceptacyjne za pierwszym razem, bez żadnych odchyleń, odstępstw lub „tak, ale” słownictwo, zostało napisane w języku, który nawet nie miał oświadczenie GOTO.

Warto również wspomnieć, że Nicklaus Wirth zmienił semantykę instrukcji RETURN w Oberon-07, najnowszej wersji języka programowania Oberon, czyniąc ją końcową częścią deklaracji procedury wpisanej (tj. Funkcji), a nie wykonywalna instrukcja w treści funkcji. Jego wyjaśnienie zmiany mówiło, że zrobił to właśnie dlatego, że poprzednia forma BYŁA naruszeniem zasady jednego wyjścia programowania strukturalnego.

John R. Strohm
źródło
2
@John: przekroczyliśmy zakaz Dykstry dotyczący wielokrotnych powrotów mniej więcej w czasie, gdy pokonaliśmy Pascala (w każdym razie większość z nas).
John Saunders,
Przypadki, w których wymagane są wielokrotne zwroty, często wskazują, że metoda próbuje zrobić zbyt dużo i należy ją ograniczyć. Nie mam zamiaru posuwać się w tym do Johna, a instrukcja return jako część walidacji parametrów może być rozsądnym wyjątkiem, ale rozumiem, skąd pochodzi pomysł.
kyoryu
@nairdaen: Nadal istnieją kontrowersje dotyczące wyjątków w tym kwartale. Moja wskazówka jest taka: jeśli rozwijany system MUSI naprawić problem, który spowodował wyjątkowy stan, a ja nie mam nic przeciwko wkurzaniu faceta, który będzie musiał napisać ten kod, rzucę wyjątek. Potem krzyczono na mnie na spotkaniu, ponieważ facet nie zadał sobie trudu, aby złapać wyjątek, a aplikacja zawiesiła się podczas testów, a ja wyjaśniam, DLACZEGO musi naprawić problem, i wszystko się uspokaja.
John R. Strohm
Istnieje duża różnica między instrukcjami guard a gotos. Zło w gotos polega na tym, że mogą skakać w dowolnym miejscu, więc ich rozwikłanie i zapamiętanie może być bardzo mylące. Strażnicy są dokładnie odwrotni - zapewniają bramkowane wejście do metody, po którym wiesz, że pracujesz w „bezpiecznym” środowisku, zmniejszając liczbę rzeczy, które musisz wziąć pod uwagę podczas pisania reszty kodu (np. „Wiem, że ten wskaźnik nigdy nie będzie pusty, więc nie muszę zajmować się tym przypadkiem w całym kodzie”).
Jason Williams
@Jason: Pierwotne pytanie nie dotyczyło konkretnie instrukcji guard, ale losowych instrukcji zwrotnych w środku metody. Podany przykład wydaje się być strażnikiem. Kluczową kwestią jest to, że w miejscu zwrotu chcesz być w stanie zrozumieć, co zrobiła, a czego nie zrobiła metoda, a losowe zwroty utrudniają to, z DOKŁADNIE tych samych powodów, dla których losowe GOTO utrudniają. Zobacz: Dijkstra, „Oświadczenie GOTO uważane za szkodliwe”. Jeśli chodzi o składnię, cdmckay podał w innej odpowiedzi swoją preferowaną składnię dla strażników; Nie zgadzam się z jego opinią, która forma jest bardziej czytelna.
John R. Strohm,
0

Korzystając ze strażników, pamiętaj o przestrzeganiu pewnych wskazówek, aby nie dezorientować czytelników.

  • funkcja robi jedną rzecz
  • strażnicy są wprowadzani tylko jako pierwsza logika funkcji
  • unnested część zawiera funkcję za podstawową intencję

Przykład

// guards point you to the core intent
void Remove(RayCastResult rayHit){

  if(rayHit== RayCastResult.Empty)
    return
    ;
  rayHit.Collider.Parent.Remove();
}

// no guards needed: function split into multiple cases
int WonOrLostMoney(int flaw)=>
  flaw==0 ? 100 :
  flaw<10 ? 30 :
  flaw<20 ? 0 :
  -20
;
Orangle
źródło
-3

Rzuć wyjątek zamiast zwracać nic, gdy obiekt ma wartość null itp.

Twoja metoda oczekuje, że obiekt nie będzie null i tak nie jest, więc powinieneś zgłosić wyjątek i pozwolić wywołującemu obsłużyć to.

W przeciwnym razie wczesny powrót nie jest złą praktyką.

Dhananjay
źródło
1
Odpowiedź nie odpowiada na pytanie. Pytanie jest metodą void, więc nic nie zostanie zwrócone. Ponadto metody nie mają parametrów. Rozumiem, że nie zwracam wartości null, jeśli typ zwracany jest obiektem, ale to nie dotyczy tego pytania.
Luke Hammer