Czy używanie goto jest kiedykolwiek opłacalne?

29

gotojest prawie powszechnie odradzany. Czy korzystanie z tego oświadczenia jest kiedykolwiek opłacalne?

Casebash
źródło
16
xkcd.com/292
TheLQ
1
gotojest tym, co ostatecznie robią procesory, ale to nie działa dobrze z tym, co ludzie muszą zrozumieć i abstrakcyjnie, ponieważ nie ma śladu na docelowym końcu. Porównaj z Intercal COMEFROM.
2
@ ThorbjørnRavnAndersen, co jeszcze ludzie mają na myśli, mówiąc o przejściach stanu w maszynie stanu? Nie widzisz podobieństwa? „Idź do danego stanu ”, to znaczy, a wszystko inne byłoby niczym innym jak niepotrzebną komplikacją tak trywialnej rzeczy. A co rozumiesz przez „brak znaku”? Etykieta jest takim znakiem.
SK-logika
@ SK-logic, zależy od tego, jak daleko będziesz pozwalać na goto. Maszyny stanowe nie wymagają goto do wdrożenia.
@ ThorbjørnRavnAndersen, oczywiście gotonie jest wymagane. Jest to po prostu najbardziej racjonalny sposób implementacji ich w imperatywnych językach, ponieważ jest najbliżej samej semantyki przejścia państwowego. A jego miejsce docelowe może znajdować się dość daleko od źródła, nie utrudni to czytelności. Moim ulubionym przykładem takiego kodu jest implementacja gry przygodowej DE Knuth.
SK-logika

Odpowiedzi:

49

Zostało to kilkakrotnie omówione na temat przepełnienia stosu, a Chris Gillum podsumował możliwe zastosowaniagoto :

Czyste wyjście z funkcji

Często w funkcji możesz przydzielić zasoby i musisz wyjść w wielu miejscach. Programiści mogą uprościć swój kod, umieszczając kod czyszczenia zasobów na końcu funkcji, wszystkie „punkty wyjścia” funkcji przejdą na etykietę czyszczenia. W ten sposób nie musisz pisać kodu czyszczenia w każdym „punkcie wyjścia” funkcji.

Wyjście z zagnieżdżonych pętli

Jeśli jesteś w zagnieżdżonej pętli i potrzebujesz wyrwać się ze wszystkich pętli, goto może uczynić to o wiele czystszym i prostszym niż instrukcje break i if-check.

Ulepszenia wydajności niskiego poziomu

Jest to poprawne tylko w kodzie krytycznym, ale instrukcje goto wykonują się bardzo szybko i mogą przyspieszyć działanie funkcji. Jest to jednak obosieczny miecz, ponieważ kompilator zazwyczaj nie może zoptymalizować kodu zawierającego gotos.

Twierdziłbym, podobnie jak wielu innych, że we wszystkich tych przypadkach użycie gotojest używane jako sposób na wydostanie się z zakątka, w który się zakodowano, i ogólnie jest objawem kodu, który można by zrefaktoryzować.

Społeczność
źródło
28
Pierwszy problem rozwiązany jest bardzo starannie za pomocą finallybloków we współczesnych językach, a drugi rozwiązany jest przez oznaczenie breaks. Jeśli jednak utkniesz z C, gotojest to właściwie jedyny sposób na eleganckie rozwiązanie tych problemów.
Chinmay Kanchi,
2
Bardzo dobre punkty. Podoba mi się, jak Java pozwala na przełamanie wielu poziomów pętli
Casebash
4
@Chinmay - wreszcie bloki dotyczą tylko 1) nowoczesnych języków, które je mają; b) możesz tolerować koszty ogólne (obsługa wyjątków ma koszty ogólne). Oznacza to, że użycie finallyjest poprawne tylko w tych warunkach. Istnieją bardzo rzadkie, ale ważne sytuacje, w których goto jest najlepszym rozwiązaniem.
luis.espinal
21
Dobra ludzie ... co do cholery jest różnica między break 3lub break myLabeli goto myLabel. Och, właśnie to słowo kluczowe. Jeśli używasz break, continuelub jakiś podobny kluczowe jesteś rzeczywiście użyciu goto(jeśli używasz for, while, foreach, do/loopużywasz warunkową Goto.)
Matthew Whited
4
O wydajności na niskim poziomie - zachowanie współczesnego procesora może być trudne do przewidzenia (potok, wykonywanie poza kolejnością), więc prawdopodobnie w 99% przypadków nie jest tego warte, a nawet może być wolniejsze. @MatthewWhited: Scoping. Jeśli wprowadzisz zakres w dowolnym momencie, nie jest jasne (dla człowieka), jak należy nazwać konstruktorów (problem istnieje również w języku C).
Maciej Piechotka,
42

Konstrukcje przepływów sterowania wyższego poziomu zwykle odpowiadają pojęciom w dziedzinie problemów. If / else jest decyzją opartą na pewnych warunkach. Pętla mówi, aby powtarzać pewne działania. Nawet stwierdzenie przerwania mówi: „robiliśmy to wielokrotnie, ale teraz musimy przestać”.

Z drugiej strony instrukcja goto zwykle odpowiada koncepcji w uruchomionym programie, a nie w dziedzinie problemów. Mówi, aby kontynuować wykonywanie w określonym punkcie programu . Ktoś czytający kod musi wywnioskować, co to oznacza w odniesieniu do domeny problemu.

Oczywiście wszystkie konstrukcje wyższego poziomu można zdefiniować w kategoriach gotos i prostych gałęzi warunkowych. To nie znaczy, że są tylko głupcami w przebraniu. Pomyśl o nich jako o ograniczonych gotosach - i to dzięki ograniczeniom są one przydatne. Instrukcja break jest implementowana jako przeskok na koniec otaczającej pętli, ale lepiej jest myśleć o niej jako o działaniu na całej pętli.

Wszystkie pozostałe elementy są jednakowe, kod, którego struktura odzwierciedla strukturę problematycznej domeny, jest zwykle łatwiejszy do odczytania i utrzymania.

Nie ma przypadków, w których instrukcja goto jest absolutnie wymagana (istnieje takie twierdzenie ), ale są przypadki, w których może to być najmniej złe rozwiązanie. Przypadki te różnią się w zależności od języka, w zależności od tego, jakie konstrukcje wyższego poziomu obsługują język.

Na przykład w C uważam, że istnieją trzy podstawowe scenariusze, w których goto jest odpowiednie.

  1. Wyłamywanie się z zagnieżdżonej pętli. Byłoby to niepotrzebne, gdyby język zawierał instrukcję break z etykietą.
  2. Usunięcie części kodu (zwykle treści funkcji) w przypadku błędu lub innego nieoczekiwanego zdarzenia. Byłoby to niepotrzebne, gdyby język miał wyjątki.
  3. Implementowanie jawnej skończonej maszyny stanów. W tym przypadku (i myślę, że tylko w tym przypadku) goto odpowiada bezpośrednio pojęciu w dziedzinie problemów, przechodząc z jednego stanu do określonego innego stanu, w którym obecny stan jest reprezentowany przez który blok kodu jest aktualnie wykonywany .

Z drugiej strony, jawną maszynę skończoną można również zaimplementować za pomocą instrukcji switch wewnątrz pętli. Ma to tę zaletę, że każdy stan zaczyna się w tym samym miejscu w kodzie, co może być przydatne na przykład przy debugowaniu.

Głównym zastosowaniem goto w dość nowoczesnym języku (takim, który obsługuje if / else i pętle) jest symulacja konstrukcji przepływu sterowania, której brakuje w tym języku.

Keith Thompson
źródło
5
To właściwie najlepsza jak dotąd odpowiedź - dość zaskakująca, na pytanie, które ma półtora roku. :-)
Konrad Rudolph
1
+1 do „jak dotąd najlepszej odpowiedzi”. --- „Głównym zastosowaniem goto w dość nowoczesnym języku (takim, który obsługuje if / else i pętle) jest symulacja konstrukcji przepływu sterowania, której brakuje w tym języku.”
Dave Dopson,
W maszynie stanów instrukcji switch każdy zapis do wartości kontrolnej jest naprawdę a goto. SSSM to często dobre struktury do użycia, ponieważ oddzielają stan SM od konstrukcji wykonawczej, ale tak naprawdę nie eliminują „gotos”.
supercat
2
„Wszystkie pozostałe elementy są jednakowe, kod, którego struktura odzwierciedla strukturę problematycznej domeny, jest zwykle łatwiejszy do odczytania i utrzymania”. Znam to całkowicie od dawna, ale brakowało mi słów, aby tak precyzyjnie przekazać ją w sposób, który inni programiści mogą i będą w stanie zrozumieć. Dziękuję Ci za to.
Wildcard,
„Twierdzenie o programie strukturalnym” bawi mnie, ponieważ ma tendencję do wyraźnego wykazania, że ​​używanie instrukcji programowania strukturalnego do emulacji goto jest o wiele mniej czytelne niż samo używanie goto!
11

Z pewnością zależy to od języka programowania. Główny powód gotobudził kontrowersje ze względu na jego złe skutki, które pojawiają się, gdy kompilator pozwala na zbyt swobodne korzystanie z niego. Mogą pojawić się problemy, na przykład, jeśli pozwala to na użycie gotow taki sposób, że można teraz uzyskać dostęp do niezainicjowanej zmiennej lub, co gorsza, przejść do innej metody i zadzierać ze stosem wywołań. Kompilator powinien być odpowiedzialny za niedopuszczenie do bezsensownego przepływu sterowania.

Java podjęła próbę „rozwiązania” tego problemu poprzez gotocałkowite niedozwolenie . Jednak Java pozwala używać returnwewnątrz finallybloku, a tym samym powoduje przypadkowe połknięcie wyjątku. Nadal występuje ten sam problem: kompilator nie wykonuje swojej pracy. Usunięcie gotoz języka nie naprawiło tego.

W języku C #, gotojest równie bezpieczna jak break, continue, try/catch/finallyi return. Nie pozwala używać niezainicjowanych zmiennych, nie pozwala wyskoczyć z bloku w końcu itp. Kompilator będzie narzekał. Jest tak, ponieważ rozwiązuje prawdziwy problem, który jest jak powiedziałem nonsensowny przepływ kontroli. gotonie magicznie anuluje analizy określonego przypisania i innych uzasadnionych kontroli kompilatora.

Timwi
źródło
Chodzi o to, że C # gotonie jest zły? Jeśli tak, dotyczy to każdego rutynowo używanego współczesnego języka z gotokonstrukcją, nie tylko C # ...
lvella
9

Tak. Gdy twoje pętle są zagnieżdżone na kilku poziomach, gotojest to jedyny sposób na eleganckie wyrwanie się z wewnętrznej pętli. Inną opcją jest ustawienie flagi i wyłamanie się z każdej pętli, jeśli flaga spełnia warunek. To jest naprawdę brzydkie i dość podatne na błędy. W takich przypadkach gotojest po prostu lepszy.

Oczywiście, etykieta Javy breakrobi to samo, ale nie pozwala przeskoczyć do dowolnego punktu w kodzie, co rozwiązuje problem starannie, nie dopuszczając rzeczy, które powodują gotozło.

Chinmay Kanchi
źródło
Czy ktoś chciałby wyjaśnić opinię negatywną? To była całkowicie poprawna odpowiedź na pytanie ...
Chinmay Kanchi,
4
goto nigdy nie jest elegancką odpowiedzią. Gdy pętle są zagnieżdżone na kilku poziomach, problem polega na tym, że nie udało się zaprojektować kodu w celu uniknięcia pętli multinestowanych. Wywołania procedur NIE są wrogiem. . odpowiedź.
John R. Strohm,
6
I oczywiście, nigdy nie ma dziwnego przypadku, gdy wymagane są głęboko zagnieżdżone pętle? Bycie dobrym programistą to nie tylko przestrzeganie zasad, ale także wiedza, kiedy je złamać.
Chinmay Kanchi
2
@ JohnR.Strohm Nigdy nie mów nigdy. Jeśli używasz terminów takich jak „nigdy” w programowaniu, wyraźnie nie zajmujesz się przypadkami narożnymi, optymalizacją, ograniczeniami zasobów itp. W głęboko zagnieżdżonych pętlach goto jest naprawdę najczystszym i najbardziej eleganckim sposobem na wyjście z pętli. Nie ma znaczenia, jak bardzo zrestrukturyzowałeś swój kod.
Sujay Phadke,
@ JohnR.Strohm: Druga najczęściej brakująca funkcja przy przełączaniu z VB.NET na C #: Dopętla. Czemu? Ponieważ mogę zmienić jedną pętlę, która wymaga głębokiego przerwania w Dopętli i pisania, Exit Doi nie ma GoTo. Zagnieżdżone pętle zdarzają się cały czas. Przełamywanie stosów zdarza się przez pewien czas.
Jozuego
7

Większość zniechęcenia pochodzi z pewnego rodzaju „religii”, która została stworzona przez Boga Djikstrę, która na początku lat 60. przekonała o swojej masowej mocy do:

  • wskocz gdziekolwiek w dowolny blok kodu
    • funkcja nie jest wykonywana od początku
    • pętle nie są wykonywane od początku
    • pominięto inicjalizację zmiennej
  • odskakuj od dowolnego bloku kodu bez żadnego czyszczenia.

Nie ma to nic więcej wspólnego z gotowypowiedzią współczesnych języków, których istnienie jest po prostu spowodowane wspieraniem tworzenia struktur kodu innych niż te dostarczone przez język.

W szczególności pierwszy główny punkt powyżej jest już dozwolony, a drugi jest czyszczony (jeśli wyjdziesz gotoz bloku, stos jest odpowiednio rozwijany i wywoływane są wszystkie odpowiednie niszczyciele)

Możesz odnieść się do tej odpowiedzi, aby zorientować się, w jaki sposób nawet kod nieużywający goto może być nieczytelny. Problemem nie jest samo goto, ale jego złe użycie.

Mogę napisać cały program bez użycia if, tylko for. Oczywiście nie będzie dobrze czytelny, nieporadny i niepotrzebnie skomplikowany.

Ale problem nie jest for. To ja.

Rzeczy takie jak break, continue, throw, bool needed=true; while(needed) {...}, itd. Są odnotowując ponad maskarady goto , aby uciec z dala od jataganów tych Djikstrarian fanatyków, że -50 lat po wynalezieniu nowoczesnego laguages- nadal chcą ich więźniów. Zapomnieli o czym mówił Djikstra, pamiętają tylko tytuł jego notatki (GOTO uważał ją za szkodliwą, a nawet nie był to jego tytuł: został zmieniony przez redaktora) i obwiniają, uderzają, uderzają i obwiniają każdy contruct posiadający te 4 litera umieszczona w sekwencji.

Jest rok 2011: nadszedł czas, aby zrozumieć, że zauważono, gotoże GOTODjikstra była przekonująca.

Emilio Garavaglia
źródło
1
„Rzeczy takie jak przerwa, kontynuacja, rzut, bool potrzebne = prawda; podczas gdy (potrzebne) {...} itd. Są czymś więcej niż tylko maskarada goto” Nie zgadzam się z tym. Wolałbym „rzeczy takie jak przerwa itp. Są ograniczone ”, czy coś w tym rodzaju. Główny problem z goto polega na tym, że jest on zwykle zbyt silny. W zakresie, w jakim obecne goto jest mniej skuteczne niż Dijkstry, twoja odpowiedź jest dla mnie w porządku.
Muhammad Alkarouri
2
@MuhammadAlkarouri: Całkowicie się zgadzam. Właśnie znalazłeś lepsze sformułowanie, aby dokładnie wyrazić moją koncepcję. Chodzi mi o to, że ograniczenia te mogą czasami nie mieć zastosowania, a rodzaju ograniczeń, których potrzebujesz, nie ma w języku. Wtedy bardziej „mocna” rzecz, mniej wyspecjalizowana, umożliwia obejście tego problemu. Na przykład Java break nnie jest dostępna w C ++, dlatego goto escapenawet jeśli escapenie jest wymagane, aby była po prostu poza pętlą n-ple.
Emilio Garavaglia
4

Dziwne goto tu czy tam, o ile jest lokalne dla funkcji, rzadko znacząco szkodzi czytelności. Często przynosi to korzyści, zwracając uwagę na fakt, że w tym kodzie dzieje się coś niezwykłego, co wymaga użycia nieco nietypowej struktury kontrolnej.

Jeśli (lokalne) goto znacznie szkodzą czytelności, oznacza to zwykle, że funkcja zawierająca goto stała się zbyt złożona.

Ostatnim goto, które umieściłem w kodzie C było zbudowanie pary blokujących się pętli. Nie pasuje do normalnej definicji „akceptowalnego” użycia goto, ale w rezultacie funkcja okazała się znacznie mniejsza i wyraźniejsza. Aby uniknąć goto, wymagałoby to szczególnie niechlujnego naruszenia SUSZENIA.

blucz
źródło
1
Odpowiedź na to pytanie jest najwierniej wyrażona w starym sloganie „Lay's Potato Chips”: „Założę się, że nie możesz zjeść tylko jednego”. W twoim przykładzie masz dwie „zazębiające się” (cokolwiek to znaczy, i założę się, że to nie jest ładne) pętle, a goto dał ci tani out. Co z konserwatorem, który musi zmodyfikować ten kod po tym, jak zostałeś potrącony przez autobus? Czy goto sprawi, że jego życie będzie łatwiejsze czy trudniejsze? Czy te dwie pętle wymagają ponownego przemyślenia?
John R. Strohm,
3

Myślę, że cała ta sprawa dotyczy szczekania niewłaściwego drzewa.

GOTO jako takie nie wydaje mi się problematyczne, ale raczej bardzo często jest to objaw prawdziwego grzechu: kod spaghetti.

Jeśli GOTO powoduje poważne przecięcie linii kontroli przepływu, to źle, kropka. Jeśli nie przecina linii kontroli przepływu, jest nieszkodliwy. W szarej strefie pomiędzy mamy takie rzeczy jak ratowanie pętli, wciąż istnieje kilka języków, które nie dodały konstrukcji, które obejmują wszystkie uzasadnione szare przypadki.

Jedynym przypadkiem, z którego faktycznie korzystam od wielu lat, jest przypadek pętli, w której punkt decyzyjny znajduje się w środku pętli. Pozostaje Ci powielony kod, flaga lub GOTO. Uważam, że rozwiązanie GOTO jest najlepsze z trzech. Nie ma tu przecięcia linii kontroli przepływu, jest nieszkodliwy.

Loren Pechtel
źródło
Przypadek, o którym wspominasz, jest czasami nazywany „pętlą półtora” lub „N plus jedna połówka pętli” i jest to słynny przypadek użycia, gotoktóry przeskakuje do pętli (jeśli język na to pozwala; od drugiego przypadku skacze poza pętlą w środku, jest zwykle trywialny bez goto).
Konrad Rudolph,
(Niestety, większość języków zabrania wskakiwania w pętlę.)
Konrad Rudolph,
@KonradRudolph: Jeśli zaimplementujesz „pętlę” za pomocą GOTO, nie ma innej struktury pętli, w którą można by wskoczyć.
Loren Pechtel,
1
Myślę, że gotokrzyczę TUTAJ PRZEPŁYW STERUJĄCY NIE PASUJE DO NORMALNYCH WZORÓW PROGRAMOWANIA STRUKTURALNEGO . Ponieważ przepływy kontroli, które pasują do strukturalnych schematów programowania, są łatwiejsze do uzasadnienia niż te, które tego nie robią, należy w miarę możliwości próbować wykorzystywać takie wzorce; jeśli kod pasuje do takich wzorców, nie należy krzyczeć, że tak nie jest. Z drugiej strony, w przypadkach, gdy kod naprawdę nie pasuje do takich wzorców, wykrzykiwanie go może być lepsze niż udawanie, że kod pasuje, kiedy tak naprawdę nie jest.
supercat
1

Tak, goto można wykorzystać, aby skorzystać z doświadczenia programisty: http://adamjonrichardson.com/2012/02/06/long-live-the-goto-statement/

Jednak, podobnie jak w przypadku każdego potężnego narzędzia (wskaźniki, wielokrotne dziedziczenie itp.), Trzeba zdyscyplinować się przy jego użyciu. Przykład podany w linku używa PHP, który ogranicza użycie konstrukcji goto do tej samej funkcji / metody i wyłącza możliwość przeskakiwania do nowego bloku kontrolnego (np. Pętla, instrukcja switch itp.)

AdamJonR
źródło
1

Zależy od języka. Na przykład jest nadal szeroko stosowany w programowaniu Cobola. Pracowałem również na urządzeniu Barionet 50, którego językiem programowania oprogramowania jest wczesny dialekt BASIC, który oczywiście wymaga użycia Goto.

użytkownik16764
źródło
0

Powiedziałbym „nie”. Jeśli zauważysz potrzebę korzystania z GOTO, założę się, że istnieje potrzeba przeprojektowania kodu.

Walter
źródło
3
Być może, ale nie
podałeś
Dijkstra przedstawił uzasadnienie szczegółowo dekady temu.
John R. Strohm,
2
Po prostu Dogma. Brak rezonansów. Uwaga: Djikstra NIE JEST WIĘCEJ WAŻNEGO REZONU: odwoływał się do instrukcji BASIC lub FORTRAN GOTO z wczesnych lat 60. Nie mają nic wspólnego z naprawdę anemicznymi (w porównaniu z ich mocą) współczesnymi problemami.
Emilio Garavaglia,
0

gotomoże być przydatny podczas przenoszenia starszego kodu asemblera do C. W pierwszej kolejności konwersja instrukcja po instrukcji do C, wykorzystująca gotojako zamiennik instrukcji asemblera branch, może umożliwić bardzo szybkie przenoszenie.

Graham Borland
źródło
-1

Twierdziłbym, że nie. Powinny zawsze być zastąpione narzędziem bardziej specyficznym dla problemu, do którego rozwiązania goto jest używany (chyba że nie jest dostępny w twoim języku). Na przykład instrukcje break i wyjątki rozwiązują wcześniej rozwiązane problemy ucieczki pętli i obsługi błędów.

Fishtoaster
źródło
Twierdzę, że gdy języki mają obie te funkcje, gotozwykle nie ma ich w tych językach.
Chinmay Kanchi,
Ale czy to dlatego, że ich nie potrzebują, czy dlatego, że ludzie myślą, że ich nie potrzebują?
Michael K
Twoje poglądy są bigotyczne. Istnieje wiele przypadków, gdy gotonajbliższa jest bardzo problematyczna semantyka domeny, wszystko inne byłoby brudnym włamaniem.
SK-logic
@ SK-logic: Nie sądzę, by słowo „bigotował” znaczyło, co myślisz.
Keith Thompson
1
@Keith, „nietolerancyjnie oddany swoim opiniom i uprzedzeniom” - to właśnie tutaj konsekwentnie obserwuję, z tym oszałamiającym losem. „ Zawsze należy wymieniać” to przynajmniej słowa fanatyka. Nic bliskiego właściwej, zdrowej opinii, ale tylko czysta bigoteria. To „zawsze” nie pozostawia miejsca na jakiekolwiek alternatywne opinie, rozumiesz?
SK-logika