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();
}
}
Odpowiedzi:
Zwrot w metodzie void nie jest zły. Powszechną praktyką jest odwracanie
if
instrukcji w celu zmniejszenia zagnieżdżenia .Mniejsze zagnieżdżanie metod poprawia czytelność kodu i łatwość konserwacji.
W rzeczywistości, jeśli masz metodę void bez instrukcji return, kompilator zawsze wygeneruje instrukcję ret na końcu.
źródło
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.
źródło
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?
źródło
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.
źródło
Pierwszym przykładem jest użycie instrukcji guard. Z Wikipedii :
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(); } }
źródło
Nie ma spadku wydajności, jednak drugi fragment kodu jest bardziej czytelny, a przez to łatwiejszy w utrzymaniu.
źródło
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.
źródło
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.
źródło
{
. 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).if
instrukcje wymagają nawiasów, będzie łatwe, a zatem posiadanieif
kontroli instrukcji pojedynczej instrukcji będzie bezpieczne. Wciśnięcie otwartego nawiasu klamrowego z powrotem do wiersza zif
zachowuje linię pionowej spacji w każdym wyrażeniu wielokrotnymif
, ale będzie wymagało użycia niepotrzebnej linii w innym przypadku.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.
źródło
Korzystając ze strażników, pamiętaj o przestrzeganiu pewnych wskazówek, aby nie dezorientować czytelników.
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 ;
źródło
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ą.
źródło