GOTO nadal uważane za szkodliwe? [Zamknięte]


282

Wszyscy zdają sobie sprawę z listów Dijkstry do redaktora: przejdź do oświadczenia uważanego za szkodliwe (także tutaj .html transkryptu i tutaj .pdf), a od tego czasu nastąpił ogromny nacisk, aby unikać oświadczenia goto, gdy tylko jest to możliwe. Chociaż można użyć goto do wytworzenia niemożliwego do utrzymania, rozrastającego się kodu, pozostaje on jednak w nowoczesnych językach programowania . Nawet zaawansowaną strukturę kontroli kontynuacji na schemacie można opisać jako wyrafinowane goto.

Jakie okoliczności uzasadniają użycie goto? Kiedy najlepiej tego uniknąć?

Jako kolejne pytanie: C udostępnia parę funkcji, setjmp i longjmp, które zapewniają możliwość przejścia nie tylko w bieżącą ramkę stosu, ale w dowolną ramkę wywołującą. Czy należy je uważać za tak niebezpieczne, jak goto? Bardziej niebezpieczne?


Sam Dijkstra żałował tego tytułu, za który nie był odpowiedzialny. Pod koniec EWD1308 (także tutaj .pdf) napisał:

Wreszcie krótka historia do nagrania. W 1968 r. W komunikatach ACM opublikowano mój tekst pod tytułem „ Oświadczenie goto uznane za szkodliwe ”, które w późniejszych latach będzie najczęściej przywoływane, niestety, często przez autorów, którzy nie widzieli go więcej niż jego tytuł, który stał się kamieniem węgielnym mojej sławy, stając się szablonem: widzieliśmy różnego rodzaju artykuły pod tytułem „X uważane za szkodliwe” dla prawie każdego X, w tym jeden zatytułowany „Dijkstra uważany za szkodliwy”. Ale co się stało? Złożyłem artykuł pod tytułem „ Sprawa przeciwko oświadczeniu goto”, który, aby przyspieszyć publikację, redaktor zmienił się w„ list do redakcji ”, nadając mu nowy tytuł własnego wynalazku! Redaktorem był Niklaus Wirth.

Dobrze przemyślanym klasycznym tekstem na ten temat, który można dopasować do tego, który napisał Dijkstra, jest Programowanie strukturalne z użyciem go w oświadczeniach Donalda E. Knutha. Czytanie pomaga przywrócić kontekst i nie-dogmatyczne zrozumienie tematu. W tym artykule przedstawiono opinię Dijkstry w tej sprawie, która jest jeszcze mocniejsza:

Donald E. Knuth: Uważam, że przedstawiając taki pogląd, tak naprawdę nie zgadzam się ostro z pomysłami Dijkstry, ponieważ ostatnio napisał on: „Proszę, nie wpadnij w pułapkę, wierząc, że jestem strasznie dogmatyczny wobec [ przejdź do oświadczenia]. Mam niewygodne wrażenie, że inni tworzą z niej religię, tak jakby koncepcyjne problemy programowania można rozwiązać za pomocą jednej sztuczki, prostej formy kodowania!


1
Goto C # nie jest tym samym, co goto, o którym mówił Dijkstra, właśnie z powodów, o których mówił Dijkstra. To goto jest zarówno szkodliwe jak wtedy, ale także o wiele mniej konieczne, ponieważ współczesne języki zapewniają alternatywne struktury kontroli. C # goto jest bardzo ograniczony.
lipiec

25
Gotowie są dobrzy, gdy dodają jasności. Jeśli masz długą zagnieżdżoną pętlę, lepiej wyjść z niej niż ustawić zmienne „break” i przerywać, dopóki nie wyjdziesz.
simendsjo

28
Jeśli masz zagnieżdżoną pętlę na 4 głębokościach (nie jest to dobre), wyrwanie się ze wszystkich wymaga ustawienia wartości tymczasowych. Goto tutaj jest dla mnie znacznie bardziej zrozumiałe, a IDE powinno łatwo pokazać, gdzie jest goto. To powiedziawszy, użycie goto powinno być rzadkie, i moim zdaniem zejdź tylko w dół, aby pominąć kod
simendsjo

7
Proponuję przeczytać dziewięć tysięcy i jeden wątek otagowany goto.
Matti Virkkunen,

5
Jest jedna rzecz wyraźnie gorsza niż używanie goto: hakowanie ustrukturyzowanych narzędzi programistycznych razem w celu wdrożenia goto.

Odpowiedzi:


184

Poniższe stwierdzenia są uogólnieniami; chociaż zawsze można powołać się na wyjątek, zwykle (według mojego doświadczenia i skromnej opinii) nie warto ryzykować.

  1. Nieograniczone korzystanie z adresów pamięci (GOTO lub nieprzetworzonych wskaźników) zapewnia zbyt wiele okazji do łatwych do uniknięcia błędów.
  2. Im więcej jest sposobów na dotarcie do określonej „lokalizacji” w kodzie, tym mniej można mieć pewność co do stanu systemu w tym momencie. (Patrz poniżej.)
  3. Programowanie strukturalne IMHO mniej polega na „unikaniu GOTO”, a bardziej na dopasowywaniu struktury kodu do struktury danych. Na przykład powtarzająca się struktura danych (np. Tablica, plik sekwencyjny itp.) Jest naturalnie przetwarzana przez powtarzającą się jednostkę kodu. Posiadanie wbudowanych struktur (np. While, for, till, for-each itp.) Pozwala programiście uniknąć nudy powtarzania tych samych wzorców kodu.
  4. Nawet jeśli GOTO jest szczegółem implementacji niskiego poziomu (nie zawsze tak jest!), To poniżej poziomu, o którym powinien myśleć programista. Ilu programistów balansuje swoje osobiste książeczki czekowe w surowym pliku binarnym? Ilu programistów martwi się o to, który sektor na dysku zawiera konkretny rekord, zamiast po prostu podać klucz do silnika bazy danych (i ile sposobów mogłoby się nie udać, gdybyśmy naprawdę napisali wszystkie nasze programy pod względem fizycznych sektorów dysku)?

Przypisy do powyższego:

W odniesieniu do pkt 2 rozważ następujący kod:

a = b + 1
/* do something with a */

W punkcie „zrób coś” w kodzie możemy stwierdzić z dużą pewnością, że ajest większa niż b. (Tak, ignoruję możliwość niezapełnionego przepełnienia liczb całkowitych. Nie bagatelizujmy prostego przykładu.)

Z drugiej strony, jeśli kod odczytał w ten sposób:

...
goto 10
...
a = b + 1
10: /* do something with a */
...
goto 10
...

Wielość sposobów dotarcia do etykiety 10 oznacza, że ​​musimy dużo ciężej pracować, aby mieć pewność co do relacji między tym punktem aa btym. (W rzeczywistości w ogólnym przypadku jest to niezdecydowane!)

Jeśli chodzi o punkt 4, całe pojęcie „pójścia gdzieś” w kodzie jest tylko metaforą. Wewnątrz procesora nic tak naprawdę nie „trafia” poza elektronami i fotonami (ciepło odpadowe). Czasami rezygnujemy z metafory dla innej, bardziej użytecznej. Pamiętam, że spotkałem (kilka dekad temu!) Język, w którym

if (some condition) {
  action-1
} else {
  action-2
}

został zaimplementowany na maszynie wirtualnej przez skompilowanie działania 1 i działania 2 jako pozaparametrowych procedur bez parametrów, a następnie za pomocą pojedynczego dwuargumentowego kodu maszynowego VM, który wykorzystał wartość logiczną warunku do wywołania jednego lub drugiego. Pomysł polegał po prostu na „wybierz, co wywołać teraz”, a nie „idź tu lub idź tam”. Znów tylko zmiana metafory.


2
Dobra uwaga. W językach wyższego poziomu goto tak naprawdę nic nie znaczy (rozważ przeskakiwanie między metodami w Javie). Funkcja Haskell może składać się z pojedynczego wyrażenia; spróbuj wyskoczyć z tego z goto!
Ślimak mechaniczny

2
Postscript działa jak twój przykład z punktu 4.
luser droog

Smalltalk działa podobnie do przykładu z punktu 4, jeśli przez „podobnie” nie masz na myśli „nic podobnego do języków proceduralnych”. : P Nie ma kontroli przepływu w języku; wszystkie decyzje są obsługiwane przez polimorfizm ( truei falsesą różnego rodzaju), a każda gałąź if / else jest w zasadzie lambda.
cHao

Są to ważne punkty, ale w końcu powtarzają, jak złe goto może być „jeśli niewłaściwie wykorzystane”. Przerwa, kontynuacja, wyjście, powrót, gosub, ustalanie czasu, globalne, uwzględnianie itp. To wszystkie nowoczesne techniki, które wymagają mentalnego śledzenia przepływu rzeczy i mogą być niewłaściwie wykorzystywane do tworzenia kodu spaghetti i pomijania w celu stworzenia niepewności stanów zmiennych. Szczerze mówiąc, chociaż nigdy nie widziałem złego użycia goto z pierwszej ręki, widziałem go tylko raz lub dwa razy. To przemawia do stwierdzenia, że ​​zawsze są lepsze rzeczy do użycia.
Beejor

gotow nowoczesnym języku programowania (Go) stackoverflow.com/a/11065563/3309046 .
nishanths

245

GOTO Comic XKCD

Mój współpracownik powiedział, że jedynym powodem korzystania z GOTO jest zaprogramowanie się tak daleko w kącie, że jest to jedyne wyjście. Innymi słowy, odpowiednio zaprojektuj z wyprzedzeniem i nie będziesz musiał później używać GOTO.

Pomyślałem, że ten komiks pięknie ilustruje to, że „mógłbym zrestrukturyzować przepływ programu lub użyć jednego małego„ GOTO ”. GOTO jest słabym wyjściem, gdy masz słaby projekt. Welociraptory żerują na słabych .


41
GOTO może „przeskakiwać” z jednego dowolnego miejsca do innego dowolnego miejsca. Velociraptor skoczył tu znikąd!
rpattabi,

29
wcale nie uważam żartu za zabawny, ponieważ wszyscy wiedzą, że musisz również utworzyć link, aby móc wykonać.
Kinjal Dixit

31
Twój współpracownik się myli i oczywiście nie czytał gazety Knutha.
Jim Balter

27
Nie widziałem końca zaciemnionego i zniekształconego kodu przez lata wprowadzonego tylko po to, aby uniknąć goto. @jimmcKeeth, powyższa kreskówka xkcd nie dowodzi, że goto jest słaby. Wyśmiewa się z histerii związanej z jego używaniem.

4
Zdajesz sobie sprawę, że kod źródłowy biblioteki Delphi zawiera instrukcje goto. I to są odpowiednie zastosowania goto.
David Heffernan

131

Czasami warto używać GOTO jako alternatywy dla obsługi wyjątków w ramach jednej funkcji:

if (f() == false) goto err_cleanup;
if (g() == false) goto err_cleanup;
if (h() == false) goto err_cleanup;

return;

err_cleanup:
...

Wygląda na to, że kod COM dość często wpada w ten schemat.


29
Zgadzam się, istnieją uzasadnione przypadki użycia, w których goto może uprościć kod i uczynić go bardziej czytelnym / łatwym w utrzymaniu, ale wydaje się, że istnieje pewien gotobofobia unosząca się wokół ...
Pop Catalin

48
@ Bob: Trudno jest przenieść kod err_cleanup do podprogramu, jeśli usuwa on zmienne lokalne.
Niki,

4
Właściwie użyłem go w COM / VB6 tylko dlatego, że nie miałem alternatywy, a nie dlatego, że była to alternatywa. Jak szczęśliwy jestem dzisiaj z try / catch / wreszcie.
Rui Craveiro,

10
@ user4891 Idiomatycznym sposobem C ++ nie jest próba {} catch () {cleanup; }, ale raczej RAII, w którym zasoby, które należy oczyścić, są wykonywane w destruktorach. Każdy konstruktor / destruktor zarządza dokładnie jednym zasobem.
David Stone,

5
Istnieją dwa sposoby pisania tego w C bez goto; i oba są znacznie krótsze. Albo: if (f ()) if (g ()) if (h ()) return return; sprzątać(); błąd powrotu; lub: if (f () && g () && h ()) zwracają sukces; sprzątać(); błąd powrotu;
LHP

124

Mogę sobie przypomnieć tylko raz, używając goto. Miałem serię pięciu zagnieżdżonych zliczonych pętli i musiałem być w stanie wcześnie wyrwać się z całej struktury, zależnie od pewnych warunków:

for{
  for{
    for{
      for{
        for{
          if(stuff){
            GOTO ENDOFLOOPS;
          }
        }
      }
    }
  }
}

ENDOFLOOPS:

Mogłem po prostu z łatwością zadeklarować zmienną typu boolean i użyć jej jako części warunku dla każdej pętli, ale w tym przypadku zdecydowałem, że GOTO jest tak samo praktyczne i tak samo czytelne.

Żadne velociraptory mnie nie zaatakowały.


83
„Przekształć go w funkcję i zastąp goto zwrotem :)”, a różnica jest taka? naprawdę jaka jest różnica? nie jest również powrót do? Zwraca również hamuje strukturalny przepływ, podobnie jak goto, aw tym przypadku robią to w ten sam sposób (nawet jeśli goto może być użyte do ważniejszych rzeczy)
Pop Catalin

38
Zagnieżdżanie wielu pętli jest zwykle zapachem całego kodu. O ile nie robisz, na przykład, 5-wymiarowego mnożenia tablic, trudno jest wyobrazić sobie sytuację, w której niektóre wewnętrzne pętle nie mogłyby być użytecznie wyodrębnione na mniejsze funkcje. Jak wszystkie praktyczne zasady, istnieje kilka wyjątków.
Doug McClean,

5
Zastąpienie go zwrotem działa tylko wtedy, gdy używasz języka obsługującego zwroty.
Loren Pechtel

31
@leppie: Pokolenie, które zbuntowało się przeciwko gotoi dało nam programowanie strukturalne, również z tego samego powodu odrzuciło wczesne powroty. Wszystko sprowadza się do tego, jak czytelny jest kod, jak jasno wyraża intencje programisty. Utworzenie funkcji w innym celu niż uniknięcie użycia źle sformułowanego słowa kluczowego powoduje złą spójność: lekarstwo jest gorsze niż choroba.
Błoto

21
@ButtleButkus: Szczerze mówiąc, to jest tak samo złe, jeśli nie gorsze. Przynajmniej za pomocą goto, można jawnie określić cel. Za pomocą break 5;(1) muszę policzyć zamknięcia pętli, aby znaleźć miejsce docelowe; oraz (2) jeśli struktura pętli kiedykolwiek się zmieni, może wymagać zmiany tej liczby, aby zachować prawidłowe miejsce docelowe. Jeśli mam zamiar tego uniknąć goto, zysk powinien polegać na tym, że nie muszę ręcznie śledzić takich rzeczy.
cHao

93

Już mieliśmy tę dyskusję i podtrzymuję swój punkt widzenia .

Ponadto, mam dość ludzi opisujących struktury języka na wyższym poziomie jako „ gotow przebraniu”, ponieważ wyraźnie nie mamy punkt w ogóle . Na przykład:

Nawet zaawansowaną strukturę kontroli kontynuacji na schemacie można opisać jako wyrafinowane goto.

To kompletny nonsens. Każda struktura sterowania może być wdrożona pod względem, gotoale ta obserwacja jest całkowicie trywialna i bezużyteczna. gotonie jest uważany za szkodliwy z powodu jego pozytywnych skutków, ale z powodu jego negatywnych konsekwencji, które zostały wyeliminowane przez programowanie strukturalne.

Podobnie powiedzenie „GOTO jest narzędziem i jak wszystkie narzędzia mogą być używane i nadużywane” jest zupełnie nie na miejscu. Żaden współczesny pracownik budowlany nie użyłby skały i twierdziłby, że „jest narzędziem”. Skały zostały zastąpione młotami. gotozostał zastąpiony przez struktury kontrolne. Gdyby robotnik budowlany utknął na wolności bez młota, oczywiście użyłby zamiast tego skały. Jeśli programista musi użyć gorszego języka programowania, który nie ma funkcji X, to oczywiście może ona będzie musiała użyć gotozamiast tego. Ale jeśli używa go gdziekolwiek indziej zamiast odpowiedniej funkcji językowej, to wyraźnie nie zrozumiała języka poprawnie i używa go niewłaściwie. To naprawdę tak proste.


82
Oczywiście właściwe użycie skały nie jest jak młot. Jednym z jego właściwych zastosowań jest kamień szlifierski lub ostrzenie innych narzędzi. Nawet niski kamień, gdy jest właściwie stosowany, jest dobrym narzędziem. Musisz tylko znaleźć właściwe użycie. To samo dotyczy goto.
Kibbee

10
Jakie jest właściwe użycie Goto? Dla każdego możliwego przypadku istnieje inne narzędzie, które lepiej się nadaje. Nawet twój kamień szlifierski jest obecnie zastępowany przez zaawansowane technologicznie narzędzia, nawet jeśli nadal są one wykonane z kamienia. Istnieje duża różnica między surowcem a narzędziem.
Konrad Rudolph

8
@jalf: Idź na pewno nie istnieje w języku C #. Zobacz stackoverflow.com/questions/359436/...
Jon Skeet,

51
Jestem przerażony, że tak wielu ludzi akceptuje ten post. Twój post wydawał się skuteczny tylko dlatego, że nigdy nie zadawałeś sobie pytania, jaką logikę faktycznie grałeś, więc nie zauważyłeś swojego błędu. Pozwólcie, że sparafrazuję cały post: „W każdej sytuacji istnieje lepsze narzędzie dla goto, więc nigdy nie należy go używać”. Jest to logiczne dwuwarunkowe i jako taki cały post zasadniczo pyta: „Skąd wiesz, że istnieje lepsze narzędzie dla goto w każdej sytuacji?”.
Kodowanie w stylu

10
@ Kodowanie: Nie, całkowicie pominąłeś istotę posta. To był riposta, a nie izolowany, kompletny argument. Zwróciłem jedynie uwagę na błąd w głównym argumencie „za” goto. Masz rację, o ile nie proponuję argumentów przeciw samym gotow sobie - nie zamierzałem - więc nie ma pytań.
Konrad Rudolph

91

Goto jest bardzo nisko na mojej liście rzeczy do włączenia do programu tylko ze względu na to. To nie znaczy, że to niedopuszczalne.

Idź może być miły dla maszyn państwowych. Instrukcja switch w pętli jest (w kolejności typowej ważności): (a) w rzeczywistości nie jest reprezentatywna dla przepływu sterowania, (b) brzydka, (c) potencjalnie nieefektywna w zależności od języka i kompilatora. W rezultacie piszesz jedną funkcję dla każdego stanu i robisz takie rzeczy, jak „return NEXT_STATE;” które nawet wyglądają jak goto.

To prawda, że ​​trudno jest kodować maszyny stanów w sposób, który czyni je łatwymi do zrozumienia. Jednak żadna z tych trudności nie wiąże się z użyciem goto i żadnej z nich nie można zmniejszyć za pomocą alternatywnych struktur kontrolnych. Chyba że twój język ma konstrukcję „maszyny stanów”. Mój nie.

W tych rzadkich przypadkach, gdy twój algorytm jest naprawdę najbardziej zrozumiały, jeśli chodzi o ścieżkę przez sekwencję węzłów (stanów) połączoną ograniczonym zestawem dopuszczalnych przejść (gotos), a nie bardziej szczegółowym przepływem sterowania (pętle, warunki warunkowe itp. ), powinno to być wyraźnie określone w kodzie. I powinieneś narysować ładny schemat.

setjmp / longjmp może być przydatne do implementacji wyjątków lub zachowania podobnego do wyjątku. Choć nie są powszechnie chwalone, wyjątki są ogólnie uważane za „prawidłową” strukturę kontroli.

setjmp / longjmp są „bardziej niebezpieczne” niż goto w tym sensie, że trudniej je używać poprawnie, nie wspominając o zrozumieniu.

Nigdy nie było ani nie będzie żadnego języka, w którym pisanie złego kodu byłoby najmniej trudne. - Donald Knuth.

Usunięcie goto z C nie ułatwiłoby pisania dobrego kodu w C. W rzeczywistości wolałby raczej nie rozumieć, że C powinien działać jako chwalebny język asemblera.

Następnie będą to „wskaźniki uważane za szkodliwe”, a następnie „pisanie kaczek uznane za szkodliwe”. Kto zatem będzie cię bronił, gdy przyjdą zabrać twój niebezpieczny program? Co?


14
Osobiście, to jest komentarz, na który dałbym czek. Jedną z rzeczy, na które chciałbym zwrócić uwagę czytelników, jest to, że ezoteryczny termin „maszyny stanowe” obejmuje takie codzienne rzeczy, jak analizatory leksykalne. Sprawdź wyniki lex kiedyś. Pełna gotów.
TED,

2
Można użyć instrukcji switch wewnątrz pętli (lub procedury obsługi zdarzeń), aby dobrze wykonywać maszyny stanów. Zrobiłem wiele automatów państwowych bez konieczności używania jmp lub goto.
Scott Whitlock,

11
+1 Te strzałki na automatach stanowych odwzorowują „goto” bliżej niż do jakiejkolwiek innej struktury kontrolnej. Jasne, możesz użyć przełącznika wewnątrz pętli - tak jak możesz użyć kilku gotów zamiast chwilowo dla innych problemów, ale zwykle jest to pomysł; co stanowi sedno tej dyskusji.
Edmund

2
Czy mogę zacytować ten ostatni akapit?
Chris Lutz

2
I tutaj, w 2013 roku, już przeszliśmy (i jakby minęliśmy) fazę „wskaźników uważanych za szkodliwe”.
Isiah Meadows

68

W Linuksie: Korzystanie z goto W Kernel Code on Kernel Trap toczy się dyskusja z Linusem Torvaldsem i „nowym facetem” na temat wykorzystania GOTO w kodzie Linux. Jest tam kilka bardzo dobrych punktów i Linus ubrany w zwykłą arogancję :)

Niektóre fragmenty:

Linus: „Nie, zostałeś poddany praniu mózgu przez ludzi z CS, którzy myśleli, że Niklaus Wirth naprawdę wiedział, o czym mówi. Nie wiedział. Nie ma cholernej wskazówki.”

-

Linus: „Myślę, że goto są w porządku i często są bardziej czytelne niż duże wcięcia”.

-

Linus: „Oczywiście w głupich językach, takich jak Pascal, gdzie etykiety nie mogą być opisowe, goto mogą być złe”.


9
To dobrze, jak? Dyskutują o jego zastosowaniu w języku, w którym nie ma nic innego. Kiedy programujesz w asemblerze, wszystkie gałęzie i skoki goto. A C jest i był „przenośnym językiem asemblera”. Co więcej, cytowane fragmenty nie mówią nic o tym, dlaczego uważa, że ​​goto jest dobre.
lipiec

5
Łał. To rozczarowujące czytać. Można by pomyśleć, że taki wielki OS, jak Linus Torvalds, wiedziałby lepiej, niż to powiedzieć. Pascal (oldschoolowy pascal, a nie współczesna wersja Object) był tym, co napisano w Mac OS Classic w okresie 68 tys. I był to najbardziej zaawansowany system operacyjny swoich czasów.
Mason Wheeler,

6
@mason Classic Mac OS miał kilka bibliotek Pascal (ostatecznie - środowisko uruchomieniowe Pascal zajmowało zbyt dużo pamięci na wczesnych komputerach Mac), ale większość kodu podstawowego została napisana w Asemblerze, szczególnie w grafice i procedurach interfejsu użytkownika.
Jim Dovey

30
Linus tylko argumentuje (wyraźnie, jak Rik van Riel w tej dyskusji) za goto do obsługi statusu wyjścia, i robi to na podstawie złożoności, jaką przyniosłyby alternatywne konstrukcje C, gdyby zostały użyte.
Charles Stewart

21
IMHO Linus ma rację w tej sprawie. Chodzi mu o to, że kod jądra napisany w C, który musi implementować coś podobnego do obsługi wyjątków, jest najwyraźniej i najprościej napisany przy użyciu goto. Idiom goto cleanup_and_exitjest jednym z nielicznych „dobrych” zastosowań goto lewo teraz, że mamy for, whilei ifzarządzać naszą przepływ sterowania. Zobacz także: programmers.stackexchange.com/a/154980
steveha

50

W C gotodziała tylko w zakresie bieżącej funkcji, która ma tendencję do lokalizowania wszelkich potencjalnych błędów. setjmpi longjmpsą znacznie bardziej niebezpieczne, ponieważ są nielokalne, skomplikowane i zależne od wdrożenia. W praktyce są jednak zbyt niejasne i rzadkie, aby powodować wiele problemów.

Uważam, że niebezpieczeństwo gotow C jest znacznie przesadzone. Pamiętaj, że oryginalne gotoargumenty miały miejsce w czasach języków takich jak staromodny BASIC, gdzie początkujący pisali kod spaghetti w ten sposób:

3420 IF A > 2 THEN GOTO 1430

Tutaj Linus opisuje odpowiednie zastosowanie goto: http://www.kernel.org/doc/Documentation/CodingStyle (rozdział 7).


7
Kiedy BASIC był po raz pierwszy dostępny, nie było żadnej alternatywy dla GOTO nnnn i GOSUB mmmm jako sposobów na przeskakiwanie. Strukturalne konstrukty zostały dodane później.
Jonathan Leffler

7
Nie rozumiesz sensu ... nawet wtedy nie musiałeś pisać spaghetti ... twoje GOTO zawsze mogły być używane w zdyscyplinowany sposób
JoelFan

Warto również zauważyć, że zachowanie setjmp/ longjmpzostało określone tylko wtedy, gdy były używane jako sposób przeskakiwania do miejsca w zasięgu z innych miejsc w tym samym zakresie. Gdy kontrola opuści zakres, w którym setjmpjest wykonywana, każda próba użycia longjmpna stworzonej przez nią strukturze setjmpspowoduje niezdefiniowane zachowanie.
supercat

9
Niektóre wersje BASICa na to pozwalają GOTO A * 40 + B * 200 + 30. Nietrudno dostrzec, jak to było bardzo przydatne i bardzo niebezpieczne.
Jon Hanna,

1
@ Hjulle obliczy wyrażenie, a następnie przejdzie do wiersza kodu z tym numerem (wyraźne numery wierszy były wymagane przez większość wcześniejszych dialektów). ZX Spectrum Basic był w stanie to zaakceptować
Jon Hanna

48

Dzisiaj trudno jest dostrzec wielką wagę tego GOTOstwierdzenia, ponieważ ludzie „programowania strukturalnego” przeważnie wygrywają debatę, a dzisiejsze języki mają wystarczającą strukturę kontroli, której można uniknąć GOTO.

Policz liczbę gotos we współczesnym programie C. Teraz dodać liczbę break, continueoraz returnsprawozdania. Ponadto, należy dodać kilka razy użyć if, else, while, switchlub case. To tyle, ile GOTOmiałby Twój program, gdybyś pisał w FORTRAN lub BASIC w 1968 roku, kiedy Dijkstra napisał swój list.

W tamtym czasie brakowało języków programowania. Na przykład w oryginalnym BASIC Dartmouth:

  • IFoświadczenia nie miały ELSE. Jeśli chcesz, musisz napisać:

    100 IF NOT condition THEN GOTO 200
    ...stuff to do if condition is true...
    190 GOTO 300
    200 REM else
    ...stuff to do if condition is false...
    300 REM end if
    
  • Nawet jeśli twoje IFoświadczenie nie potrzebowało ELSE, nadal było ograniczone do jednej linii, która zwykle składała się z GOTO.

  • Nie było DO...LOOPoświadczenia. W przypadku nie- FORpętli trzeba było zakończyć pętlę jawnie GOTOlub IF...GOTOwrócić do początku.

  • Tam nie było SELECT CASE. Musiałeś użyć ON...GOTO.

Tak więc skończyłeś z dużą ilością GOTOsw swoim programie. I nie mogłeś polegać na ograniczeniu GOTOs do jednego podprogramu (ponieważ GOSUB...RETURNbyła to tak słaba koncepcja podprogramów), więc GOTOmogły one iść gdziekolwiek . Oczywiście utrudniało to kontrolę przepływu.

Stąd pochodzi anty- GOTOruch.


Inną rzeczą do odnotowania jest to, że preferowanym sposobem pisania kodu, jeśli ktoś ma jakiś kod w pętli, który musiałby być wykonywany rzadko, byłby 420 if (rare_condition) then 3000// 430 and onward: rest of loop and other main-line code// 3000 [code for rare condition]// 3230 goto 430. Pisanie kodu w ten sposób pozwala uniknąć wszelkich odgałęzień lub skoków w typowym przypadku linii głównej, ale utrudnia śledzenie. Unikanie rozgałęzień w kodzie asemblera może być gorsze, jeśli niektóre rozgałęzienia są ograniczone do np. +/- 128 bajtów i czasami nie mają par komplementarnych (np. Istnieje „cjne”, ale nie „cje”).
supercat

Kiedyś napisałem kod dla 8x51, który miał przerwanie, które działało raz na 128 cykli. Każdy dodatkowy cykl spędzony we wspólnym przypadku tego ISR zmniejszyłby szybkość wykonywania kodu linii głównej o ponad 1% (myślę, że około 90 cykli ze 128 było zwykle dostępnych dla linii głównej), a każda instrukcja rozgałęzienia zajęłaby dwa cykle ( zarówno w przypadkach rozgałęzień, jak i awaryjnych). Kod miał dwa porównania - jedno, które zwykle wykazywałoby równość; drugi nie jest równy. W obu przypadkach kod rzadkiego przypadku znajdował się w odległości większej niż 128 bajtów. Więc ...
supercat

cjne r0,expected_value,first_comp_springboard/.../ cjne r1,unexpected_value,second_comp_fallthrough// `ajmp second_comp_target` // first_comp_springboard: ajmp first_comp_target// second_comp_fallthrough: .... Niezbyt fajny wzór kodowania, ale kiedy liczą się poszczególne cykle, robi się takie rzeczy. Oczywiście w latach 60. takie poziomy optymalizacji były ważniejsze niż obecnie, zwłaszcza że współczesne procesory często wymagają dziwnych optymalizacji, a systemy kompilacji just-in-time mogą być w stanie zastosować kod optymalizacji dla procesorów, które nie istniały, gdy kod został napisany.
supercat

34

Idź do może zapewnić rodzaj „stand-in” do obsługi „prawdziwych” wyjątków w niektórych przypadkach. Rozważać:

ptr = malloc(size);
if (!ptr) goto label_fail;
bytes_in = read(f_in,ptr,size);
if (bytes_in=<0) goto label_fail;
bytes_out = write(f_out,ptr,bytes_in);
if (bytes_out != bytes_in) goto label_fail;

Oczywiście ten kod został uproszczony, aby zajmował mniej miejsca, więc nie odkładaj się zbytnio na szczegółach. Ale rozważ alternatywę, którą widziałem zbyt wiele razy w kodzie produkcyjnym przez programistów dążących do absurdalnych długości, aby uniknąć użycia goto:

success=false;
do {
    ptr = malloc(size);
    if (!ptr) break;
    bytes_in = read(f_in,ptr,size);
    if (count=<0) break;
    bytes_out = write(f_out,ptr,bytes_in);
    if (bytes_out != bytes_in) break;
    success = true;
} while (false);

Teraz funkcjonalnie ten kod robi dokładnie to samo. W rzeczywistości kod wygenerowany przez kompilator jest prawie identyczny. Jednak w zapał programisty, by uspokoić Nogoto ( budzącego grozę boga upomnień akademickich), ten programista całkowicie złamał podstawowy idiom, który whilereprezentuje pętla, i zrobił prawdziwą liczbę na temat czytelności kodu. To nie jest lepsze.

Morał tej historii jest taki, że jeśli uciekasz się do czegoś naprawdę głupiego, aby uniknąć używania goto, nie rób tego.


1
Chociaż zgadzam się z tym, co tu mówisz, fakt, że breakinstrukcje znajdują się w strukturze kontroli, wyjaśnia dokładnie, co robią. W tym gotoprzykładzie osoba czytająca kod musi przejrzeć kod, aby znaleźć etykietę, która teoretycznie mogłaby faktycznie znajdować się przed strukturą kontroli. Nie mam wystarczającego doświadczenia z C w starym stylu, aby sądzić, że jedno jest zdecydowanie lepsze od drugiego, ale i tak istnieją kompromisy.
Vivian River,

5
@DanielAllenLangdon: Fakt, że breaks są wewnątrz pętli, wyraźnie pokazuje, że wychodzą z pętli . To nie jest „dokładnie to, co robią”, ponieważ w rzeczywistości nie ma w ogóle pętli! Nic w tym nigdy nie ma szansy na powtórzenie, ale do końca nie jest to jasne. Fakt, że masz „pętlę”, która nigdy nie działa więcej niż jeden raz, oznacza, że ​​struktury kontrolne są nadużywane. Na gotoprzykład programista może powiedzieć goto error_handler;. Jest to bardziej jednoznaczne i jeszcze trudniejsze do naśladowania. (Ctrl + F, „moduł obsługi błędów:”, aby znaleźć cel. Spróbuj to zrobić za pomocą „}”.)
cHao

1
Kiedyś widziałem kod podobny do twojego drugiego przykładu w systemie kontroli ruchu lotniczego - ponieważ „goto nie ma w naszym leksykonie”.
QED

30

Donald E. Knuth odpowiedział na to pytanie w książce „Literate Programming”, 1992 CSLI. Na str. 17 znajduje się esej „ Programowanie strukturalne za pomocą instrukcji goto ” (PDF). Myślę, że artykuł mógł zostać opublikowany również w innych książkach.

Artykuł opisuje sugestię Dijkstry i opisuje okoliczności, w których jest to ważne. Podaje także wiele przykładów liczników (problemów i algorytmów), których nie można łatwo odtworzyć przy użyciu tylko pętli strukturalnych.

Artykuł zawiera pełny opis problemu, historię, przykłady i przykłady przeciwne.


25

Goto uważany za pomocny.

Zacząłem programować w 1975 roku. Dla programistów z lat 70. słowa „goto uważane za szkodliwe” mówiły mniej więcej, że nowe języki programowania z nowoczesnymi strukturami sterowania są warte wypróbowania. Próbowaliśmy nowych języków. Szybko się przekonaliśmy. Nigdy nie wróciliśmy.

Nigdy nie wróciliśmy, ale jeśli jesteś młodszy, to nigdy tam nie byłeś.

Teraz tło w starożytnych językach programowania może nie być bardzo przydatne, z wyjątkiem wskaźnika wieku programisty. Niezależnie od tego młodsi programiści nie znają tego tła, więc nie rozumieją już przesłania, które hasło „stało się uważane za szkodliwe” przekazane docelowym odbiorcom w momencie jego wprowadzenia.

Hasła, których nie rozumiemy, nie są zbyt pouczające. Prawdopodobnie najlepiej zapomnieć o takich hasłach. Takie hasła nie pomagają.

Jednak to konkretne hasło „Goto uważane za szkodliwe” przyjęło własne nieumarłe życie.

Czy nie można nadużywać? Odpowiedź: jasne, ale co z tego? Praktycznie każdy element programowania może być nadużywany. Na boolprzykład pokorni są wykorzystywani częściej niż niektórzy z nas chcieliby w to uwierzyć.

Dla kontrastu nie pamiętam, żeby spotkałem się z jednym faktycznym przypadkiem znęcania się nad Goto od 1990 roku.

Największym problemem związanym z goto jest prawdopodobnie nie techniczny, ale społeczny. Programiści, którzy nie wiedzą zbyt wiele, czasami wydają się czuć, że wycofanie goto sprawia, że ​​brzmią inteligentnie. Od czasu do czasu będziesz musiał zadowolić takich programistów. Takie jest życie.

Najgorszą rzeczą w goto dzisiaj jest to, że nie jest wystarczająco używana.


24

Zainteresowany dodaniem odpowiedzi przez Jaya Ballou, dodam 0,02 £. Gdyby Bruno Ranschaert jeszcze tego nie zrobił, wspomniałbym o artykule Knutha „Programowanie strukturalne z oświadczeniami GOTO”.

Jedną rzeczą, o której nie widziałem, jest rodzaj kodu, który, choć nie jest tak powszechny, był nauczany w podręcznikach Fortrana. Rzeczy takie jak rozszerzony zakres pętli DO i podprogramy o otwartym kodzie (pamiętaj, że byłby to Fortran II lub Fortran IV lub Fortran 66 - nie Fortran 77 lub 90). Istnieje co najmniej szansa, że ​​szczegóły składniowe są niedokładne, ale pojęcia powinny być wystarczająco dokładne. Fragmenty w każdym przypadku znajdują się w jednej funkcji.

Zwróć uwagę, że doskonała, ale przestarzała (i wyczerpana) książka „ The Elements of Programming Style, 2nd Edn ” autorstwa Kernighan & Plauger zawiera kilka rzeczywistych przykładów nadużywania GOTO w programowaniu podręczników swojej epoki (koniec lat 70.). Poniższy materiał nie pochodzi jednak z tej książki.

Rozszerzony zasięg dla pętli DO

       do 10 i = 1,30
           ...blah...
           ...blah...
           if (k.gt.4) goto 37
91         ...blah...
           ...blah...
10     continue
       ...blah...
       return
37     ...some computation...
       goto 91

Jednym z powodów takich nonsensów była dobra, staromodna karta uderzeniowa. Można zauważyć, że etykiety (ładnie spoza sekwencji, ponieważ był to styl kanoniczny!) Znajdują się w kolumnie 1 (w rzeczywistości musiały być w kolumnach 1-5), a kod znajduje się w kolumnach 7-72 ​​(kolumna 6 była kontynuacją kolumna znaczników). Kolumny 73-80 otrzymałyby numer sekwencyjny, a były maszyny, które sortowałyby talie kart dziurkaczy według kolejności numerów sekwencyjnych. Jeśli masz program na sekwencyjnych kartach i potrzebujesz dodać kilka kart (linii) do środka pętli, będziesz musiał ponownie wszystko uruchomić po tych dodatkowych liniach. Jeśli jednak zastąpisz jedną kartę GOTO, możesz uniknąć ponownego szeregowania wszystkich kart - po prostu włożyłeś nowe karty na końcu procedury z nowymi numerami sekwencji. Uważaj to za pierwszą próbę „zielonego przetwarzania”

Och, możesz też zauważyć, że oszukuję i nie krzyczę - Fortran IV napisano normalnie wielkimi literami.

Podprogram o otwartym kodzie

       ...blah...
       i = 1
       goto 76
123    ...blah...
       ...blah...
       i = 2
       goto 76
79     ...blah...
       ...blah...
       goto 54
       ...blah...
12     continue
       return
76     ...calculate something...
       ...blah...
       goto (123, 79) i
54     ...more calculation...
       goto 12

GOTO między etykietami 76 i 54 jest wersją obliczonego goto. Jeśli zmienna i ma wartość 1, przejdzie do pierwszej etykiety na liście (123); jeśli ma wartość 2, jest drugi i tak dalej. Fragment od 76 do obliczonego goto jest podprogramem o otwartym kodzie. Był to fragment kodu wykonywany raczej jak podprogram, ale zapisany w treści funkcji. (Fortran miał również funkcje instrukcji - które były funkcjami osadzonymi, które pasowały do ​​jednej linii).

Były gorsze konstrukcje niż obliczone goto - można przypisać etykiety do zmiennych, a następnie użyć przypisanego goto. Goto przypisane do Goo mówi mi, że został usunięty z Fortran 95. Podejmij wyzwanie dla rewolucji w programowaniu strukturalnym, o której można powiedzieć, że rozpoczęła się publicznie listem lub artykułem „GOTO za szkodliwe” Dijkstry.

Bez pewnej wiedzy na temat tego, co zostało zrobione w Fortranie (i w innych językach, z których większość słusznie się pomogła), trudno jest nam, nowicjuszom, zrozumieć zakres problemu, z którym borykał się Dijkstra. Cholera, zacząłem programować dopiero dziesięć lat po opublikowaniu tego listu (ale miałem nieszczęście zaprogramować w Fortran IV).


2
Jeśli chcesz zobaczyć przykład jakiegoś kodu używającego goto„na wolności”, pytanie Wanted: Working Bose-Hibbard Sort Algorytm pokazuje jakiś kod (Algol 60) opublikowany w 1963 roku. Oryginalny układ nie jest zgodny z nowoczesnymi standardami kodowania. Wyjaśniony (wcięty) kod jest wciąż dość nieprzenikniony. Te gotooświadczenia nie robić zrobić to (bardzo) trudno zrozumieć, co algorytm jest do.
Jonathan Leffler

1
Będąc zbyt młodym, by doświadczyć czegoś bliskiego do uderzania kart, było pouczające, aby przeczytać o problemie ponownego przebijania. +1
Qix - MONICA MISTREATED

20

Nie ma takich rzeczy, jak GOTO uważane za szkodliwe .

GOTO jest narzędziem i, jak wszystkie narzędzia, można z niego korzystać i nadużywać .

Istnieje jednak wiele narzędzi w świecie programowania, które mają tendencję do częstszego wykorzystywania niż używania , a GOTO jest jednym z nich. instrukcja Z Delphi jest kolejna.

Osobiście nie używam żadnego z typowych kodów , ale miałem dziwne użycie GOTO i WITH, które były uzasadnione, a alternatywne rozwiązanie zawierałoby więcej kodu.

Najlepszym rozwiązaniem byłoby, aby kompilator po prostu ostrzegł cię, że słowo kluczowe zostało skażone , i będziesz musiał umieścić kilka instrukcji pragma wokół instrukcji, aby pozbyć się ostrzeżeń.

To tak, jakby powiedzieć dzieciom, żeby nie biegały nożyczkami . Nożyczki nie są złe, ale niektóre z nich mogą nie być najlepszym sposobem na utrzymanie zdrowia.


Problem z GOTO polega na tym, że łamie ważne niezmienniki, które uznajemy za oczywiste w przypadku nowoczesnych języków programowania. Na przykład, jeśli wywołam funkcję, zakładamy, że gdy funkcja się zakończy, zwróci kontrolę nad wywołującym, albo normalnie, albo poprzez wyjątkowe odwijanie stosu. Jeśli ta funkcja źle wykorzystuje GOTO, to oczywiście nie jest to już prawdą. To bardzo utrudnia rozumowanie naszego kodu. Nie wystarczy ostrożnie unikać niewłaściwego użycia GOTO. Problem nadal występuje, jeśli GOTO jest niewłaściwie wykorzystywane przez nasze zależności ...
Jonathan Hartley,

... Aby wiedzieć, czy możemy uzasadnić nasze wywołania funkcji, musimy zbadać każdą linię kodu źródłowego każdej z naszych zależności przechodnich, sprawdzając, czy nie nadużywają GOTO. Tak więc samo istnienie GOTO w tym języku złamało naszą zdolność do pewnego rozumowania własnego kodu, nawet jeśli sami go wykorzystamy doskonale (lub nigdy go nie użyjemy). Z tego powodu GOTO to nie tylko narzędzie, z którego należy ostrożnie korzystać. Jest on systematycznie łamany, a jego istnienie w języku jest jednostronnie uważane za szkodliwe.
Jonathan Hartley

Nawet jeśli gotosłowo kluczowe zostało usunięte z C #, koncepcja „skocz tutaj” wciąż istnieje w IL. Nieskończoną pętlę można łatwo zbudować bez słowa kluczowego goto. Jeśli ten brak gwarancji, że wywołany kod zwróci, Twoim zdaniem, oznacza „niezdolność do rozumowania o kodzie”, to powiedziałbym, że nigdy nie mieliśmy takiej możliwości.
Lasse V. Karlsen

Ach, wnikliwie znalazłeś źródło naszej złej komunikacji. In gotoC # nie jest prawdziwym „goto” w pierwotnym znaczeniu tego słowa. Jest to znacznie słabsza wersja, która pozwala tylko przeskakiwać w obrębie funkcji. W sensie, w którym używam „goto”, można skakać gdziekolwiek w trakcie procesu. Więc chociaż C # ma słowo kluczowe „goto”, prawdopodobnie nigdy nie było, prawdziwe goto. Tak, prawdziwe goto jest dostępne na poziomie IL, w taki sam sposób, jak w przypadku kompilacji dowolnego języka aż do asemblera. Ale programista jest przed tym chroniony, nie może go używać w normalnych okolicznościach, więc się nie liczy.
Jonathan Hartley,

Nawiasem mówiąc, opinia, którą tu opisuję, nie jest pierwotnie moja, ale jest rdzeniem oryginalnej pracy Dijkstry z 1967 roku, jak sądzę, pod tytułem jego redaktora „Goto uważa się za szkodliwe”, która stała się tak często cytowanym memem dla 50 lat właśnie dlatego, że był tak rewolucyjny, wnikliwy i powszechnie akceptowany.
Jonathan Hartley,


17

Odkąd zacząłem robić kilka rzeczy w jądrze Linuksa, gotos nie przeszkadza mi tak bardzo jak kiedyś. Na początku byłem trochę przerażony, widząc, że (ludzie z jądra) dodali gotos do mojego kodu. Od tego czasu przyzwyczaiłem się do używania gotów, w niektórych ograniczonych kontekstach, i od czasu do czasu używam ich osobiście. Zazwyczaj jest to goto, które przeskakuje na koniec funkcji, aby wykonać jakieś czyszczenie i wyskoczyć, zamiast powielać to samo czyszczenie i ratowanie w kilku miejscach funkcji. I zazwyczaj nie jest to coś wystarczająco dużego, aby przekazać inną funkcję - np. Uwolnienie niektórych zmiennych lokalnych (k) malloc'ów jest typowym przypadkiem.

Napisałem kod, który używał setjmp / longjmp tylko raz. Było to w programie sekwencera perkusyjnego MIDI. Odtwarzanie odbyło się w oddzielnym procesie od wszystkich interakcji użytkownika, a proces odtwarzania wykorzystał pamięć współdzieloną z procesem interfejsu użytkownika, aby uzyskać ograniczone informacje potrzebne do odtworzenia. Gdy użytkownik chciał zatrzymać odtwarzanie, proces odtwarzania po prostu wykonał longjmp „od początku”, aby rozpocząć od nowa, zamiast skomplikowanego odwijania gdziekolwiek to się dzieje, gdy użytkownik chciał, aby się zatrzymał. Działało świetnie, było proste i nigdy nie miałem z tym żadnych problemów ani błędów.

setjmp / longjmp mają swoje miejsce - ale to miejsce prawdopodobnie nie będzie odwiedzane, ale od czasu do czasu.

Edycja: Właśnie spojrzałem na kod. Tak naprawdę to siglongjmp (), którego użyłem, a nie longjmp (nie to, że to wielka sprawa, ale zapomniałem, że siglongjmp nawet istniał.)


15

Jeśli piszesz maszynę wirtualną w C, okazuje się, że używając gotowych obliczeń (gcc):

char run(char *pc) {
    void *opcodes[3] = {&&op_inc, &&op_lda_direct, &&op_hlt};
    #define NEXT_INSTR(stride) goto *(opcodes[*(pc += stride)])
    NEXT_INSTR(0);
    op_inc:
    ++acc;
    NEXT_INSTR(1);
    op_lda_direct:
    acc = ram[++pc];
    NEXT_INSTR(1);
    op_hlt:
    return acc;
}

działa znacznie szybciej niż konwencjonalny przełącznik wewnątrz pętli.


Jedynym problemem jest to, że nie jest to standard, prawda?
Calmarius

&&op_incna pewno się nie kompiluje, ponieważ (po lewej) &oczekuje wartości, ale (po prawej) &daje wartość.
fredoverflow

7
@FredO: To specjalny operator GCC. Jednak odrzuciłbym ten kod w najgorszych okolicznościach, ponieważ z całą pewnością nie rozumiem, co się dzieje w wtf.
Szczeniak

Chociaż jest to jeden przykład (maks. Optymalizacja prędkości), który uzasadnia zarówno użycie goto, jak i kodu kryptycznego, należy go jednak bardzo skomentować, aby wyjaśnić potrzebę optymalizacji, z grubsza, jak to działa, i najlepszą linię po linii komentarze, które można wprowadzić. Tak więc nadal nie działa, ale ponieważ pozostawia programistów konserwacji w ciemności. Ale podoba mi się przykład VM. Dzięki.
MicroservicesOnDDD

14

Ponieważ gotomoże być stosowany do mylącego metaprogramowania

Gotojest zarówno wyrażeniem kontrolnym wysokiego, jak i niskiego poziomu , w wyniku czego po prostu nie ma odpowiedniego wzorca projektowego odpowiedniego dla większości problemów.

To niskiego poziomu w tym sensie, że jest prymitywny goto operacja, która implementuje coś jak wyżej whilelub foreachczy coś.

Jest to wysoki poziom w tym sensie, że gdy jest używany w określony sposób, pobiera kod, który wykonuje się w przejrzystej sekwencji, w sposób nieprzerwany, z wyjątkiem strukturalnych pętli, i zmienia go w logikę, która przy wystarczającej liczbie gotos jest chwytaniem - torba logiki jest dynamicznie składana.

Jest więc prozaiczna i zła strona goto.

Prosaic bok jest skierowany ku górze, wskazując goto może stosować całkowicie wystarczającą pętlę i skierowaną w dół goto potrafi doskonale rozsądne breaklub return. Oczywiście rzeczywisty while, breaklub returnbyłby o wiele bardziej czytelny, ponieważ biedny człowiek nie musiałby symulować efektu goto, aby uzyskać duży obraz. Ogólnie zły pomysł.

Strona zła obejmuje rutynę polegającą na tym, że nie używa się goto na chwilę, przerywa lub wraca, ale używa go do tak zwanej logiki spaghetti . W tym przypadku szczęśliwy deweloper goto buduje fragmenty kodu z labiryntu goto, a jedynym sposobem na zrozumienie go jest symulacja go mentalnie jako całości, strasznie męczące zadanie, gdy jest wiele goto. Wyobraź sobie trud obliczenia kodu, w którym elsenie jest on dokładnie odwrotnością tego if, gdzie zagnieżdżone ifs mogą pozwolić na niektóre rzeczy, które zostały odrzucone przez zewnętrzne if, itp.

Wreszcie, aby naprawdę omówić ten temat, należy zauważyć, że zasadniczo wszystkie wczesne języki, z wyjątkiem Algola, początkowo uzależniły tylko pojedyncze stwierdzenia od swoich wersji if-then-else. Tak więc jedynym sposobem na wykonanie bloku warunkowego było gotoobejście go za pomocą odwrotnego warunkowego. Szalony, wiem, ale przeczytałem kilka starych specyfikacji. Pamiętaj, że pierwsze komputery zostały zaprogramowane w binarnym kodzie maszynowym, więc przypuszczam, że jakikolwiek HLL był ratownikiem; Myślę, że nie byli zbyt wybredni, jeśli chodzi o to, jakie dokładnie funkcje HLL mają.

Powiedziawszy to, co zwykle wkładałem gotow każdy program, który napisałem „tylko po to, by zirytować purystów” .


4
+1 za denerwowanie purystów! :-)
Lumi,

10

Odmawianie programistom użycia instrukcji GOTO jest jak mówienie stolarzowi, aby nie używał młotka, ponieważ może on uszkodzić ścianę, gdy wbija gwóźdź. Prawdziwy programista wie, jak i kiedy używać GOTO. Podążałem za niektórymi z tak zwanych „programów strukturalnych”. Widzę taki kod Horrida, aby uniknąć używania GOTO, aby móc strzelać do programisty. Ok, w obronie drugiej strony, widziałem też trochę prawdziwego kodu spaghetti i ci programiści też powinni zostać zastrzeleni.

Oto tylko jeden mały przykład kodu, który znalazłem.

  YORN = ''
  LOOP
  UNTIL YORN = 'Y' OR YORN = 'N' DO
     CRT 'Is this correct? (Y/N) : ':
     INPUT YORN
  REPEAT
  IF YORN = 'N' THEN
     CRT 'Aborted!'
     STOP
  END

-----------------------LUB----------------------

10:  CRT 'Is this Correct (Y)es/(N)o ':

     INPUT YORN

     IF YORN='N' THEN
        CRT 'Aborted!'
        STOP
     ENDIF
     IF YORN<>'Y' THEN GOTO 10

3
DO CRT „Czy to jest poprawne? (T / N): ': WEJŚCIE YORN DO YORN =' Y 'LUB YORN =' N '; itp.
joel.neely

5
Rzeczywiście, ale co ważniejsze, prawdziwy programista wie, kiedy nie używać goto- i wie, dlaczego . Unikanie konstrukcji języka tabu, ponieważ $ program_guru tak powiedział, to jest właśnie definicja programowania kultowego.
Piskvor opuścił budynek

1
To dobra analogia. Aby wbić gwóźdź bez uszkadzania podłoża, nie eliminujesz młotka. Zamiast tego używasz prostego narzędzia zwanego gwoździem do paznokci. Jest to metalowa szpilka ze zwężającym się końcem z pustym wgłębieniem na końcu, aby bezpiecznie połączyć się z główką gwoździa (narzędzia te są dostępne w różnych rozmiarach dla różnych gwoździ). Drugi, tępy koniec stempla jest uderzany młotkiem.
Kaz


7

Oryginalny artykuł należy traktować jako „bezwarunkowe GOTO uważane za szkodliwe”. W szczególności opowiadał się za formą programowania opartą na konstrukcjach warunkowych ( if) i iteracyjnych ( while), a nie testowaniem i przeskakiwaniem typowym dla wczesnego kodu. gotojest nadal przydatny w niektórych językach lub okolicznościach, w których nie istnieje odpowiednia struktura kontroli.


6

O jedynym miejscem zgadzam Goto mógł być wykorzystane, to kiedy musisz poradzić sobie z błędami, a każdy konkretny punkt, w którym pojawia się błąd, wymaga specjalnej obsługi.

Na przykład, jeśli chwytasz zasoby i używasz semaforów lub muteksów, musisz chwytać je w kolejności i zawsze powinieneś zwalniać je w odwrotny sposób.

Niektóre kody wymagają bardzo dziwnego wzorca chwytania tych zasobów i nie można po prostu napisać łatwej w utrzymaniu i zrozumiałej strukturze sterowania, aby poprawnie obsłużyć zarówno chwytanie, jak i zwalnianie tych zasobów, aby uniknąć impasu.

Zawsze można to zrobić poprawnie bez goto, ale w tym przypadku i kilku innych Goto jest w rzeczywistości lepszym rozwiązaniem przede wszystkim dla czytelności i łatwości konserwacji.

-Adam


5

Jednym z nowoczesnych zastosowań GOTO jest kompilator C # do tworzenia automatów stanów dla enumeratorów zdefiniowanych przez zwrot z wydajności.

GOTO jest czymś, z czego powinni korzystać kompilatorzy, a nie programiści.


5
Jak myślisz, kto dokładnie tworzy kompilatory?
tloach

10
Oczywiście kompilatory!
Seiti

3
Myślę, że ma na myśli „GOTO jest czymś, z czego powinien korzystać tylko kod emitowany przez kompilator”.
Simon Buchan,

To przypadek przeciwko goto. Tam, gdzie moglibyśmy użyć gotow maszynie stanu zakodowanej ręcznie do implementacji modułu wyliczającego, możemy po prostu użyć yieldzamiast tego.
Jon Hanna,

włącza wiele ciągów znaków (aby zapobiec kompilacji do if-else) z domyślnymi kompilacjami wielkości liter do przełączania za pomocą instrukcji goto.
M.kazem Akhgary

5

Dopóki C i C ++ (pośród innych sprawców) nie oznaczą przerw i kontynuuje, goto będzie nadal odgrywać pewną rolę.


Więc oznaczone przerwanie lub kontynuacja różni się od goto jak?
Matthew Whited,

3
Nie pozwalają na całkowicie arbitralne skoki w przepływie sterowania.
DrPizza

5

Gdyby samo GOTO było złe, kompilatory byłyby złe, ponieważ generują JMP. Jeśli przeskakiwanie do bloku kodu, szczególnie podążającego za wskaźnikiem, byłoby z natury złe, instrukcja POWROTU byłaby zła. Przeciwnie, zło może być nadużywane.

Czasami musiałem pisać aplikacje, które musiały śledzić pewną liczbę obiektów, w których każdy obiekt musiał reagować na skomplikowaną sekwencję stanów w odpowiedzi na zdarzenia, ale całość była zdecydowanie jednowątkowa. Typowa sekwencja stanów, jeśli jest reprezentowana w pseudokodzie, to:

request something
wait for it to be done
while some condition
    request something
    wait for it
    if one response
        while another condition
            request something
            wait for it
            do something
        endwhile
        request one more thing
        wait for it
    else if some other response
        ... some other similar sequence ...
    ... etc, etc.
endwhile

Jestem pewien, że to nie jest nowe, ale sposób, w jaki obsługiwałem to w C (++), to zdefiniowanie niektórych makr:

#define WAIT(n) do{state=(n); enque(this); return; L##n:;}while(0)
#define DONE state = -1

#define DISPATCH0 if state < 0) return;
#define DISPATCH1 if(state==1) goto L1; DISPATCH0
#define DISPATCH2 if(state==2) goto L2; DISPATCH1
#define DISPATCH3 if(state==3) goto L3; DISPATCH2
#define DISPATCH4 if(state==4) goto L4; DISPATCH3
... as needed ...

Następnie (zakładając, że stan jest początkowo 0), ustrukturyzowana maszyna stanów powyżej zamienia się w kod strukturalny:

{
    DISPATCH4; // or as high a number as needed
    request something;
    WAIT(1); // each WAIT has a different number
    while (some condition){
        request something;
        WAIT(2);
        if (one response){
            while (another condition){
                request something;
                WAIT(3);
                do something;
            }
            request one more thing;
            WAIT(4);
        }
        else if (some other response){
            ... some other similar sequence ...
        }
        ... etc, etc.
    }
    DONE;
}

Z pewną odmianą może być CALL i RETURN, więc niektóre maszyny stanowe mogą działać jak podprogramy innych maszyn stanowych.

Czy to niezwykłe? Tak. Czy zajmuje to trochę nauki ze strony opiekuna? Tak. Czy ta nauka się opłaca? Chyba tak. Czy można tego dokonać bez GOTO, które skaczą w bloki? Nie.


1
Unikanie języka ze strachu jest oznaką uszkodzenia mózgu. Jest to także bardziej przyjemne niż piekło.
Wektor Gorgoth,

4

Unikam tego, ponieważ współpracownik / menedżer bez wątpienia zakwestionuje jego użycie w recenzji kodu lub kiedy natknie się na niego. Chociaż wydaje mi się, że ma to zastosowanie (na przykład przypadek obsługi błędów) - popełnisz błąd innego programisty, który będzie miał z tym jakiś problem.

To nie jest tego warte.


Zaletą Try ... Catch bloki w C # jest to, że zajmują się czyszczeniem stosu i innymi przydzielonymi zasobami (zwanymi odwijaniem stosu), ponieważ wyjątek przechodzi do procedury obsługi wyjątku. To sprawia, że ​​Try ... Catch jest znacznie lepszy niż Goto, więc jeśli masz Try ... Catch, użyj go.
Scott Whitlock,

Mam lepsze rozwiązanie: zawiń fragment kodu, z którego chcesz wyskoczyć w 'do {...} while (0);' pętla. W ten sposób możesz wyskoczyć w ten sam sposób co goto bez narzutu pętli try / catch (nie wiem o C #, ale w C ++ try jest tani, a catch jest drogi, więc wydaje się, że nadmierne, aby rzucić wyjątek, w którym wystarczyłby zwykły skok).
Jim Dovey

10
Jim, problem polega na tym, że jest to tylko głupi, okrągły sposób na uzyskanie goto.
Kodowanie w stylu

4

Byłem zmuszony użyć goto, ponieważ dosłownie nie mogłem wymyślić lepszego (szybszego) sposobu na napisanie tego kodu:

Miałem złożony obiekt i musiałem wykonać na nim pewną operację. Jeśli obiekt był w jednym stanie, mógłbym wykonać szybką wersję operacji, w przeciwnym razie musiałbym wykonać wolną wersję operacji. Chodziło o to, że w niektórych przypadkach, w trakcie powolnej operacji można było zrozumieć, że można to zrobić przy szybkiej operacji.

SomeObject someObject;    

if (someObject.IsComplex())    // this test is trivial
{
    // begin slow calculations here
    if (result of calculations)
    {
        // just discovered that I could use the fast calculation !
        goto Fast_Calculations;
    }
    // do the rest of the slow calculations here
    return;
}

if (someObject.IsmediumComplex())    // this test is slightly less trivial
{
    Fast_Calculations:
    // Do fast calculations
    return;
}

// object is simple, no calculations needed.

Było to w kawałku kodu interfejsu użytkownika w czasie rzeczywistym, więc szczerze sądzę, że GOTO było tutaj uzasadnione.

Hugo


Sposobem innym niż GOTO byłoby użycie funkcji fast_calculations, co pociąga za sobą pewne obciążenie. Prawdopodobnie nie jest zauważalny w większości przypadków, ale, jak powiedziałeś, miało to krytyczne znaczenie dla prędkości.
Kyle Cronin

1
Cóż, nie jest to zaskakujące. Cały kod, który ma absolutnie szalone wytyczne dotyczące wydajności, zawsze złamie prawie wszystkie najlepsze praktyki. Najlepsze praktyki dotyczą osiągalności i konserwacji, a nie wyciskania kolejnych 10 milisekund lub oszczędzania 5 bajtów więcej pamięci RAM.
Jonathon

1
@JonathonWisnoski, uzasadnione użycie goto eliminuje także szalone ilości kodu spagetti wtrącającego się w gniazda zmiennych szczura, aby śledzić, dokąd zmierzamy.
vonbrand,

4

Niemal we wszystkich sytuacjach, w których można użyć goto, możesz zrobić to samo, używając innych konstrukcji. I tak kompilator używa Goto.

Osobiście nigdy nie używam tego jawnie, nigdy nie muszę.


4

Jednej rzeczy, której nie widziałem w żadnej z odpowiedzi tutaj, jest to, że rozwiązanie „goto” jest często bardziej wydajne niż jedno z często wymienianych rozwiązań programowania strukturalnego.

Rozważ przypadek wielu zagnieżdżonych pętli, w którym użycie „goto” zamiast kilku if(breakVariable) sekcji jest oczywiście bardziej wydajne. Rozwiązanie „Włącz pętle w funkcję i użyj return” jest często całkowicie nieracjonalne. W prawdopodobnym przypadku, gdy pętle używają zmiennych lokalnych, musisz teraz przekazać je wszystkie przez parametry funkcji, potencjalnie radząc sobie z mnóstwem dodatkowych bólów głowy, które z tego wynikają.

Rozważmy teraz przypadek czyszczenia, z którego korzystałem dość często i jest tak powszechny, że prawdopodobnie był odpowiedzialny za strukturę try {} catch {} niedostępną w wielu językach. Liczba kontroli i dodatkowych zmiennych wymaganych do osiągnięcia tego samego jest znacznie gorsza niż jedna lub dwie instrukcje wykonania skoku, i znowu, dodatkowe rozwiązanie funkcji wcale nie jest rozwiązaniem. Nie możesz mi powiedzieć, że jest to łatwiejsze w zarządzaniu lub bardziej czytelne.

Teraz przestrzeń kodu, użycie stosu i czas wykonania mogą w wielu sytuacjach nie mieć wystarczającego znaczenia dla wielu programistów, ale gdy jesteś w środowisku osadzonym z tylko 2 KB miejsca do pracy, 50 bajtów dodatkowych instrukcji, aby uniknąć jednego jasno określonego „goto” jest po prostu śmieszne, i nie jest to tak rzadka sytuacja, jak uważa wielu programistów wysokiego szczebla.

Stwierdzenie, że „goto jest szkodliwe”, było bardzo pomocne w przejściu na programowanie strukturalne, nawet jeśli zawsze było to nadmierne uogólnienie. W tym momencie wszyscy słyszeliśmy to na tyle, aby uważać na używanie go (tak jak powinniśmy). Gdy jest to oczywiście odpowiednie narzędzie do pracy, nie musimy się go bać.


3

Możesz go użyć do zerwania z głęboko zagnieżdżonej pętli, ale przez większość czasu twój kod może zostać przekształcony w czystszy bez głęboko zagnieżdżonych pętli.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.