Czy funkcja powinna mieć tylko jedną instrukcję return?

780

Czy istnieją dobre powody, dla których lepiej jest mieć tylko jedną instrukcję return w funkcji?

Czy jest w porządku, aby powrócić z funkcji, gdy jest to logicznie poprawne, co oznacza, że ​​może być wiele instrukcji return w funkcji?

Tim Post
źródło
25
Nie zgadzam się, że pytanie jest niezależne od języka. W niektórych językach wielokrotne zwroty są bardziej naturalne i wygodne niż w innych. Byłbym bardziej skłonny narzekać na wcześniejsze zwroty w funkcji C niż w C ++ korzystającej z RAII.
Adrian McCarthy
3
Jest to ściśle powiązane i ma doskonałe odpowiedzi: programmers.stackexchange.com/questions/118703/…
Tim Schmelter
język-agnostyk? Wyjaśnij komuś, kto używa języka funkcjonalnego, że musi użyć jednego zwrotu na funkcję: p
Boiethios

Odpowiedzi:

741

Często mam kilka stwierdzeń na początku metody powrotu do „łatwych” sytuacji. Na przykład:

public void DoStuff(Foo foo)
{
    if (foo != null)
    {
        ...
    }
}

... można uczynić bardziej czytelnym (IMHO) w następujący sposób:

public void DoStuff(Foo foo)
{
    if (foo == null) return;

    ...
}

Więc tak, myślę, że dobrze jest mieć wiele „punktów wyjścia” z funkcji / metody.

Matt Hamilton
źródło
83
Zgoda. Chociaż posiadanie wielu punktów wyjścia może wymknąć się spod kontroli, zdecydowanie uważam, że jest to lepsze niż umieszczenie całej funkcji w bloku IF. Używaj return tak często, jak to ma sens, aby utrzymać czytelność kodu.
Joshua Carmody,
172
Nazywa się to „oświadczeniem ochronnym” - Refaktoryzacja Fowlera.
Lars Westergren,
12
Gdy funkcje są utrzymywane stosunkowo krótko, nie jest trudno podążać za strukturą funkcji z punktem powrotu w pobliżu środka.
KJAWolf
21
Ogromny blok instrukcji if-else, każda ze zwrotem? To nic. Taka rzecz jest zwykle łatwo refaktoryzowana. (przynajmniej częstsza jest odmiana pojedynczego wyjścia ze zmienną wynikową, więc wątpię, czy wariant wielokrotnego wyjścia byłby trudniejszy.) Jeśli chcesz prawdziwego bólu głowy, spójrz na kombinację pętli if-else i while (kontrolowanych przez lokalne booleany), w których ustawiane są zmienne wynikowe, prowadzące do końcowego punktu wyjścia na końcu metody. To jest pomysł na jedno wyjście, który oszalał i tak, mówię z faktycznego doświadczenia, które musiałem sobie z tym poradzić.
Marcus Andrén
7
„Wyobraź sobie: musisz wykonać metodę„ IncreaseStuffCallCounter ”na końcu funkcji„ DoStuff ”. Co zrobisz w tym przypadku? :) '-DoStuff() { DoStuffInner(); IncreaseStuffCallCounter(); }
Jim Balter
355

Nikt nie wspomniał ani nie podał kodu Complete, więc zrobię to.

17.1 powrót

Zminimalizuj liczbę zwrotów w każdej procedurze . Trudniej jest zrozumieć rutynę, jeśli czytając ją na dole, nie jesteś świadomy możliwości, że powróciła gdzieś powyżej.

Użyj znaku powrotu, gdy poprawia to czytelność . W niektórych procedurach, gdy znasz odpowiedź, chcesz ją natychmiast przywrócić do procedury wywoływania. Jeśli procedura jest zdefiniowana w taki sposób, że nie wymaga żadnego czyszczenia, brak powrotu natychmiast oznacza, że ​​musisz napisać więcej kodu.

Chris S.
źródło
64
+1 za niuans „minimalizuj”, ale nie zabrania wielokrotnych zwrotów.
Raedwald,
13
„trudniejszy do zrozumienia” jest bardzo subiektywny, szczególnie gdy autor nie przedstawia żadnych dowodów empirycznych na poparcie ogólnego twierdzenia ... posiadanie jednej zmiennej, która musi być ustawiona w wielu lokalizacjach w kodzie dla końcowego oświadczenia zwrotnego, jest również poddawana „nie zdawał sobie sprawy z możliwości przypisania zmiennej gdzieś powyżej int!”
Heston T. Holtmann
26
Osobiście wolę wracać wcześniej, jeśli to możliwe. Dlaczego? Cóż, gdy zobaczysz słowo kluczowe return dla danego przypadku, natychmiast wiesz, że „jestem skończony” - nie musisz czytać dalej, aby dowiedzieć się, co, jeśli w ogóle, nastąpi.
Mark Simpson
12
@ HestonT.Holtmann: Co sprawiło, że Code Complete był wyjątkowy wśród książek programistycznych, to że rada jest poparta dowodami empirycznymi.
Adrian McCarthy
9
To prawdopodobnie powinna być zaakceptowana odpowiedź, ponieważ wspomina, że ​​posiadanie wielu punktów zwrotnych nie zawsze jest dobre, ale czasem konieczne.
Rafid
229

Powiedziałbym, że niezwykle rozsądne byłoby arbitralne podejmowanie decyzji przeciwko wielu punktom wyjścia, ponieważ uważałem, że technika ta jest przydatna w praktyce w kółko , w rzeczywistości często przebudowałem istniejący kod do wielu punktów wyjścia dla zachowania przejrzystości. Możemy porównać dwa podejścia w ten sposób:

string fooBar(string s, int? i) {
  string ret = "";
  if(!string.IsNullOrEmpty(s) && i != null) {
    var res = someFunction(s, i);

    bool passed = true;
    foreach(var r in res) {
      if(!r.Passed) {
        passed = false;
        break;
      }
    }

    if(passed) {
      // Rest of code...
    }
  }

  return ret;
}

Porównaj to z kodem, w którym dozwolonych jest wiele punktów wyjścia :

string fooBar(string s, int? i) {
  var ret = "";
  if(string.IsNullOrEmpty(s) || i == null) return null;

  var res = someFunction(s, i);

  foreach(var r in res) {
      if(!r.Passed) return null;
  }

  // Rest of code...

  return ret;
}

Myślę, że ta ostatnia jest znacznie jaśniejsza. O ile mogę powiedzieć, krytyka wielu punktów wyjścia jest obecnie raczej archaicznym punktem widzenia.

ljs
źródło
12
Jasność leży w oku patrzącego - patrzę na funkcję i szukam początku środka i końca. Kiedy funkcja jest niewielka, to w porządku - ale kiedy próbujesz dowiedzieć się, dlaczego coś się zepsuło, a „Reszta kodu” okazuje się nietrywialna, możesz spędzić wieki, szukając przyczyny ret
Murph
7
Przede wszystkim jest to wymyślony przykład. Po drugie, gdzie: string ret; "przejdź do drugiej wersji? Po trzecie, Ret nie zawiera przydatnych informacji. Po czwarte, dlaczego tyle logiki w jednej funkcji / metodzie? Po piąte, dlaczego nie rozdzielić DidValuesPass (typ res), a następnie RestOfCode () osobno podfunkcje?
Rick Minerich
25
@Rick 1. Nie zorientowałem się w moim doświadczeniu, w rzeczywistości jest to wzorzec, z którym wiele razy się spotkałem. 2. Jest przypisany do „reszty kodu”, może to nie jest jasne. 3. Um? To jest przykład? 4. Cóż, wydaje mi się, że jest pod tym względem wymyślony, ale można to tak bardzo uzasadnić, 5. mógłby zrobić ...
ljs 21.03.2009
5
@ Chodzi o to, że często łatwiej jest wrócić wcześniej niż zawijać kod w dużej instrukcji if. Z mojego doświadczenia wynika, że ​​nawet przy odpowiednim refaktoryzacji.
ljs
5
Brak wielu instrukcji return sprawia, że ​​debugowanie jest łatwiejsze, a znacznie większa jest czytelność. Jeden punkt przerwania na końcu pozwala zobaczyć wartość wyjścia, bez wyjątków. Jeśli chodzi o czytelność, 2 przedstawione funkcje nie zachowują się tak samo. Domyślnym zwróceniem jest pusty ciąg, jeśli! R.Passed, ale „bardziej czytelny” zmienia to, zwracając null. Autor błędnie zinterpretował, że wcześniej pojawił się domyślny już po kilku wierszach. Nawet w trywialnych przykładach łatwo jest uzyskać niejasne zwroty domyślne, coś, co pojedynczy zwrot na końcu pomaga wymusić.
Mitch
191

Obecnie pracuję nad bazą kodów, w której dwie osoby pracujące nad nią ślepo popierają teorię „pojedynczego punktu wyjścia” i mogę powiedzieć, że z doświadczenia jest to okropna, okropna praktyka. To sprawia, że ​​kod jest niezwykle trudny w utrzymaniu i pokażę ci dlaczego.

Z teorią „pojedynczego punktu wyjścia” nieuchronnie kończy się kod, który wygląda następująco:

function()
{
    HRESULT error = S_OK;

    if(SUCCEEDED(Operation1()))
    {
        if(SUCCEEDED(Operation2()))
        {
            if(SUCCEEDED(Operation3()))
            {
                if(SUCCEEDED(Operation4()))
                {
                }
                else
                {
                    error = OPERATION4FAILED;
                }
            }
            else
            {
                error = OPERATION3FAILED;
            }
        }
        else
        {
            error = OPERATION2FAILED;
        }
    }
    else
    {
        error = OPERATION1FAILED;
    }

    return error;
}

To nie tylko sprawia, że ​​kod jest bardzo trudny do naśladowania, ale teraz powiedz później, że musisz wrócić i dodać operację między 1 a 2. Musisz wciąć tylko całą funkcję maniaków i powodzenia, upewniając się, że wszystkie twoje warunki / szelki if / else są odpowiednio dopasowane.

Ta metoda sprawia, że ​​konserwacja kodu jest niezwykle trudna i podatna na błędy.

17 z 26
źródło
5
@Murph: Nie możesz wiedzieć, że nic więcej nie występuje po każdym stanie bez przeczytania każdego z nich. Zwykle powiedziałbym, że tego rodzaju tematy są subiektywne, ale jest to po prostu błędne. Po zwróceniu każdego błędu skończysz, wiesz dokładnie, co się stało.
GEOCHET
6
@Murph: Widziałem tego rodzaju kod używany, nadużywany i nadużywany. Przykład jest dość prosty, ponieważ w środku nie ma prawdy. Wszystko, czego ten kod potrzebuje do rozbicia, to jedno „inne” zapomniane. AFAIK, ten kod naprawdę potrzebuje wyjątków.
paercebal
15
Możesz to przełożyć na to, zachowując jego „czystość”: if (! SUCCEEDED (Operation1 ())) {} else error = OPERATION1FAILED; if (błąd! = S_OK) {if (SUCCEEDED (Operation2 ())) {} else error = OPERATION2FAILED; } if (error! = S_OK) {if (SUCCEEDED (Operation3 ())) {} else error = OPERATION3FAILED; } // itp.
Joe Pineda
6
Ten kod nie ma tylko jednego punktu wyjścia: każda z tych instrukcji „error =” znajduje się na ścieżce wyjścia. Nie chodzi tylko o wyjście z funkcji, ale o wyjście z dowolnego bloku lub sekwencji.
Jay Bazuzi,
6
Nie zgadzam się z tym, że pojedynczy zwrot „nieuchronnie” powoduje głębokie zagnieżdżenie. Twój przykład można zapisać jako prostą, liniową funkcję z jednym powrotem (bez gotów). A jeśli nie możesz polegać wyłącznie na RAII lub nie możesz polegać wyłącznie na zarządzaniu zasobami, wczesne powroty powodują wycieki lub duplikowanie kodu. Co najważniejsze, wczesne powroty sprawiają, że dochodzenie post-warunków jest niepraktyczne.
Adrian McCarthy
72

Programowanie strukturalne mówi, że powinieneś mieć tylko jedną instrukcję return na funkcję. Ma to na celu ograniczenie złożoności. Wiele osób, takich jak Martin Fowler, twierdzi, że łatwiej jest pisać funkcje z wieloma instrukcjami return. Przedstawia ten argument w klasycznej książce o refaktoryzacji, którą napisał. Działa to dobrze, jeśli zastosujesz się do jego innych rad i napiszesz małe funkcje. Zgadzam się z tym punktem widzenia i tylko ściśle ustrukturyzowani purystycy programowania przestrzegają pojedynczych instrukcji return na funkcję.

cdv
źródło
44
Programowanie strukturalne nic nie mówi. Mówią o tym niektórzy (ale nie wszyscy) ludzie, którzy określają się jako zwolennicy programowania strukturalnego.
wnoise
15
„To działa dobrze, jeśli zastosujesz się do jego innych rad i napiszesz małe funkcje”. To jest najważniejszy punkt. Małe funkcje rzadko wymagają wielu punktów powrotu.
6
@wnoise +1 za komentarz, więc to prawda. Wszystkie „programowanie strukturalne” mówi, że nie używaj GOTO.
paxos1977,
1
@ceretullis: chyba że jest to konieczne. Oczywiście nie jest to konieczne, ale może być przydatne w C. Jądro linuksa korzysta z niego i nie bez powodu. GOTO uważał za szkodliwe mówienie o używaniu GOTOdo sterowania przepływem sterowania, nawet gdy istniała funkcja. Nigdy nie mówi „nigdy nie używaj GOTO”.
Esteban Küber
1
„polegają na ignorowaniu„ struktury ”kodu.” - Nie, wręcz przeciwnie. „sensownie jest powiedzieć, że należy ich unikać” - nie, nie robi tego.
Jim Balter
62

Jak zauważa Kent Beck podczas omawiania klauzul ochronnych we Wzorcach implementacyjnych, w których rutyna ma jeden punkt wejścia i wyjścia ...

”miało zapobiec nieporozumieniom możliwym przy wskakiwaniu i wychodzeniu z wielu lokalizacji w tej samej procedurze. To miało sens, gdy zastosowano je do FORTRAN lub programów w języku asemblera napisanych z dużą ilością danych globalnych, w których nawet zrozumienie, które instrukcje zostały wykonane, było ciężką pracą. przy użyciu małych metod i głównie danych lokalnych jest to niepotrzebnie konserwatywne ”.

Uważam, że funkcja napisana za pomocą klauzul ochronnych jest znacznie łatwiejsza do naśladowania niż jedna długa zagnieżdżona wiązka if then elseinstrukcji.

pusty
źródło
Oczywiście „jedna długa zagnieżdżona wiązka instrukcji if-then-else” nie jest jedyną alternatywą dla klauzul ochronnych.
Adrian McCarthy
@AdrianMcCarthy czy masz lepszą alternatywę? Byłoby to bardziej przydatne niż sarkazm.
shinzou,
@kuhaku: Nie jestem pewien, czy nazwałbym to sarkazmem. Odpowiedź sugeruje, że jest to sytuacja albo: albo klauzule ochronne, albo długie zagnieżdżone wiązki „jeśli-to-jeszcze”. Wiele (większość?) Języków programowania oferuje wiele sposobów na uwzględnienie takiej logiki oprócz klauzul ochronnych.
Adrian McCarthy
61

W funkcji, która nie ma skutków ubocznych, nie ma dobrego powodu, aby mieć więcej niż jeden zwrot i powinieneś napisać je w funkcjonalnym stylu. W metodzie z efektami ubocznymi rzeczy są bardziej sekwencyjne (indeksowane czasowo), więc piszesz w trybie rozkazującym, używając instrukcji return jako polecenia, aby zatrzymać wykonywanie.

Innymi słowy, jeśli to możliwe, faworyzuj ten styl

return a > 0 ?
  positively(a):
  negatively(a);

Nad tym

if (a > 0)
  return positively(a);
else
  return negatively(a);

Jeśli zauważysz, że piszesz kilka warstw warunków zagnieżdżonych, prawdopodobnie istnieje sposób, aby to zmienić, np. Używając listy predykatów. Jeśli okaże się, że twoje ifs i wady są daleko od siebie syntaktyczne, możesz podzielić to na mniejsze funkcje. Blok warunkowy obejmujący więcej niż ekran tekstu jest trudny do odczytania.

Nie ma twardej i szybkiej reguły, która miałaby zastosowanie do każdego języka. Coś takiego jak posiadanie pojedynczej instrukcji return nie poprawi twojego kodu. Ale dobry kod pozwala na pisanie funkcji w ten sposób.

Apokalipsa
źródło
6
+1 „Jeśli stwierdzisz, że twoje ifs i wady są daleko od siebie syntaktyczne, możesz podzielić to na mniejsze funkcje.”
Andres Jaan Tack
4
+1, jeśli jest to problem, zwykle oznacza to, że robisz za dużo w jednej funkcji. Naprawdę przygnębia mnie to, że nie jest to najwyższa głosowana odpowiedź
Matt Briggs,
1
Strażnicy nie mają też żadnych skutków ubocznych, ale większość ludzi uważa je za przydatne. Dlatego mogą istnieć powody, aby wcześniej przerwać wykonywanie, nawet jeśli nie występują żadne skutki uboczne. Moim zdaniem ta odpowiedź nie w pełni rozwiązuje ten problem.
Maarten Bodewes
@ MaartenBodewes-owlstead Patrz
Apocalisp
43

Widziałem to w standardach kodowania dla C ++, które były kacem z C, ponieważ jeśli nie masz RAII lub innego automatycznego zarządzania pamięcią, musisz wyczyścić każdy zwrot, co oznacza albo wycinanie i wklejanie czyszczenia lub goto (logicznie to samo, co „wreszcie” w zarządzanych językach), które są uważane za złą formę. Jeśli używasz inteligentnych wskaźników i kolekcji w C ++ lub innym automatycznym systemie pamięci, nie ma ku temu silnego powodu, a wszystko sprowadza się do czytelności i bardziej do oceny.

Pete Kirkham
źródło
Dobrze powiedziane, chociaż uważam, że lepiej jest skopiować skreślenia, gdy próbujesz napisać wysoce zoptymalizowany kod (taki jak oprogramowanie do skórowania złożonych siatek 3d!)
Grant Peters
1
Co sprawia, że ​​w to wierzysz? Jeśli masz kompilator ze słabą optymalizacją, w którym istnieje pewien narzut związany z dereferencją auto_ptr, możesz używać prostych wskaźników równolegle. Chociaż pisanie „zoptymalizowanego” kodu za pomocą nieoptymalizującego kompilatora byłoby dziwne.
Pete Kirkham
Stanowi to interesujący wyjątek od reguły: jeśli twój język programowania nie zawiera czegoś, co jest automatycznie wywoływane na końcu metody (np. try... finallyw Javie) i musisz przeprowadzić konserwację zasobów, którą możesz zrobić za pomocą jednego zwraca na końcu metody. Zanim to zrobisz, powinieneś poważnie rozważyć zmianę kodu w celu pozbycia się sytuacji.
Maarten Bodewes
@PeteKirkham, dlaczego goto na czyszczenie jest złe? tak, można źle używać, ale to konkretne użycie nie jest złe.
q126y
1
@ q126y w C ++, w przeciwieństwie do RAII, kończy się niepowodzeniem po zgłoszeniu wyjątku. W C jest to całkowicie poprawna praktyka. Zobacz stackoverflow.com/questions/379172/use-goto-or-not
Pete Kirkham,
40

Opieram się na pomyśle, że instrukcje zwracane w środku funkcji są złe. Możesz użyć return, aby zbudować kilka klauzul ochronnych u góry funkcji i oczywiście powiedzieć kompilatorowi, co zwrócić na końcu funkcji bez problemu, ale zwroty w środku funkcji mogą być łatwe do pominięcia i mogą utrudniają interpretację funkcji.

Joel Coehoorn
źródło
38

Czy istnieją dobre powody, dla których lepiej jest mieć tylko jedną instrukcję return w funkcji?

Tak , są:

  • Pojedynczy punkt wyjścia zapewnia doskonałe miejsce do potwierdzenia swoich warunków po zakończeniu pobytu.
  • Często przydatna jest możliwość umieszczenia punktu przerwania debuggera przy jednym powrocie na końcu funkcji.
  • Mniej zwrotów oznacza mniej złożoności. Kod liniowy jest ogólnie łatwiejszy do zrozumienia.
  • Jeśli próba uproszczenia funkcji do pojedynczego zwrotu powoduje złożoność, to jest to zachęta do zmiany na mniejsze, bardziej ogólne, łatwiejsze do zrozumienia funkcje.
  • Jeśli posługujesz się językiem pozbawionym niszczycieli lub nie używasz RAII, to pojedynczy zwrot zmniejsza liczbę miejsc, które musisz posprzątać.
  • Niektóre języki wymagają jednego punktu wyjścia (np. Pascal i Eiffel).

Pytanie to jest często przedstawiane jako fałszywa dychotomia między wieloma zwrotami lub głęboko zagnieżdżone, jeśli instrukcje. Prawie zawsze istnieje trzecie rozwiązanie, które jest bardzo liniowe (bez głębokiego zagnieżdżania) z tylko jednym punktem wyjścia.

Aktualizacja : Najwyraźniej wytyczne MISRA promują również pojedyncze wyjście .

Dla jasności nie twierdzę, że wielokrotne zwroty zawsze są złe. Ale biorąc pod uwagę równoważne rozwiązania, istnieje wiele dobrych powodów, aby preferować ten z jednym zwrotem.

Adrian McCarthy
źródło
2
innym dobrym powodem, prawdopodobnie najlepszym w dzisiejszych czasach, aby mieć jedną instrukcję zwrotu, jest logowanie. jeśli chcesz dodać rejestrowanie do metody, możesz umieścić pojedynczą instrukcję dziennika, która przekazuje zwrot metody.
2013
Jak często stwierdzenie FORTRAN ENTRY? Zobacz docs.oracle.com/cd/E19957-01/805-4939/6j4m0vn99/index.html . A jeśli masz ochotę na przygodę, możesz rejestrować metody za pomocą AOP i porady
Eric Jablow
1
+1 Pierwsze 2 punkty wystarczyły, by mnie przekonać. To z ostatnim akapitem. Nie zgadzałbym się z elementem rejestrującym z tego samego powodu, co odradzam warunkowe zagnieżdżanie głęboko, ponieważ zachęca to do złamania zasady pojedynczej odpowiedzialności, która jest głównym powodem wprowadzenia polimorfizmu do OOP.
Francis Rodgers
Chciałbym tylko dodać, że w przypadku C # i kontraktów kodowych problem po spełnieniu warunku nie jest problemem, ponieważ nadal można go używać Contract.Ensuresz wieloma punktami zwrotu.
julealgon
1
@ q126y: Jeśli korzystasz gotoz wspólnego kodu czyszczenia, prawdopodobnie uprościłeś tę funkcję, aby returnna końcu kodu czyszczenia była jedna . Więc możesz powiedzieć, że rozwiązałeś problem goto, ale powiedziałbym, że rozwiązałeś go przez uproszczenie do jednego return.
Adrian McCarthy
33

Posiadanie pojedynczego punktu wyjścia zapewnia przewagę podczas debugowania, ponieważ pozwala ustawić pojedynczy punkt przerwania na końcu funkcji, aby zobaczyć, jaka wartość rzeczywiście zostanie zwrócona.

Mark
źródło
6
BRAWO! Jesteś jedyną osobą, która podała ten obiektywny powód. To jest powód, dla którego wolę pojedyncze punkty wyjścia niż wiele punktów wyjścia. Jeśli mój debuger mógłby ustawić punkt przerwania w dowolnym punkcie wyjścia, prawdopodobnie wolałbym wiele punktów wyjścia. Moja obecna opinia jest taka, że ​​ludzie, którzy kodują wiele punktów wyjścia, robią to dla własnej korzyści kosztem innych, którzy muszą używać debuggera na swoim kodzie (i tak, mówię o wszystkich użytkownikach open source, którzy piszą kod za pomocą wiele punktów wyjścia.)
MikeSchinkel
3
TAK. Dodam kod logowania do systemu, który sporadycznie źle działa w środowisku produkcyjnym (gdzie nie mogę przejść). Gdyby poprzedni koder używał pojedynczego wyjścia, byłoby to DUŻO łatwiejsze.
Michael Blackburn,
3
To prawda, że ​​przy debugowaniu jest to pomocne. Ale w praktyce w większości przypadków byłem w stanie ustawić punkt przerwania w funkcji wywoływania, tuż po wywołaniu - skutecznie do tego samego wyniku. (I ta pozycja znajduje się oczywiście na stosie wywołań.) YMMV.
foo
Dzieje się tak, chyba że twój debugger udostępnia funkcję step-out lub step-return (i każdy debugger robi, o ile wiem), która pokazuje zwróconą wartość zaraz po zwróceniu. Późniejsza zmiana wartości może być nieco trudna, jeśli nie zostanie przypisana do zmiennej.
Maarten Bodewes
7
Dawno nie widziałem debugera, który nie pozwoliłby ci ustawić punktu przerwania na „zamknięciu” metody (koniec, prawy nawias klamrowy, niezależnie od języka) i trafić w ten punkt przerwania bez względu na to, gdzie i ile , zwrócone statemety są w metodzie. Ponadto, nawet jeśli funkcja ma tylko jeden zwrot, nie oznacza to, że nie można wyjść z funkcji z wyjątkiem (jawnym lub odziedziczonym). Więc myślę, że to naprawdę nie jest słuszna kwestia.
Scott Gartner
19

Ogólnie staram się mieć tylko jeden punkt wyjścia z funkcji. Są jednak chwile, że tak naprawdę kończy się to tworzeniem bardziej złożonego korpusu funkcji, niż jest to konieczne, w takim przypadku lepiej jest mieć wiele punktów wyjścia. To naprawdę musi być „wezwanie do oceny” oparte na wynikającej z tego złożoności, ale celem powinno być jak najmniej punktów wyjścia, bez poświęcania złożoności i zrozumiałości.

Scott Dorman
źródło
„Ogólnie staram się mieć tylko jeden punkt wyjścia z funkcji” - dlaczego? „celem powinno być jak najmniej punktów wyjścia” - dlaczego? I dlaczego 19 osób głosowało za brakiem odpowiedzi?
Jim Balter
@JimBalter Ostatecznie sprowadza się do osobistych preferencji. Więcej punktów wyjścia zazwyczaj prowadzi do bardziej złożonej metody (choć nie zawsze) i utrudnia komuś zrozumienie.
Scott Dorman
„sprowadza się to do osobistych preferencji.” - Innymi słowy, nie możesz podać powodu. „Więcej punktów wyjścia zwykle prowadzi do bardziej złożonej metody (choć nie zawsze)” - Nie, w rzeczywistości tak nie jest. Biorąc pod uwagę dwie funkcje, które są logicznie równoważne, jedną z klauzulami ochronnymi i jedną z pojedynczym wyjściem, ta druga będzie miała wyższą cykliczność, co liczne badania pokazują wyniki w kodzie, który jest bardziej podatny na błędy i trudny do zrozumienia. Przydałoby Ci się przeczytanie innych odpowiedzi tutaj.
Jim Balter
14

Nie, ponieważ nie żyjemy już w latach siedemdziesiątych . Jeśli twoja funkcja jest wystarczająco długa, aby wielokrotne zwroty stanowiły problem, jest za długa.

(Pomijając fakt, że każda funkcja wieloliniowa w języku z wyjątkami i tak będzie miała wiele punktów wyjścia).

Ben Lings
źródło
14

Preferuję pojedyncze wyjście, chyba że naprawdę to komplikuje. Odkryłem, że w niektórych przypadkach wiele istniejących punktów może maskować inne, bardziej znaczące problemy projektowe:

public void DoStuff(Foo foo)
{
    if (foo == null) return;
}

Po zobaczeniu tego kodu od razu zapytam:

  • Czy „foo” jest kiedykolwiek zerowe?
  • Jeśli tak, to ilu klientów „DoStuff” wywołuje tę funkcję z zerowym „foo”?

W zależności od odpowiedzi na te pytania może to być to

  1. kontrola jest bezcelowa, ponieważ nigdy nie jest prawdziwa (tzn. powinna być stwierdzeniem)
  2. sprawdzenie jest bardzo rzadko prawdziwe, dlatego może być lepsza zmiana tych konkretnych funkcji dzwoniącego, ponieważ prawdopodobnie i tak powinny podjąć jakieś inne działania.

W obu powyższych przypadkach kod można prawdopodobnie przerobić za pomocą asercji, aby upewnić się, że „foo” nigdy nie ma wartości zerowej, a odpowiednie elementy wywołujące uległy zmianie.

Istnieją dwa inne powody (specyficzne, jak sądzę, kod C ++), w których istnieje wiele, które mogą mieć negatywny wpływ. Są to rozmiar kodu i optymalizacje kompilatora.

Obiekt non-POD C ++ w zasięgu na wyjściu funkcji będzie miał wywołany destruktor. Jeśli istnieje kilka instrukcji return, może się zdarzyć, że w zasięgu znajdują się różne obiekty, więc lista wywoływanych destruktorów będzie inna. Dlatego kompilator musi wygenerować kod dla każdej instrukcji return:

void foo (int i, int j) {
  A a;
  if (i > 0) {
     B b;
     return ;   // Call dtor for 'b' followed by 'a'
  }
  if (i == j) {
     C c;
     B b;
     return ;   // Call dtor for 'b', 'c' and then 'a'
  }
  return 'a'    // Call dtor for 'a'
}

Jeśli problemem jest rozmiar kodu - warto go unikać.

Drugi problem dotyczy „Nazwanej optymalizacji wartości zwracanej” (inaczej Copy Elision, ISO C ++ '03 12.8 / 15). C ++ pozwala implementacji pominąć wywoływanie konstruktora kopiowania, jeśli może:

A foo () {
  A a1;
  // do something
  return a1;
}

void bar () {
  A a2 ( foo() );
}

Przyjmując kod w niezmienionej postaci, obiekt „a1” jest konstruowany w „foo”, a następnie jego konstrukcja kopiująca zostanie wywołana w celu utworzenia „a2”. Jednak eliminacja kopii pozwala kompilatorowi zbudować „a1” w tym samym miejscu na stosie co „a2”. Nie ma zatem potrzeby „kopiowania” obiektu po powrocie funkcji.

Wiele punktów wyjścia komplikuje pracę kompilatora w próbach wykrycia tego, a przynajmniej dla stosunkowo nowej wersji VC ++ optymalizacja nie miała miejsca, gdy ciało funkcji miało wiele zwrotów. Aby uzyskać więcej informacji, zobacz Optymalizacja nazwanych wartości zwrotnych w programie Visual C ++ 2005 .

Richard Corden
źródło
1
Jeśli weźmiesz wszystko oprócz ostatniego dtor ze swojego przykładu w C ++, kod do zniszczenia B, a później C i B, nadal będzie musiał zostać wygenerowany po zakończeniu zakresu instrukcji if, więc tak naprawdę nic nie zyskasz, nie mając wielu zwrotów .
Eclipse
4
+1 I waaaaay na dole listy mamy PRAWDZIWY powód, dla którego ta praktyka kodowania istnieje - NRVO. Jest to jednak mikrooptymalizacja; i, podobnie jak wszystkie praktyki mikrooptymalizacji, został prawdopodobnie rozpoczęty przez około 50-letniego „eksperta”, który jest przyzwyczajony do programowania na PDP-8 300 kHz i nie rozumie znaczenia czystego i uporządkowanego kodu. Zasadniczo skorzystaj z porady Chrisa S. i używaj wielu zwrotów, gdy tylko ma to sens.
BlueRaja - Danny Pflughoeft
Chociaż nie zgadzam się z twoimi preferencjami (w mojej opinii twoja sugestia Assert jest również punktem zwrotnym, podobnie jak throw new ArgumentNullException()w C # w tym przypadku), naprawdę spodobały mi się twoje inne rozważania, wszystkie są dla mnie ważne i mogą być krytyczne w niektórych konteksty niszowe.
julealgon
To jest klin pełen słomków. Pytanie, dlaczego foojest testowany, nie ma nic wspólnego z tym tematem, czy chodzi o to, if (foo == NULL) return; dowork; czyif (foo != NULL) { dowork; }
Jim Balter
11

Posiadanie jednego punktu wyjścia zmniejsza złożoność cykliczną, a zatem teoretycznie zmniejsza prawdopodobieństwo wprowadzenia błędów w kodzie podczas jego zmiany. Praktyka sugeruje jednak, że potrzebne jest bardziej pragmatyczne podejście. Dlatego staram się mieć jeden punkt wyjścia, ale pozwalam, aby mój kod miał kilka, jeśli jest to bardziej czytelne.

John Channing
źródło
Bardzo wnikliwy. Chociaż uważam, że dopóki programista nie wie, kiedy użyć wielu punktów wyjścia, należy je ograniczyć do jednego.
Rick Minerich
5
Nie całkiem. Cyklomatyczna złożoność „jeśli (...) powraca; ... powraca;” jest taki sam jak „if (...) {...} return;”. Obaj mają przez nie dwie ścieżki.
Steve Emmerson,
11

Zmuszam się do użycia tylko jednej returninstrukcji, ponieważ w pewnym sensie wygeneruje zapach kodu. Pozwól mi wyjaśnić:

function isCorrect($param1, $param2, $param3) {
    $toret = false;
    if ($param1 != $param2) {
        if ($param1 == ($param3 * 2)) {
            if ($param2 == ($param3 / 3)) {
                $toret = true;
            } else {
                $error = 'Error 3';
            }
        } else {
            $error = 'Error 2';
        }
    } else {
        $error = 'Error 1';
    }
    return $toret;
}

(Warunki są arbritary ...)

Im więcej warunków, tym większa funkcja, tym trudniej jest ją odczytać. Jeśli więc zestroisz się z zapachem kodu, zdasz sobie z tego sprawę i zechcesz go zmodyfikować. Dwa możliwe rozwiązania to:

  • Wiele zwrotów
  • Refaktoryzacja do oddzielnych funkcji

Wiele zwrotów

function isCorrect($param1, $param2, $param3) {
    if ($param1 == $param2)       { $error = 'Error 1'; return false; }
    if ($param1 != ($param3 * 2)) { $error = 'Error 2'; return false; }
    if ($param2 != ($param3 / 3)) { $error = 'Error 3'; return false; }
    return true;
}

Oddzielne funkcje

function isEqual($param1, $param2) {
    return $param1 == $param2;
}

function isDouble($param1, $param2) {
    return $param1 == ($param2 * 2);
}

function isThird($param1, $param2) {
    return $param1 == ($param2 / 3);
}

function isCorrect($param1, $param2, $param3) {
    return !isEqual($param1, $param2)
        && isDouble($param1, $param3)
        && isThird($param2, $param3);
}

To prawda, jest dłuższy i nieco niechlujny, ale w trakcie refaktoryzacji funkcji w ten sposób mamy

  • stworzył szereg funkcji wielokrotnego użytku,
  • uczyniono tę funkcję bardziej czytelną dla ludzi, oraz
  • funkcje skupiają się na tym, dlaczego wartości są prawidłowe.
Jrgns
źródło
5
-1: zły przykład. Pominięto obsługę komunikatu o błędzie. Jeśli nie byłoby to potrzebne, isCorrect można wyrazić jako zwrot xx &&yy &&zz; gdzie xx, yy i z są wyrażeniami isEqual, isDouble i isThird.
kauppi,
10

Powiedziałbym, że powinieneś mieć tyle, ile potrzeba, lub takie, które czynią kod czystszym (np. Klauzule ochronne ).

Osobiście nigdy nie słyszałem / nie widziałem żadnych „najlepszych praktyk”, które mówią, że powinieneś mieć tylko jedno oświadczenie zwrotne.

W większości przypadków wychodzę z funkcji tak szybko, jak to możliwe, na podstawie ścieżki logicznej (klauzule ochronne są tego doskonałym przykładem).

Rob Cooper
źródło
10

Uważam, że wielokrotne zwroty są zwykle dobre (w kodzie, który piszę w języku C #). Styl pojedynczego zwrotu jest pozostałością po C. Ale prawdopodobnie nie kodujesz w C.

Nie ma prawa wymagającego tylko jednego punktu wyjścia dla metody we wszystkich językach programowania . Niektórzy ludzie nalegają na wyższość tego stylu, a czasem podnoszą go do „reguły” lub „prawa”, ale przekonanie to nie jest poparte żadnymi dowodami ani badaniami.

Więcej niż jeden styl zwracania może być złym nawykiem w kodzie C, gdzie zasoby muszą być jawnie zwolnione, ale języki takie jak Java, C #, Python lub JavaScript, które mają konstrukcje takie jak automatyczne odśmiecanie i try..finallybloki (i usingbloki w C # ), a ten argument nie ma zastosowania - w tych językach bardzo rzadko jest potrzebne scentralizowane ręczne zwalnianie zasobów.

Są przypadki, w których pojedynczy zwrot jest bardziej czytelny, a przypadki, w których nie jest. Sprawdź, czy zmniejsza liczbę wierszy kodu, czyści logikę lub zmniejsza liczbę nawiasów klamrowych i wcięć lub zmiennych tymczasowych.

Dlatego stosuj tyle zwrotów, ile odpowiada twojej wrażliwości artystycznej, ponieważ jest to kwestia układu i czytelności, a nie kwestia techniczna.

Mówiłem o tym bardziej szczegółowo na moim blogu .

Anthony
źródło
10

Są dobre rzeczy do powiedzenia na temat posiadania jednego punktu wyjścia, podobnie jak złe rzeczy do powiedzenia na temat nieuchronnego programowania „strzałkowego”, które się pojawia .

Jeśli korzystam z wielu punktów wyjścia podczas sprawdzania poprawności danych wejściowych lub alokacji zasobów, staram się umieścić wszystkie „wyjścia błędów” bardzo wyraźnie na górze funkcji.

Zarówno artykuł o programowaniu spartańskim „SSDSLPedia”, jak i artykuł dotyczący punktu wyjścia z pojedynczą funkcją z „Wiki wzorcowego repozytorium portlandzkiego” mają wnikliwe argumenty na ten temat. Oczywiście jest jeszcze ten post do rozważenia.

Jeśli naprawdę chcesz jednego punktu wyjścia (w dowolnym języku nieobsługującym wyjątków), na przykład w celu uwolnienia zasobów w jednym miejscu, uważam, że staranne zastosowanie goto jest dobre; patrz na przykład ten wymyślony przykład (skompresowany w celu zapisania nieruchomości na ekranie):

int f(int y) {
    int value = -1;
    void *data = NULL;

    if (y < 0)
        goto clean;

    if ((data = malloc(123)) == NULL)
        goto clean;

    /* More code */

    value = 1;
clean:
   free(data);
   return value;
}

Osobiście nie lubię programowania strzał bardziej niż wielu punktów wyjścia, chociaż oba są przydatne, gdy są stosowane poprawnie. Oczywiście najlepiej jest ustrukturyzować program tak, aby nie wymagał żadnego z nich. Podział funkcji na wiele części zwykle pomaga :)

Chociaż robiąc to, okazuje się, że i tak mam wiele punktów wyjścia, jak w tym przykładzie, w którym jakaś większa funkcja została podzielona na kilka mniejszych funkcji:

int g(int y) {
  value = 0;

  if ((value = g0(y, value)) == -1)
    return -1;

  if ((value = g1(y, value)) == -1)
    return -1;

  return g2(y, value);
}

W zależności od projektu lub wytycznych kodowania większość kodu płyty kotła można zastąpić makrami. Na marginesie, podzielenie go w ten sposób sprawia, że ​​funkcje g0, g1, g2 są bardzo łatwe do przetestowania indywidualnie.

Oczywiście w języku OO i języku obsługującym wyjątki nie używałbym takich instrukcji if (lub w ogóle, gdybym mógł to zrobić przy niewielkim wysiłku), a kod byłby o wiele prostszy. I niearrowy. I większość nieoficjalnych zwrotów prawdopodobnie byłaby wyjątkiem.

W skrócie;

  • Niewiele zwrotów jest lepszych niż wiele zwrotów
  • Więcej niż jeden zwrot jest lepszy niż ogromne strzały, a klauzule ochronne są ogólnie ok.
  • Wyjątki mogłyby / powinny prawdopodobnie zastąpić większość „klauzul ochronnych”, jeśli to możliwe.
Henrik Gustafsson
źródło
Przykład ulega awarii dla y <0, ponieważ próbuje uwolnić wskaźnik NULL ;-)
Erich Kitzmueller
2
opengroup.org/onlinepubs/009695399/functions/free.html „Jeśli ptr jest wskaźnikiem zerowym, żadne działanie nie zostanie wykonane”.
Henrik Gustafsson
1
Nie, to się nie zawiesi, ponieważ przekazanie NULL do free jest zdefiniowanym zakazem. To denerwująco powszechne nieporozumienie, że musisz najpierw przetestować na NULL.
hlovdal
Wzór „strzałki” nie jest nieuniknioną alternatywą. To jest fałszywa dychotomia.
Adrian McCarthy
9

Znasz powiedzenie - piękno jest w oczach patrzącego .

Niektórzy przysięgają przez NetBeans, inni przez IntelliJ IDEA , inni przez Python, a inni przez PHP .

W niektórych sklepach możesz stracić pracę, jeśli nalegasz na zrobienie tego:

public void hello()
{
   if (....)
   {
      ....
   }
}

Pytanie dotyczy widoczności i łatwości konserwacji.

Jestem uzależniony od używania algebry boolowskiej w celu ograniczenia i uproszczenia logiki i korzystania z automatów stanów. Byli jednak byli koledzy, którzy uważali, że zastosowanie przeze mnie „technik matematycznych” w kodowaniu jest nieodpowiednie, ponieważ nie byłoby to widoczne i możliwe do utrzymania. I to byłaby zła praktyka. Przepraszam ludzi, stosowane przeze mnie techniki są dla mnie bardzo widoczne i łatwe do utrzymania - ponieważ gdy wrócę do kodu sześć miesięcy później, zrozumiem kod wyraźnie, widząc bałagan przysłowiowego spaghetti.

Hej kolego (jak mawiał były klient) rób, co chcesz, o ile wiesz, jak to naprawić, kiedy będę tego potrzebować.

Pamiętam 20 lat temu, że mój kolega został zwolniony za stosowanie strategii zwinnego rozwoju . Miał drobiazgowy plan przyrostowy. Ale jego menedżer krzyczał na niego: „Nie można stopniowo udostępniać funkcji użytkownikom! Musisz trzymać się wodospadu ”. Jego odpowiedzią dla kierownika było to, że stopniowy rozwój będzie bardziej precyzyjny w stosunku do potrzeb klienta. Wierzył w rozwój dla potrzeb klientów, ale kierownik wierzył w kodowanie zgodnie z „wymaganiami klienta”.

Często jesteśmy winni łamania normalizacji danych, granic MVP i MVC . Wstawiamy zamiast konstruować funkcję. Bierzemy skróty.

Osobiście uważam, że PHP to zła praktyka, ale co wiem. Wszystkie teoretyczne argumenty sprowadzają się do próby spełnienia jednego zestawu zasad

jakość = precyzja, łatwość konserwacji i rentowność.

Wszystkie pozostałe reguły znikają w tle. I oczywiście ta zasada nigdy nie zanika:

Lenistwo jest zaletą dobrego programisty.

Błogosławiony Geek
źródło
1
„Hej kolego (jak mawiał były klient) rób, co chcesz, o ile wiesz, jak to naprawić, kiedy potrzebuję, abyś to naprawił.” Problem: często to nie ty go „naprawiasz”.
Dan Barron,
Daj +1 tej odpowiedzi, ponieważ zgadzam się z tym, dokąd idziesz, ale niekoniecznie, w jaki sposób się tam dostałeś. Twierdziłbym, że istnieją poziomy zrozumienia. tzn. zrozumienie, że pracownik A ma 5 lat doświadczenia w programowaniu i 5 lat w firmie, jest zupełnie inne niż pracownik B, nowy absolwent college'u, który dopiero zaczyna pracę w firmie. Chodzi mi o to, że jeśli pracownik A jest jedyną osobą, która może zrozumieć kod, NIE jest on do utrzymania i dlatego wszyscy powinniśmy dążyć do napisania kodu, który pracownik B może zrozumieć. To jest, gdzie prawdziwa sztuka jest w oprogramowaniu.
Francis Rodgers
9

Skłaniam się do korzystania z klauzul ochronnych, aby powrócić wcześniej i inaczej wyjść na końcu metody. Reguła pojedynczego wejścia i wyjścia ma znaczenie historyczne i była szczególnie pomocna w przypadku starszego kodu, który prowadził do 10 stron A4 dla jednej metody C ++ z wieloma zwrotami (i wieloma wadami). Niedawno przyjętą dobrą praktyką jest utrzymywanie niewielkich metod, co sprawia, że ​​wielokrotne wyjścia są mniej przeszkodą dla zrozumienia. W poniższym przykładzie Kronoz skopiowanym z góry pytanie brzmi: co dzieje się w // Reszcie kodu ... ?:

void string fooBar(string s, int? i) {

  if(string.IsNullOrEmpty(s) || i == null) return null;

  var res = someFunction(s, i);

  foreach(var r in res) {
      if(!r.Passed) return null;
  }

  // Rest of code...

  return ret;
}

Zdaję sobie sprawę, że przykład jest nieco wymyślony, ale kusiłoby mnie, aby przekształcić pętlę foreach w instrukcję LINQ, którą można by następnie uznać za klauzulę ochronną. Ponownie, w na dobry przykład intencją kodu nie jest oczywiste i someFunction () może mieć inny efekt uboczny albo wynik może być stosowany w // reszty kodu ... .

if (string.IsNullOrEmpty(s) || i == null) return null;
if (someFunction(s, i).Any(r => !r.Passed)) return null;

Nadanie następującej funkcji refaktoryzowanej:

void string fooBar(string s, int? i) {

  if (string.IsNullOrEmpty(s) || i == null) return null;
  if (someFunction(s, i).Any(r => !r.Passed)) return null;

  // Rest of code...

  return ret;
}
David Clarke
źródło
Czy C ++ nie ma wyjątków? Dlaczego więc powróciłbyś nullzamiast rzucać wyjątek wskazujący, że argument nie został zaakceptowany?
Maarten Bodewes,
1
Jak wskazałem, przykładowy kod jest kopiowany z poprzedniej odpowiedzi ( stackoverflow.com/a/36729/132599 ). Pierwotny przykład zwracał wartości zerowe, a refaktoryzacja w celu zgłaszania wyjątków argumentów nie była istotna do tego stopnia, że ​​próbowałem to zrobić lub do pierwotnego pytania. Zgodnie z dobrą praktyką, tak, normalnie (w języku C #) wrzucę ArgumentNullException do klauzuli ochronnej, zamiast zwracać wartość null.
David Clarke
7

Jednym z dobrych powodów, dla których mogę wymyślić konserwację kodu, jest jeden punkt wyjścia. Jeśli chcesz zmienić format wyniku, ... jest to o wiele prostsze do wdrożenia. Ponadto w celu debugowania możesz po prostu wstawić tam punkt przerwania :)

Powiedziawszy to, kiedyś musiałem pracować w bibliotece, w której standardy kodowania nakładały „jedną instrukcję zwrotną na funkcję”, i uważałem to za dość trudne. Piszę dużo kodu do obliczeń numerycznych i często zdarzają się „przypadki specjalne”, więc kod był trudny do naśladowania ...

OysterD
źródło
To naprawdę nie robi różnicy. Jeśli zmienisz typ zwracanej zmiennej lokalnej, będziesz musiał naprawić wszystkie przypisania do tej zmiennej lokalnej. Prawdopodobnie i tak lepiej jest zdefiniować metodę z inną sygnaturą, ponieważ będziesz musiał również naprawić wszystkie wywołania metod.
Maarten Bodewes 16.01.2013
@ MaartenBodewes-owlstead - To może coś zmienić. Aby wymienić tylko dwa przykłady, w których nie trzeba będzie naprawiać wszystkich przypisań do zmiennej lokalnej ani zmieniać wywołań metod, funkcja może zwrócić datę jako ciąg znaków (zmienna lokalna byłaby rzeczywistą datą, sformatowaną jako ciąg tylko w ostatnia chwila) lub może zwrócić liczbę dziesiętną i chcesz zmienić liczbę miejsc dziesiętnych.
nnnnnn
@ nnnnnn OK, jeśli chcesz wykonać przetwarzanie końcowe na wyjściu ... Ale po prostu wygenerowałbym nową metodę, która wykonuje przetwarzanie końcowe i zostawiłbym stary w spokoju. To tylko nieco trudniejsze do refaktoryzacji, ale i tak będziesz musiał sprawdzić, czy inne połączenia są zgodne z nowym formatem. Jest to jednak ważny powód.
Maarten Bodewes
7

Wiele punktów wyjścia jest wystarczających dla wystarczająco małych funkcji - to jest funkcji, którą można oglądać na jednym ekranie w całości. Jeśli długa funkcja zawiera również wiele punktów wyjścia, jest to znak, że funkcję można jeszcze bardziej podzielić.

To powiedziawszy, unikam funkcji wielokrotnego wyjścia, chyba że jest to absolutnie konieczne . Czułem ból błędów, które wynikają z pewnego błądzącego powrotu w jakiejś niejasnej linii w bardziej złożonych funkcjach.

Jon Limjap
źródło
6

Pracowałem ze strasznymi standardami kodowania, które wymusiły na tobie jedną ścieżkę wyjścia, a wynikiem jest prawie zawsze nieustrukturyzowane spaghetti, jeśli funkcja ta jest trywialna - kończysz się wieloma przerwami i dalej to przeszkadza.

Phil Bennett
źródło
Nie wspominając już o konieczności wymuszenia pominięcia ifinstrukcji przed każdym wywołaniem metody, która zwraca sukces, czy nie :(
Maarten Bodewes
6

Pojedynczy punkt wyjścia - wszystkie inne rzeczy są równe - sprawia, że ​​kod jest znacznie bardziej czytelny. Ale jest pewien haczyk: popularna konstrukcja

resulttype res;
if if if...
return res;

to podróbka, „res =” nie jest dużo lepsze niż „return”. Ma pojedynczą instrukcję powrotu, ale wiele punktów, w których funkcja faktycznie się kończy.

Jeśli masz funkcję z wieloma zwrotami (lub „res =” s), często dobrym pomysłem jest podzielenie jej na kilka mniejszych funkcji z jednym punktem wyjścia.

ima
źródło
6

Moją zwykłą polityką jest posiadanie tylko jednej instrukcji return na końcu funkcji, chyba że złożoność kodu zostanie znacznie zmniejszona przez dodanie większej liczby. W rzeczywistości jestem raczej fanem Eiffla, który egzekwuje jedyną regułę zwrotu, ponieważ nie ma instrukcji return (jest tylko automatycznie utworzona zmienna „wynik”, w której można umieścić wynik).

Z pewnością istnieją przypadki, w których kod może być wyraźniejszy dzięki wielokrotnym zwrotom, niż w przypadku oczywistej wersji bez nich. Można argumentować, że potrzeba więcej przeróbek, jeśli masz funkcję, która jest zbyt złożona, aby była zrozumiała bez wielu instrukcji return, ale czasem dobrze jest być pragmatycznym w takich sprawach.

Matt Sheppard
źródło
5

Jeśli uzyskasz więcej niż kilka zwrotów, może być coś nie tak z twoim kodem. W przeciwnym razie zgodziłbym się, że czasami miło jest móc powrócić z wielu miejsc w podprogramie, szczególnie gdy sprawia, że ​​kod jest czystszy.

Perl 6: Zły przykład

sub Int_to_String( Int i ){
  given( i ){
    when 0 { return "zero" }
    when 1 { return "one" }
    when 2 { return "two" }
    when 3 { return "three" }
    when 4 { return "four" }
    ...
    default { return undef }
  }
}

lepiej byłoby napisać w ten sposób

Perl 6: Dobry przykład

@Int_to_String = qw{
  zero
  one
  two
  three
  four
  ...
}
sub Int_to_String( Int i ){
  return undef if i < 0;
  return undef unless i < @Int_to_String.length;
  return @Int_to_String[i]
}

Uwaga: to był tylko szybki przykład

Brad Gilbert
źródło
Ok Dlaczego to zostało odrzucone? To nie tak, że to nie jest opinia.
Brad Gilbert
5

Głosuję za Pojedynczym powrotem na końcu jako wytyczną. Pomaga to w czyszczeniu wspólnego kodu ... Na przykład spójrz na następujący kod ...

void ProcessMyFile (char *szFileName)
{
   FILE *fp = NULL;
   char *pbyBuffer = NULL:

   do {

      fp = fopen (szFileName, "r");

      if (NULL == fp) {

         break;
      }

      pbyBuffer = malloc (__SOME__SIZE___);

      if (NULL == pbyBuffer) {

         break;
      }

      /*** Do some processing with file ***/

   } while (0);

   if (pbyBuffer) {

      free (pbyBuffer);
   }

   if (fp) {

      fclose (fp);
   }
}
Alphaneo
źródło
Głosujesz na pojedynczy zwrot - w kodzie C. Ale co, jeśli piszesz w języku, który ma funkcję wyrzucania elementów bezużytecznych i próbuje… ostatecznie zablokować?
Anthony
4

Jest to prawdopodobnie niezwykła perspektywa, ale myślę, że każdy, kto uważa, że ​​należy faworyzować wiele instrukcji zwrotnych, nigdy nie musiał używać debugera na mikroprocesorze, który obsługuje tylko 4 punkty przerwania sprzętowego. ;-)

Podczas gdy problemy z „kodem strzałki” są całkowicie poprawne, jednym z problemów, który wydaje się znikać podczas używania wielu instrukcji return, jest sytuacja, w której używasz debuggera. Nie masz dogodnej pozycji catch-all, aby ustawić punkt przerwania, aby zagwarantować, że zobaczysz wyjście, a tym samym warunek powrotu.

Andrew Edgecombe
źródło
5
To tylko inny rodzaj przedwczesnej optymalizacji. Nigdy nie należy optymalizować pod kątem specjalnego przypadku. Jeśli często debugujesz określoną sekcję kodu, jest w tym coś więcej niż tylko liczba punktów wyjścia.
Wedge
Zależy również od twojego debuggera.
Maarten Bodewes
4

Im więcej instrukcji return masz w funkcji, tym większa złożoność tej jednej metody. Jeśli zastanawiasz się, czy masz za dużo instrukcji return, możesz zadać sobie pytanie, czy w tej funkcji jest zbyt wiele wierszy kodu.

Ale nie, nie ma nic złego w jednym / wielu zwrotach. W niektórych językach jest to lepsza praktyka (C ++) niż w innych (C).

hazzen
źródło