Skąd się wzięło pojęcie „tylko jeden powrót”?

1055

Często rozmawiam z programistami, którzy mówią: „ Nie umieszczaj wielu instrukcji return w tej samej metodzie. Gdy pytam ich o powody, otrzymuję tylko „ Standard kodowania tak mówi ” lub „ To mylące ”. Kiedy pokazują mi rozwiązania z pojedynczą instrukcją return, kod wygląda dla mnie brzydiej. Na przykład:

if (condition)
   return 42;
else
   return 97;

To brzydkie, musisz użyć zmiennej lokalnej!

int result;
if (condition)
   result = 42;
else
   result = 97;
return result;

W jaki sposób ten 50% wzdęcie kodu sprawia, że ​​program jest łatwiejszy do zrozumienia? Osobiście uważam to za trudniejsze, ponieważ przestrzeń stanu właśnie wzrosła o kolejną zmienną, której można było łatwo zapobiec.

Oczywiście normalnie po prostu napisałbym:

return (condition) ? 42 : 97;

Ale wielu programistów unika operatora warunkowego i preferuje długą formę.

Skąd wzięło się pojęcie „tylko jeden powrót”? Czy istnieje historyczny powód, dla którego powstała ta konwencja?

fredoverflow
źródło
2
Jest to nieco związane z refaktoryzacją klauzuli ochronnej. stackoverflow.com/a/8493256/679340 Klauzula ochronna doda zwroty na początku twoich metod. I według mnie kod jest o wiele czystszy.
Piotr Perak,
3
Wywodzi się z pojęcia programowania strukturalnego. Niektórzy mogą twierdzić, że tylko jeden zwrot pozwala łatwo zmodyfikować kod, aby zrobić coś tuż przed zwrotem lub łatwo debugować.
martinkunev
3
Myślę, że przykład jest dość prostym przypadkiem, w którym nie miałbym silnej opinii w ten czy inny sposób. ideał single-entry-single-exit jest bardziej w stanie oderwać nas od szalonych sytuacji, takich jak 15 zwrotów i dwie kolejne gałęzie, które wcale nie wracają!
mendota,
2
To jeden z najgorszych artykułów, jakie kiedykolwiek czytałem. Wygląda na to, że autor spędza więcej czasu fantazjując o czystości swojego OOP, niż zastanawiając się, jak coś osiągnąć. Drzewa wyrażeń i oceny mają wartość, ale nie wtedy, gdy można po prostu napisać normalną funkcję.
DeadMG
3
Powinieneś całkowicie usunąć warunek. Odpowiedź to 42.
kambuzowy

Odpowiedzi:

1119

„Single Entry, Single Exit” zostało napisane, gdy większość programowania została wykonana w języku asemblera, FORTRAN lub COBOL. Został on źle zinterpretowany, ponieważ współczesne języki nie wspierają praktyk, przed którymi ostrzegała Dijkstra.

„Pojedyncze wejście” oznacza „nie twórz alternatywnych punktów wejścia dla funkcji”. Oczywiście w języku asemblera można wprowadzić funkcję w dowolnej instrukcji. FORTRAN obsługiwał wiele pozycji funkcji za pomocą ENTRYinstrukcji:

      SUBROUTINE S(X, Y)
      R = SQRT(X*X + Y*Y)
C ALTERNATE ENTRY USED WHEN R IS ALREADY KNOWN
      ENTRY S2(R)
      ...
      RETURN
      END

C USAGE
      CALL S(3,4)
C ALTERNATE USAGE
      CALL S2(5)

„Pojedyncze wyjście” oznaczało, że funkcja powinna powrócić tylko do jednego miejsca: instrukcji bezpośrednio po wywołaniu. To było nie oznacza to, że funkcja powinna zwracać tylko z jednego miejsca. Kiedy pisano programowanie strukturalne , powszechną praktyką było wskazywanie błędu przez funkcję poprzez powrót do innej lokalizacji. FORTRAN wspierał to poprzez „alternatywny zwrot”:

C SUBROUTINE WITH ALTERNATE RETURN.  THE '*' IS A PLACE HOLDER FOR THE ERROR RETURN
      SUBROUTINE QSOLVE(A, B, C, X1, X2, *)
      DISCR = B*B - 4*A*C
C NO SOLUTIONS, RETURN TO ERROR HANDLING LOCATION
      IF DISCR .LT. 0 RETURN 1
      SD = SQRT(DISCR)
      DENOM = 2*A
      X1 = (-B + SD) / DENOM
      X2 = (-B - SD) / DENOM
      RETURN
      END

C USE OF ALTERNATE RETURN
      CALL QSOLVE(1, 0, 1, X1, X2, *99)
C SOLUTION FOUND
      ...
C QSOLVE RETURNS HERE IF NO SOLUTIONS
99    PRINT 'NO SOLUTIONS'

Obie te techniki były wysoce podatne na błędy. Użycie alternatywnych wpisów często pozostawiało niektóre zmienne niezainicjowane. Zastosowanie alternatywnych zwrotów miało wszystkie problemy z instrukcją GOTO, z dodatkową komplikacją, że warunek rozgałęzienia nie sąsiadował z rozgałęzieniem, ale gdzieś w podprogramie.

Kevin Cline
źródło
38
I nie zapomnij kodu spaghetti . Nie było nieznane, aby podprogramy kończyły się przy użyciu GOTO zamiast powrotu, pozostawiając parametry wywołania funkcji i adres zwrotny na stosie. Pojedyncze wyjście było promowane jako sposób przynajmniej na skierowanie wszystkich ścieżek kodu do instrukcji RETURN.
TMN
2
@TMN: na początku większość maszyn nie miała stosu sprzętu. Rekursja zasadniczo nie była obsługiwana. Argumenty podprogramu i adres zwrotny były przechowywane w ustalonych lokalizacjach przylegających do kodu podprogramu. Powrót był tylko pośrednim goto.
kevin cline
5
@kevin: Tak, ale według ciebie to nawet nie znaczy, jak zostało wymyślone. (BTW, jestem właściwie pewien, że Fred zapytał, czy preferowana jest obecna interpretacja „Single Exit”.) Ponadto, C ma już to, constzanim wielu użytkowników tutaj się urodziło, więc nie ma już potrzeby stosowania stałych kapitałowych Ale nawet w C. Java zachowane wszystkie te złe starych nawyków C .
sbi 10.11.11
3
Czy więc wyjątki naruszają tę interpretację pojedynczego wyjścia? (Lub ich bardziej prymitywny kuzyn,? setjmp/longjmp)
Mason Wheeler
2
Mimo że operatorzy pytali o obecną interpretację pojedynczego zwrotu, ta odpowiedź jest najbardziej historyczna. Zasadniczo nie ma sensu stosowanie pojedynczego zwrotu , chyba że chcesz, aby Twój język pasował do niesamowitości VB (nie .NET). Pamiętaj tylko, aby używać logiki logicznej, która nie powoduje zwarcia.
acelent
912

Pojęcie pojedynczego wejścia, pojedynczego wyjścia (SESE) pochodzi z języków z jawnym zarządzaniem zasobami , takich jak C i asembler. W języku C taki kod spowoduje wyciek zasobów:

void f()
{
  resource res = acquire_resource();  // think malloc()
  if( f1(res) )
    return; // leaks res
  f2(res);
  release_resource(res);  // think free()
}

W takich językach masz zasadniczo trzy opcje:

  • Replikuj kod czyszczenia.
    Ugh. Redundancja jest zawsze zła.

  • Użyj a, gotoaby przejść do kodu czyszczenia.
    Wymaga to, aby kod czyszczenia był ostatnią rzeczą w funkcji. (I dlatego niektórzy twierdzą, że gotoma swoje miejsce. I rzeczywiście - w C.)

  • Wprowadź zmienną lokalną i manipuluj przez nią przepływem sterowania.
    Wadą jest to, że przepływ sterowania manipulowane przez składni (myślę break, return, if, while) jest o wiele łatwiejsze do naśladowania niż przepływ sterowania manipulowane przez stan zmiennych (bo te nie mają zmienne stanu, jeśli spojrzeć na algorytmie).

W asemblerze jest jeszcze dziwniej, ponieważ możesz wywołać dowolny adres w funkcji, gdy wywołujesz tę funkcję, co oznacza, że ​​masz prawie nieograniczoną liczbę punktów wejścia do dowolnej funkcji. (Czasami jest to pomocne. Takie zespoły są powszechną techniką dla kompilatorów do implementacji thisdostosowania wskaźnika koniecznego do wywoływania virtualfunkcji w scenariuszach wielokrotnego dziedziczenia w C ++.)

Gdy trzeba ręcznie zarządzać zasobami, korzystanie z opcji wprowadzania lub wychodzenia z funkcji w dowolnym miejscu prowadzi do bardziej złożonego kodu, a tym samym do błędów. Dlatego pojawiła się szkoła myślenia, która propagowała SESE, aby uzyskać czystszy kod i mniej błędów.


Jednak, gdy język zawiera wyjątki, (prawie) dowolna funkcja może zostać przedwcześnie zakończona (prawie) w dowolnym momencie, więc i tak należy przewidzieć możliwość przedwczesnego powrotu. (Myślę, że finallyjest głównie używany do tego w Javie i using(podczas implementacji IDisposable, w finallyprzeciwnym razie) w C #; C ++ zamiast tego wykorzystuje RAII .) Po wykonaniu tej czynności nie można nie posprzątać po sobie z powodu wczesnego returnoświadczenia, więc co jest prawdopodobnie najsilniejszy argument przemawiający za SESE zniknął.

To pozostawia czytelność. Oczywiście funkcja 200 LoC z returnlosowo posypanymi pół tuzinem instrukcji nie jest dobrym stylem programowania i nie zapewnia czytelnego kodu. Ale taka funkcja nie byłaby łatwa do zrozumienia bez tych przedwczesnych zwrotów.

W językach, w których zasoby nie są lub nie powinny być zarządzane ręcznie, przestrzeganie starej konwencji SESE ma niewielką lub żadną wartość. OTOH, jak argumentowałem powyżej, SESE często czyni kod bardziej złożonym . To dinozaur, który (oprócz C) nie pasuje dobrze do większości współczesnych języków. Zamiast pomagać w zrozumieniu kodu, utrudnia go.


Dlaczego programiści Java trzymają się tego? Nie wiem, ale z mojego (zewnętrznego) POV Java wzięła wiele konwencji z C (gdzie mają sens) i zastosowała je do swojego świata OO (gdzie są bezużyteczne lub wręcz złe), gdzie teraz się trzyma im, bez względu na koszty. (Podobna konwencja do definiowania wszystkich zmiennych na początku zakresu).

Programiści trzymają się różnego rodzaju dziwnych zapisów z irracjonalnych powodów. (Głęboko zagnieżdżone instrukcje strukturalne - „groty strzał” - były w takich językach jak Pascal, kiedyś postrzegane jako piękny kod.) Zastosowanie czystego logicznego rozumowania wydaje się nie przekonać większości z nich do odstąpienia od ustalonych sposobów. Najlepszym sposobem na zmianę takich nawyków jest prawdopodobnie nauczenie ich wcześnie, jak robić to, co najlepsze, a nie to, co konwencjonalne. Jako nauczyciel programowania masz go w ręku.:)

sbi
źródło
52
Dobrze. W Javie kod czyszczenia należy do finallyklauzul, w których jest wykonywany niezależnie od wczesnych returnwyjątków lub wyjątków.
dan04,
15
@ Dan04 w Javie 7 nie potrzebujesz nawet finallywiększości czasu.
R. Martinho Fernandes,
93
@Steven: Oczywiście możesz to zademonstrować! W rzeczywistości możesz wyświetlać zawiły i złożony kod za pomocą dowolnej funkcji, którą można również wyświetlić, aby uczynić kod prostszym i łatwiejszym do zrozumienia. Wszystko może być nadużywane. Chodzi o to, aby napisać kod, aby był łatwiejszy do zrozumienia , a kiedy to wiąże się z wyrzuceniem SESE przez okno, niech tak będzie i cholerne stare nawyki, które obowiązywały w różnych językach. Ale nie zawahałbym się kontrolować wykonywania przez zmienne, jeśli uważam, że dzięki temu kod jest łatwiejszy do odczytania. Po prostu nie pamiętam, że widziałem taki kod od prawie dwóch dekad.
sbi
21
@Karl: Rzeczywiście, poważną wadą języków GC, takich jak Java, jest to, że zwalniają cię one z konieczności czyszczenia jednego zasobu, ale zawodzą z innymi. (C ++ rozwiązuje ten problem dla wszystkich zasobów korzystających z RAII .) Ale nawet nie mówiłem tylko o pamięci (podałem tylko malloc()i free()jako komentarz), mówiłem ogólnie o zasobach. Nie sugerowałem też, że GC rozwiąże te problemy. (Wspomniałem o C ++, który nie ma GC po wyjęciu z pudełka.) Z tego, co rozumiem, Java finallyjest używana do rozwiązania tego problemu.
sbi
10
@sbi: Dla funkcji (procedury, metody itp.) ważniejsze niż nie dłuższe niż strona jest to, że funkcja ma jasno określoną umowę; jeśli nie robi czegoś wyraźnego, ponieważ został pocięty na kawałki, aby spełnić dowolne ograniczenie długości, to źle. Programowanie polega na rozgrywaniu różnych, czasem sprzecznych ze sobą sił.
Donal Fellows
81

Z jednej strony pojedyncze instrukcje zwrotne ułatwiają rejestrowanie, a także formy debugowania oparte na logowaniu. Pamiętam, że wiele razy musiałem zredukować funkcję do pojedynczego zwrotu, aby wydrukować wartość zwracaną w jednym punkcie.

  int function() {
     if (bidi) { print("return 1"); return 1; }
     for (int i = 0; i < n; i++) {
       if (vidi) { print("return 2"); return 2;}
     }
     print("return 3");
     return 3;
  }

Z drugiej strony, możesz to przełożyć na function()te połączenia _function()i rejestrować wynik.

perreal
źródło
31
Dodałbym również, że ułatwia to debugowanie, ponieważ wystarczy tylko ustawić jeden punkt przerwania, aby złapać wszystkie wyjścia * z funkcji. Wierzę, że niektóre IDE pozwalają ci ustawić punkt przerwania na bliskim nawiasie funkcji, aby zrobić to samo. (* chyba, że ​​zadzwonisz do wyjścia)
Skizz
3
Z podobnego powodu ułatwia także rozszerzenie (dodanie) funkcji, ponieważ nowa funkcja nie musi być wstawiana przed każdym powrotem. Załóżmy na przykład, że musisz zaktualizować dziennik z wynikiem wywołania funkcji.
JeffSahol,
63
Szczerze mówiąc, gdybym utrzymywał ten kod, wolałbym mieć rozsądnie zdefiniowane _function(), returnzs w odpowiednich miejscach, i opakowanie o nazwie, function()które obsługuje zewnętrzne rejestrowanie, niż mieć function()logikę ze zniekształconą logiką, aby wszystkie zwroty pasowały do ​​jednego wyjścia -point tylko po to, abym mógł wstawić dodatkową instrukcję przed tym punktem.
ruakh
11
W niektórych debuggerach (MSVS) możesz ustawić punkt przerwania na ostatnim zamykającym
nawiasie
6
drukowanie! = debugowanie. To wcale nie jest argument.
Piotr Perak,
53

„Single Entry, Single Exit” wywodzi się z rewolucji programowania strukturalnego z początku lat 70. XX wieku, którą zainicjował list Edsgera W. Dijkstry do redakcji „ Oświadczenie GOTO uznane za szkodliwe ”. Koncepcje programowania strukturalnego zostały szczegółowo opisane w klasycznej książce „Structured Programming” autorstwa Ole Johan-Dahla, Edsgera W. Dijkstry i Charlesa Anthony'ego Richarda Hoare'a.

„Oświadczenie GOTO uznane za szkodliwe” jest wymagane nawet dziś. „Programowanie strukturalne” jest przestarzałe, ale wciąż bardzo, bardzo satysfakcjonujące i powinno znajdować się na szczycie listy „Must Read” każdego programisty, znacznie przewyższającej wszystko np. Steve'a McConnella. (Sekcja Dahla przedstawia podstawy klas w Simula 67, które są techniczną podstawą dla klas w C ++ i całego programowania obiektowego.)

John R. Strohm
źródło
6
Artykuł został napisany na kilka dni przed C, kiedy GOTO były intensywnie używane. Nie są wrogami, ale ta odpowiedź jest zdecydowanie poprawna. Instrukcja return, która nie jest na końcu funkcji, jest w rzeczywistości goto.
user606723,
31
Artykuł został również napisany w czasach, gdy gotodosłownie można było przejść gdziekolwiek , na przykład do jakiegoś losowego punktu w innej funkcji, omijając wszelkie procedury, funkcje, stos wywołań itp. Żaden rozsądny język nie pozwala na to, aby w dzisiejszych czasach było to proste goto. C setjmp/ longjmpto jedyny częściowo wyjątkowy przypadek, o którym wiem, i nawet to wymaga współpracy z obu stron. (Częściowo ironiczne, że użyłem tam słowa „wyjątkowy”, biorąc pod uwagę, że wyjątki robią prawie to samo…) Zasadniczo artykuł zniechęca do praktyki, która już dawno nie żyła.
cHao
5
Z ostatniego akapitu „Oświadczenia Goto uznanego za szkodliwe”: „w [2] wydaje się, że Guiseppe Jacopini udowodnił (logiczną) zbyteczność wyrażenia„ przejdź do instrukcji ”. Ćwiczenie polegające na przełożeniu mniej lub bardziej mechanicznego schematu przepływu na skokowo mniej nie jest jednak zalecane . W związku z tym nie można oczekiwać, że wynikowy schemat będzie bardziej przejrzysty niż oryginalny.
hugomg
10
Co to ma wspólnego z pytaniem? Tak, praca Dijkstry ostatecznie doprowadziła do języków SESE i co z tego? Tak jak praca Babbage'a. Być może powinieneś ponownie przeczytać artykuł, jeśli uważasz, że mówi coś o posiadaniu wielu punktów wyjścia w funkcji. Ponieważ tak nie jest.
lipiec
10
@John, chyba próbujesz odpowiedzieć na pytanie, nie odpowiadając na nie. To dobra lista lektur, ale nie zacytowałeś ani nie parafrazowałeś niczego, co uzasadniałoby twoje twierdzenie, że ten esej i książka mają coś do powiedzenia na temat troski pytającego. Rzeczywiście, poza komentarzami nie powiedziałeś nic istotnego na temat tego pytania. Rozważ rozszerzenie tej odpowiedzi.
Shog9
35

Zawsze łatwo jest połączyć Fowler.

Jednym z głównych przykładów sprzecznych z SESE są klauzule ochronne:

Zastąp zagnieżdżone warunkowe klauzulami ochronnymi

Użyj klauzul ochronnych we wszystkich szczególnych przypadkach

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
return result;
};  

                                                                                                         http://www.refactoring.com/catalog/arrow.gif

double getPayAmount() {
    if (_isDead) return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired) return retiredAmount();
    return normalPayAmount();
};  

Więcej informacji znajduje się na stronie 250 Refaktoryzacji ...

Pieter B
źródło
11
Kolejny zły przykład: równie łatwo można go naprawić za pomocą else-ifs.
Jack
1
Twój przykład jest niesprawiedliwy, a co powiesz na to: double getPayAmount () {double ret = normalPayAmount (); if (_isDead) ret = deadAmount (); if (_isSeparated) ret = separowaneAmount (); if (_isRetired) ret = retiredAmount (); return ret; };
Charbel,
6
@Charbel To nie to samo. Jeśli _isSeparatedi _isRetiredmoże to być prawda (i dlaczego nie byłoby to możliwe?) Zwracasz niewłaściwą kwotę.
hvd
2
@Konchog „ Zagnieżdżone warunki warunkowe zapewnią lepszy czas wykonania niż klauzule zabezpieczające ”. To głównie wymaga cytowania. Mam wątpliwości, czy to w ogóle niezawodnie prawda. W tym przypadku, na przykład, w jaki sposób wczesne zwroty różnią się od logicznego zwarcia pod względem generowanego kodu? Nawet gdyby miało to znaczenie, nie wyobrażam sobie przypadku, w którym różnica byłaby czymś więcej niż nieskończenie małym kawałkiem. Stosujesz więc przedwczesną optymalizację, czyniąc kod mniej czytelnym, aby zaspokoić pewną niesprawdzoną teorię teoretyczną dotyczącą tego, co według ciebie prowadzi do nieco szybszego kodu. Nie robimy tego tutaj
podkreślenie_d
1
@underscore_d, masz rację. zależy to bardzo od kompilatora, ale może zająć więcej miejsca. Spójrz na dwa pseudo-zespoły i łatwo zrozumieć, dlaczego klauzule ochronne pochodzą z języków wysokiego poziomu. Test „A” (1); odgałęzienie kończy się niepowodzeniem; test (2); odgałęzienie kończy się niepowodzeniem; test (3); odgałęzienie kończy się niepowodzeniem; {CODE} end: return; Test „B” (1); branch_good next1; powrót; next1: test (2); branch_good next2; powrót; next2: test (3); branch_good next3; powrót; next3: {CODE} return;
Konchog
11

Jakiś czas temu napisałem blog na ten temat.

Najważniejsze jest to, że ta reguła pochodzi z epoki języków, które nie mają funkcji czyszczenia pamięci ani obsługi wyjątków. Nie ma formalnego badania, które wykazałoby, że ta zasada prowadzi do lepszego kodu we współczesnych językach. Możesz go zignorować, gdy tylko doprowadzi to do skrócenia lub zwiększenia czytelności kodu. Ludzie z Javy, którzy nalegają na to, są ślepi i niekwestionowani zgodnie z przestarzałą, bezcelową zasadą.

To pytanie zostało również zadane na Stackoverflow

Anthony
źródło
Hej, nie mogę już dotrzeć do tego linku. Czy zdarza się, że masz wersję, która jest hostowana gdzieś nadal dostępna?
Nic Hartley,
Cześć, QPT, dobre miejsce. Przywróciłem post na blogu i zaktualizowałem powyższy adres URL. Powinien link teraz!
Anthony
Jest w tym jednak coś więcej. O wiele łatwiej jest zarządzać precyzyjnym harmonogramem wykonania przy użyciu SESE. Zagnieżdżone warunki warunkowe często można zrefaktoryzować za pomocą przełącznika. Nie chodzi tylko o to, czy jest zwracana wartość.
Konchog
Jeśli masz zamiar twierdzić, że nie ma formalnego badania na poparcie tego, to byłoby słuszne, abyś linkował do takiego, który jest przeciwny.
Mehrdad
Mehrdad, jeśli istnieje formalne studium na jego poparcie, pokaż to. To wszystko. Naleganie na dowód przesuwa ciężar dowodu.
Anthony
7

Jeden zwrot ułatwia refaktoryzację. Spróbuj wykonać „metodę wyodrębniania” do wewnętrznej części pętli for, która zawiera zwrot, przerwanie lub kontynuowanie. To się nie powiedzie, ponieważ przerwałeś kontrolę.

Chodzi o to: Myślę, że nikt nie udaje, że pisze idealny kod. Dlatego kodowanie jest regularnie poddawane refaktoryzacji w celu „ulepszenia” i rozszerzenia. Więc moim celem byłoby, aby mój kod był jak najbardziej przyjazny dla refaktoryzacji.

Często mam problem z tym, że muszę całkowicie przeformułować funkcje, jeśli zawierają one wyłączniki kontroli przepływu i jeśli chcę dodać tylko niewielką funkcjonalność. Jest to bardzo podatne na błędy, ponieważ zmieniasz cały przepływ sterowania zamiast wprowadzać nowe ścieżki do izolowanych zagnieżdżeń. Jeśli masz tylko jeden zwrot na końcu lub używasz strażników do wyjścia z pętli, to oczywiście masz więcej zagnieżdżania i więcej kodu. Ale zyskujesz możliwości refaktoryzacji obsługiwane przez kompilator i IDE.

oopexpert
źródło
To samo dotyczy zmiennych. Które są alternatywą dla stosowania konstrukcji przepływów kontrolnych, takich jak wczesny powrót.
Deduplicator
Zmienne w większości przypadków nie będą przeszkadzały w rozbiciu kodu na części w taki sposób, że zachowany zostanie istniejący przepływ kontrolny. Spróbuj „wyodrębnić metodę”. IDE są w stanie przeprowadzać refaktoryzacje przed kontrolą przepływu, ponieważ nie są w stanie uzyskać semantyki z tego, co napisałeś.
oopexpert
5

Weź pod uwagę fakt, że wiele instrukcji zwrotu jest równoważnych posiadaniu GOTO z jedną instrukcją zwrotu. To samo dotyczy instrukcji break. Dlatego niektórzy, podobnie jak ja, uważają je za GOTO dla wszystkich celów i celów.

Jednak nie uważam tych rodzajów GOTO za szkodliwe i nie zawaham się użyć rzeczywistego GOTO w moim kodzie, jeśli znajdę dobry powód.

Zasadą ogólną jest to, że GOTO służą wyłącznie do kontroli przepływu. Nigdy nie należy ich używać do zapętlania i nigdy nie należy GOTOWAĆ „w górę” lub „w tył”. (tak działają przerwy / zwroty)

Jak wspomnieli inni, poniżej należy przeczytać oświadczenie GOTO uważane za szkodliwe.
Należy jednak pamiętać, że zostało to napisane w 1970 r., Kiedy GOTO były nadmiernie nadużywane. Nie każde GOTO jest szkodliwe i nie odradzałbym ich używania, dopóki nie użyjesz ich zamiast normalnych konstrukcji, ale raczej w dziwnym przypadku, gdy użycie normalnych konstrukcji byłoby bardzo niewygodne.

Uważam, że używanie ich w przypadkach błędów, w których musisz uciec z obszaru z powodu awarii, która nigdy nie powinna wystąpić w normalnych przypadkach, czasami jest przydatna. Ale powinieneś również rozważyć umieszczenie tego kodu w osobnej funkcji, abyś mógł po prostu wrócić wcześniej zamiast używać GOTO ... ale czasami jest to również niewygodne.

606723
źródło
6
Wszystkie konstrukcje strukturalne, które zastępują goto, są implementowane w kategoriach goto. Np. Pętle, „if” i „case”. Nie czyni ich to złymi - wręcz przeciwnie. Ponadto są to „intencje i cele”.
Anthony
Touche, ale to nie różni się ode mnie ... To po prostu nieco wyjaśnia moje wyjaśnienie. No cóż.
user606723,
GOTO powinno być zawsze w porządku, o ile (1) cel znajduje się w tej samej metodzie lub funkcji i (2) kierunek jest w kodzie naprzód (pomiń kod) i (3) cel nie znajduje się w innej zagnieżdżonej strukturze (np. GOTO od środka if-case do środka else-case). Jeśli zastosujesz się do tych zasad, wszystkie niewłaściwe użycie GOTO ma naprawdę silny zapach kodu zarówno wizualnie, jak i logicznie.
Mikko Rantalainen,
3

Złożoność cykliczna

Widziałem, że SonarCube używa wielu instrukcji return do określania złożoności cyklicznej. Im więcej instrukcji return, tym wyższa złożoność cykliczna

Zmień typ zwrotu

Wiele zwrotów oznacza, że ​​musimy zmienić w wielu miejscach funkcji, gdy zdecydujemy się zmienić typ zwrotu.

Wiele wyjść

Trudniej jest debugować, ponieważ logika musi być dokładnie przestudiowana w połączeniu z instrukcjami warunkowymi, aby zrozumieć, co spowodowało zwróconą wartość.

Refaktoryzowane rozwiązanie

Rozwiązaniem wielu instrukcji return jest zastąpienie ich polimorfizmem z jednym zwrotem po rozstrzygnięciu wymaganego obiektu implementacji.

Sorter
źródło
3
Przejście od wielu zwrotów do ustawiania wartości zwracanej w wielu miejscach nie eliminuje cykliczności złożoności, tylko ujednolica lokalizację wyjścia. Pozostają wszystkie problemy, które złożoność cykliczna może wskazywać w danym kontekście. „Trudniej jest debugować, ponieważ logika musi być dokładnie przestudiowana w połączeniu z instrukcjami warunkowymi, aby zrozumieć, co spowodowało zwróconą wartość” Ponownie logika nie zmienia się poprzez ujednolicenie zwrotu. Jeśli musisz dokładnie przestudiować kod, aby zrozumieć, jak on działa, należy go ponownie sformułować z kropką.
WillD