Co jest takiego złego w goto, gdy jest używany w tych oczywistych i istotnych przypadkach?

40

Zawsze wiedziałem, że gototo coś złego, zamknięte w piwnicy gdzieś, gdzie nigdy nie można go zobaczyć na dobre, ale dzisiaj natknąłem się na przykładowy kod, który ma sens goto.

Mam adres IP, w którym muszę sprawdzić, czy znajduje się na liście adresów IP, a następnie kontynuować kod, w przeciwnym razie zgłoszenie wyjątku.

<?php

$ip = '192.168.1.5';
$ips = [
    '192.168.1.3',
    '192.168.1.4',
    '192.168.1.5',
];

foreach ($ips as $i) {
    if ($ip === $i) {
        goto allowed;
    }
}

throw new Exception('Not allowed');

allowed:

...

Jeśli nie używam, gotomuszę użyć jakiejś zmiennej, takiej jak

$allowed = false;

foreach ($ips as $i) {
    if ($ip === $i) {
        $allowed = true;
        break;
    }
}

if (!$allowed) {
    throw new Exception('Not allowed');
}

Moje pytanie brzmi, co jest tak złego, gotogdy jest używane w tak oczywistych i niemądrych przypadkach?

php_nub_qq
źródło
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
wałek klonowy

Odpowiedzi:

120

Samo GOTO nie jest bezpośrednim problemem, to niejawne maszyny stanów, które ludzie zwykle wdrażają za jego pomocą. W twoim przypadku potrzebujesz kodu, który sprawdza, czy adres IP znajduje się na liście dozwolonych adresów

if (!contains($ips, $ip)) throw new Exception('Not allowed');

więc twój kod chce sprawdzić warunek. Algorytm do implementacji tej kontroli nie powinien mieć tutaj znaczenia, w przestrzeni mentalnej głównego programu kontrola jest atomowa. Tak powinno być.

Ale jeśli umieścisz kod sprawdzający w głównym programie, stracisz to. Wprowadzasz stan mutable, albo jawnie:

$list_contains_ip = undef;        # STATE: we don't know yet

foreach ($ips as $i) {
  if ($ip === $i) {
      $list_contains_ip = true;   # STATE: positive
      break;
  }
                                  # STATE: we still don't know yet, huh?                                                          
                                  # Well, then...
  $list_contains_ip = false;      # STATE: negative
}

if (!$list_contains_ip) {
  throw new Exception('Not allowed');
}

gdzie $list_contains_ipjest twoja jedyna zmienna stanu lub pośrednio:

                             # STATE: unknown
foreach ($ips as $i) {       # What are we checking here anyway?
  if ($ip === $i) {
    goto allowed;            # STATE: positive
  }
                             # STATE: unknown
}
                             # guess this means STATE: negative
throw new Exception('Not allowed');

allowed:                     # Guess we jumped over the trap door

Jak widać, w konstrukcji GOTO znajduje się niezadeklarowana zmienna stanu. Nie jest to problem sam w sobie, ale te zmienne stanu są jak kamyki: noszenie jednego nie jest trudne, noszenie ich pełnej torby sprawi, że się pocisz. Twój kod nie pozostanie taki sam: w przyszłym miesiącu zostaniesz poproszony o rozróżnienie adresów prywatnych i publicznych. W następnym miesiącu kod będzie obsługiwał zakresy adresów IP. W przyszłym roku ktoś poprosi Cię o wsparcie adresów IPv6. W krótkim czasie Twój kod będzie wyglądał następująco:

if ($ip =~ /:/) goto IP_V6;
if ($ip =~ /\//) goto IP_RANGE;
if ($ip =~ /^10\./) goto IP_IS_PRIVATE;

foreach ($ips as $i) { ... }

IP_IS_PRIVATE:
   foreach ($ip_priv as $i) { ... }

IP_V6:
   foreach ($ipv6 as $i) { ... }

IP_RANGE:
   # i don't even want to know how you'd implement that

ALLOWED:
   # Wait, is this code even correct?
   # There seems to be a bug in here.

A ktokolwiek musi debugować ten kod, przeklnie ciebie i twoje dzieci.

Dijkstra przedstawia to następująco:

Nieokiełznane użycie instrukcji go to powoduje, że niezwykle trudno jest znaleźć sensowny zestaw współrzędnych, w których można opisać postęp procesu.

I dlatego GOTO jest uważane za szkodliwe.

Wallenborn
źródło
22
To $list_contains_ip = false;stwierdzenie wydaje się niewłaściwe
Bergi,
1
Worek pełen kamyków jest łatwy, mam wygodną torbę do ich przechowywania. : p
whatsisname
13
@Bergi: Masz rację. To pokazuje, jak łatwo jest popsuć te rzeczy.
wallenborn
6
@whatsisname Ta torba nazywa się funkcją / klasą / pakietem do ich przechowywania. używanie gotos jest jak żonglowanie nimi i wyrzucanie torby.
Falco
5
Uwielbiam cytat z Dijkstry, nie słyszałem go w ten sposób, ale to naprawdę dobry punkt. Zamiast przepływu, który jest prawie od góry do dołu, nagle masz coś, co może losowo przeskakiwać całą stronę. Mówiąc zwięźle, jest to imponujące.
Bill K
41

Istnieje kilka uzasadnionych przypadków użycia GOTO. Na przykład do obsługi błędów i czyszczenia w C lub do implementacji niektórych form automatów stanowych. Ale to nie jest jeden z tych przypadków. Drugi przykład jest bardziej czytelny IMHO, ale jeszcze bardziej czytelne byłoby wyodrębnienie pętli do oddzielnej funkcji, a następnie powrót po znalezieniu dopasowania. Jeszcze lepiej byłoby (w pseudokodzie nie znam dokładnej składni):

if (!in_array($ip, $ips)) throw new Exception('Not allowed');

Więc co jest takiego złego w GOTO? Programowanie strukturalne wykorzystuje funkcje i struktury kontrolne do organizowania kodu, aby struktura składniowa odzwierciedlała strukturę logiczną. Jeśli coś jest wykonywane tylko warunkowo, pojawi się w bloku instrukcji warunkowej. Jeśli coś zostanie wykonane w pętli, pojawi się w bloku pętli. GOTOumożliwia obejście struktury składniowej przez dowolne przeskakiwanie, przez co kod jest znacznie trudniejszy do naśladowania.

Oczywiście, jeśli nie masz innego wyboru, jakiego używasz GOTO, ale jeśli ten sam efekt można osiągnąć za pomocą funkcji i struktur kontrolnych, lepiej jest.

JacquesB
źródło
5
@jameslarge Chcesz it's easier to write good optimizing compilers for "structured" languages.opracować? Jako przeciwny przykład ludzie z Rust wprowadzili MIR , pośrednią reprezentację w kompilatorze, która konkretnie zastępuje pętle, kontynuuje, zrywa i tym podobne z gotosami, ponieważ łatwiej jest sprawdzić i zoptymalizować.
8bittree,
6
„jeśli nie masz innego wyboru, korzystasz z GOTO” Byłoby to niezwykle rzadkie. Szczerze mówiąc, nie mogę wymyślić przypadku, w którym nie masz innego wyboru, poza całkowicie absurdalnymi ograniczeniami, które uniemożliwiają refaktoryzację istniejącego kodu lub podobnych bzdur.
jpmc26,
8
Ponadto, IMO, emulując za gotopomocą gniazda szczurów flagi i warunki warunkowe (jak w drugim przykładzie OP) jest znacznie mniej czytelny niż juty za pomocą a goto.
3
@ 8bittree: Programowanie strukturalne jest przeznaczone dla języka źródłowego, a nie docelowego języka kompilatora. Językiem docelowym dla natywnych kompilatorów są instrukcje procesora, które zdecydowanie nie są „ustrukturyzowane” w sensie języka programowania.
Robert Harvey,
4
@ 8bittree Odpowiedź na pytanie dotyczące MIR znajduje się w podanym linku: „Ale dobrze jest mieć taki konstrukt w MIR, ponieważ wiemy, że będzie on używany tylko w określony sposób, na przykład do wyrażenia pętli lub przerwy . ” Innymi słowy, ponieważ goto nie jest dozwolone w Rust, wiedzą, że ma on strukturę i jest też dostępna wersja pośrednia z goto. Ostatecznie kod musi przejść do goto jak polecenia maszynowe. To, co otrzymuję z twojego linku, polega na tym, że po prostu dodają krok po kroku do konwersji z języka wysokiego poziomu na język niskiego poziomu.
JimmyJames,
17

Jak powiedzieli inni, problem nie dotyczy gotosamego siebie; problem polega na tym, jak ludzie używają gotoi jak może to utrudnić zrozumienie i utrzymanie kodu.

Załóżmy następujący fragment kodu:

       i = 4;
label: printf( "%d\n", i );

Dla jakiej wartości drukowane i? Kiedy zostanie wydrukowany? Dopóki nie rozliczysz każdego wystąpienia goto labelfunkcji, nie możesz tego wiedzieć. Prosta obecność tej etykiety niszczy możliwość debugowania kodu przez prostą kontrolę. W przypadku małych funkcji z jedną lub dwiema gałęziami nie stanowi większego problemu. Dla niemałych funkcji ...

We wczesnych latach 90. dostaliśmy stos kodu C, który napędzał trójwymiarowy wyświetlacz graficzny i kazano mu działać szybciej. Było tylko około 5000 linii kodu, ale wszystko było w porządku main, a autor wykorzystał około 15 gotogałęzi w obu kierunkach. Na początku był to zły kod, ale obecność tych kodów gotoznacznie go pogorszyła. Około 2 tygodni zajęło mojemu współpracownikowi ustalenie przepływu kontroli. Co więcej, gotospowodowało to, że kod był tak ściśle powiązany z samym sobą, że nie mogliśmy dokonywać żadnych zmian bez zepsucia czegoś.

Próbowaliśmy kompilować z optymalizacją na poziomie 1, a kompilator zjadł całą dostępną pamięć RAM, następnie całą dostępną wymianę, a następnie spanikował system (co prawdopodobnie nie miało nic wspólnego z samym gotosobą, ale lubię wyrzucać tę anegdotę).

Na koniec daliśmy klientowi dwie opcje - przepiszmy wszystko od zera lub kupmy szybszy sprzęt.

Kupili szybszy sprzęt.

Zasady Bode dotyczące używania goto:

  1. Tylko oddział do przodu;
  2. Nie obejście struktur kontrolnych (czyli, nie rozgałęzia się w ciało iflub forlub whilerachunku);
  3. Nie stosować gotozamiast struktury kontrolnej

Są przypadki, w których a goto jest właściwą odpowiedzią, ale są rzadkie (wyrwanie się z głęboko zagnieżdżonej pętli jest jedynym miejscem, w którym mógłbym jej użyć).

EDYTOWAĆ

Rozwijając ostatnie zdanie, oto jeden z niewielu prawidłowych przypadków użycia dla goto. Załóżmy, że mamy następującą funkcję:

T ***myalloc( size_t N, size_t M, size_t P )
{
  size_t i, j, k;

  T ***arr = malloc( sizeof *arr * N );
  for ( i = 0; i < N; i ++ )
  {
    arr[i] = malloc( sizeof *arr[i] * M );
    for ( j = 0; j < M; j++ )
    {
      arr[i][j] = malloc( sizeof *arr[i][j] * P );
      for ( k = 0; k < P; k++ )
        arr[i][j][k] = initial_value();
    }
  }
  return arr;
}

Mamy teraz problem - co jeśli jedno z mallocpołączeń zakończy się niepowodzeniem w połowie? Wydaje się to mało prawdopodobne, nie chcemy zwracać częściowo przydzielonej tablicy ani nie chcemy po prostu wyskoczyć z funkcji z błędem; chcemy po sobie posprzątać i zwolnić częściowo przydzieloną pamięć. W języku, który rzuca wyjątek na zły przydział, jest to dość proste - wystarczy napisać moduł obsługi wyjątków, aby zwolnić to, co już zostało przydzielone.

W C nie masz strukturalnej obsługi wyjątków; musisz sprawdzić wartość zwrotu każdego mallocpołączenia i podjąć odpowiednie działanie.

T ***myalloc( size_t N, size_t M, size_t P )
{
  size_t i, j, k;

  T ***arr = malloc( sizeof *arr * N );
  if ( arr )
  {
    for ( i = 0; i < N; i ++ )
    {
      if ( !(arr[i] = malloc( sizeof *arr[i] * M )) )
        goto cleanup_1;

      for ( j = 0; j < M; j++ )
      {
        if ( !(arr[i][j] = malloc( sizeof *arr[i][j] * P )) )
          goto cleanup_2;

        for ( k = 0; k < P; k++ )
          arr[i][j][k] = initial_value();
      }
    }
  }
  goto done;

  cleanup_2:
    // We failed while allocating arr[i][j]; clean up the previously allocated arr[i][j]
    while ( j-- )
      free( arr[i][j] );
    free( arr[i] );
    // fall through

  cleanup_1:
    // We failed while allocating arr[i]; free up all previously allocated arr[i][j]
    while ( i-- )
    {
      for ( j = 0; j < M; j++ )
        free( arr[i][j] );
      free( arr[i] );
    }

    free( arr );
    arr = NULL;

  done:
    return arr;
}

Czy możemy to zrobić bez użycia goto? Oczywiście, że możemy - wymaga to tylko trochę dodatkowej księgowości (i w praktyce to jest moja ścieżka). Ale jeśli szukasz miejsc, w których używanie gotonie jest od razu oznaką złej praktyki lub projektu, jest to jedno z niewielu.

John Bode
źródło
Lubię te zasady. Nadal nie używałbym gotonawet zgodnie z tymi zasadami - nie używałbym go wcale, chyba że jest to jedyna forma rozgałęziania dostępna w języku - ale widzę, że debugowanie nie byłoby zbyt wielkim koszmarem gotojeśli zostały napisane zgodnie z tymi zasadami.
Wildcard,
jeśli chcesz wyrwać się z głęboko zagnieżdżonej pętli, musisz po prostu uczynić tę pętlę inną funkcją i powrócić
bunyaCloven
6
Jak ktoś kiedyś to podsumował: „To nie jest problem, ale problem pochodzący”. Jako specjalista od optymalizacji oprogramowania zgadzam się, że nieustrukturyzowany przepływ sterowania może generować pętle dla kompilatorów (i ludzi!). Kiedyś spędziłem godziny na rozszyfrowaniu prostej maszyny stanów (cztery z pięciu stanów) w funkcji BLAS, która używała różnych form GOTO, w tym słynnych (nie) znanych Fortrana i arytmetycznych gotów, jeśli dobrze pamiętam.
njuffa
@njuffa „Jako specjalista od optymalizacji oprogramowania zgadzam się, że nieustrukturyzowany przepływ sterowania może wyrzucić kompilatory (i ludzi!) do pętli.” - jakieś szczegóły? Myślałem, że praktycznie każdy kompilator optymalizujący używa SSA jako IR, co oznacza, że ​​używa kodu podobnego do goto.
Maciej Piechotka,
@MaciejPiechotka Nie jestem inżynierem kompilatora. Tak, wszystkie współczesne kompilatory wydają się używać SSA w swojej pośredniej reprezentacji. Kompilatory wolą konstrukcje kontrolne single-entry-single-exit, nie jesteś pewien, w jaki sposób korzysta z SSA? Obserwacja generowanego kodu maszynowego i wykorzystania zasobów kompilatora oraz dyskusje z inżynierami kompilatora niestrukturalny przepływ sterowania zakłóca optymalizację transformacji kodu, prawdopodobnie z powodu problemu „pochodzić”. Wykorzystanie zasobów (czas, pamięć) może mieć negatywny wpływ na wydajność kodu, jeśli kompilator zdecyduje się pominąć optymalizację jako zbyt kosztowną.
njuffa
12

return, break, continueI throw/ catchsą zasadniczo GOTOS - wszyscy kontrola transferu do innego kawałka kodu i wszystko może być realizowane z GOTOS - w rzeczywistości Zrobiłem tak raz w projekcie szkoła, PASCAL instruktor mówił jak wiele lepiej niż było Pascal podstawowe ze względu na strukturę ... więc musiałem być przeciwny ...

Najważniejszą rzeczą w inżynierii oprogramowania (użyję tego terminu nad kodowaniem w odniesieniu do sytuacji, w której ktoś płaci za utworzenie bazy kodu wraz z innymi inżynierami, która wymaga ciągłego doskonalenia i konserwacji), sprawia, że ​​kod jest czytelny - -zabycie tego, żeby coś zrobić, jest prawie drugorzędne. Twój kod zostanie napisany tylko raz, ale w większości przypadków ludzie spędzą dni i tygodnie na ponownym odwiedzaniu / uczeniu się, ulepszaniu i naprawianiu go - i za każdym razem, gdy (lub ty) będziesz musiał zacząć od zera i spróbować zapamiętać / wymyślić Twój kod.

Większość funkcji dodawanych do języków na przestrzeni lat sprawia, że ​​oprogramowanie jest łatwiejsze w utrzymaniu, a nie łatwiejsze do napisania (chociaż niektóre języki idą w tym kierunku - często powodują długoterminowe problemy ...).

W porównaniu z podobnymi stwierdzeniami kontroli przepływu, GOTO mogą być prawie tak samo łatwe do naśladowania w najlepszym wydaniu (Pojedyncze goto używane w przypadku, jak sugerujesz), i koszmar, gdy są nadużywane - i są bardzo łatwo nadużywane ...

Więc po kilku latach zmagania się z koszmarami spaghetti, po prostu powiedzieliśmy „Nie”, jako społeczność nie zamierzamy tego zaakceptować - zbyt wielu ludzi popsuje to, jeśli da się im trochę swobody - to naprawdę jedyny problem z nimi. Możesz ich użyć ... ale nawet jeśli jest to idealny przypadek, następny facet uzna, że ​​jesteś okropnym programistą, ponieważ nie rozumiesz historii społeczności.

Opracowano wiele innych struktur, aby uczynić kod bardziej zrozumiałym: funkcje, obiekty, zakres, enkapsulacja, komentarze (!) ... a także ważniejsze wzorce / procesy, takie jak „DRY” (zapobieganie duplikacji) i „YAGNI” (Ograniczenie nadmiernej uogólnienia / komplikacji kodu) - wszystko naprawdę importuje tylko dla KOLEJNEGO faceta, aby odczytał twój kod (Kim prawdopodobnie będziesz ty - po tym, jak zapomniałeś większość tego, co zrobiłeś!)

Bill K.
źródło
3
Głosuję za tym tylko dla zdania: „Najważniejszą rzeczą ... jest uczynienie kodu czytelnym; sprawienie, by coś zrobił, jest prawie wtórne”. Powiedziałbym to nieco inaczej; to nie tyle co kodu czytelnej jak czyni kodu proste . Ale tak czy inaczej, to jest tak prawdziwe, że zmuszenie go do zrobienia czegoś jest drugorzędne.
Wildcard,
„ , I / są zasadniczo GOTOS” Nie zgadzam się, dawny szacunek założeniami programowania strukturalnego (choć można spierać o wyjątkami), przejdź do zdecydowanie nie. W świetle tego pytania ta uwaga niewiele zyskuje. returnbreakcontinuethrowcatch
Eric
@eric Możesz modelować dowolne z oświadczeń za pomocą GOTO. Możesz upewnić się, że wszystkie GOTO zgadzają się z założeniami programowania strukturalnego - mają one po prostu inną składnię i muszą, w celu dokładnego skopiowania funkcjonalności, zawierać inne operacje, takie jak „jeśli”. Zaletą wersji ustrukturyzowanych jest to, że ograniczają się one tylko do obsługi określonych celów - WIESZ, gdy widzisz kontynuację w przybliżeniu w miejscu, w którym zostanie zlokalizowany cel. Powodem, dla którego wspomniałem, było wskazanie, że problemem jest nadużycie, a nie to, że nie można go prawidłowo użyć.
Bill K
7

GOTOjest narzędziem. Można go użyć dla dobra lub zła.

W dawnych, złych czasach, dzięki FORTRAN i BASIC było to prawie jedyne narzędzie.

Patrząc na kod z tamtych dni, kiedy widzisz jakiś GOTO, musisz dowiedzieć się, dlaczego on tam jest. Może być częścią standardowego idiomu, który można szybko zrozumieć ... lub może być częścią jakiejś koszmarnej struktury kontroli, która nigdy nie powinna była istnieć. Nie wiesz, dopóki nie spojrzysz, i łatwo się pomylić.

Ludzie chcieli czegoś lepszego i wynaleziono bardziej zaawansowane struktury kontroli. Obejmowały one większość przypadków użycia, a ludzie, którzy zostali spaleni przez złych, GOTOchcieli ich całkowicie zakazać.

Jak na ironię, GOTOnie jest tak źle, gdy jest rzadkie. Kiedy go zobaczysz, wiesz, że dzieje się coś wyjątkowego i łatwo jest znaleźć odpowiednią etykietę, ponieważ jest to jedyna etykieta w pobliżu.

Szybkie przejście do dzisiaj. Jesteś wykładowcą uczącym programowania. Ty mógł powiedzieć: „W większości przypadków należy użyć zaawansowanych nowych konstrukcji, ale w niektórych przypadkach prosta GOTOmoże być bardziej czytelne.” Studenci tego nie zrozumieją. Będą nadużywać, GOTOaby stworzyć nieczytelny kod.

Zamiast tego mówisz „ GOTOzły. GOTOZły. Nie GOTOzdać egzaminu”. Uczniowie zrozumieją , że !

Stig Hemmer
źródło
4
Dotyczy to w rzeczywistości wszystkich przestarzałych rzeczy. Globals. Jednoliterowe nazwy zmiennych. Kod modyfikujący. Eval. Podejrzewam, że nawet niefiltrowane dane wejściowe użytkownika. Kiedy już uwarunkowaliśmy się, aby uniknąć takich rzeczy, jak zaraza, jesteśmy w odpowiedniej pozycji, aby ocenić, czy pewne ich użycie, dobrze oznakowane, może być właściwym rozwiązaniem konkretnego problemu. Wcześniej najlepiej traktować je jako po prostu zakazane.
Dewi Morgan
4

Z wyjątkiem gotowszystkich konstrukcji przepływów w PHP (i większości języków) ma zasięg hierarchiczny.

Wyobraź sobie kod badany przez zmrużone oczy:

a;
foo {
    b;
}
c;

Niezależnie od tego, co konstrukt kontrola foojest ( if, while, itd.), Istnieją tylko pewne dozwolony nakazy a, bi c.

Możesz mieć a- b- club a- club nawet a- b- b- b- c. Ale nigdy nie możesz mieć b- cani a- b- a- c.

... chyba że masz goto.

$a = 1;
first:
echo 'a';
if ($a === 1) {
    echo 'b';
    $a = 2;
    goto first;
}
echo 'c'; 

goto(w szczególności do tyłu goto) może być na tyle kłopotliwy, że najlepiej po prostu zostawić go w spokoju i zastosować hierarchiczne, blokowane konstrukcje przepływu.

gotomają swoje miejsce, ale głównie jako mikrooptymalizacje w językach niskiego poziomu. IMO, w PHP nie ma na to dobrego miejsca.


Do Twojej wiadomości, przykładowy kod można napisać nawet lepiej niż jedną z twoich sugestii.

if(!in_array($ip, $ips, true)) {
    throw new Exception('Not allowed');
}
Paul Draper
źródło
2

W językach niskiego poziomu GOTO jest nieuniknione. Ale na wysokim poziomie należy tego unikać (w przypadku, gdy język go obsługuje), ponieważ utrudnia to czytanie programów .

Wszystko sprowadza się do utrudnienia odczytu kodu. Obsługiwane są języki wysokiego poziomu, aby ułatwić czytanie kodu niż języki niskiego poziomu, takie jak, powiedzmy, asembler lub C.

GOTO nie powoduje globalnego ocieplenia ani nie powoduje ubóstwa w trzecim świecie. Utrudnia to czytanie kodu.

Większość współczesnych języków ma struktury kontrolne, które sprawiają, że GOTO nie jest konieczne. Niektórzy jak Java nawet tego nie mają.

W rzeczywistości termin kod spaguetti pochodzi od skomplikowanych, trudnych do śledzenia przyczyn kodu przez nieustrukturyzowane struktury rozgałęziające.

Tulains Córdova
źródło
6
gotomoże sprawić, że kod łatwiej czytać. Kiedy tworzysz dodatkowe zmienne i zagnieżdżone gałęzie w porównaniu do pojedynczego skoku, kod jest znacznie trudniejszy do odczytania i zrozumienia.
Matthew Whited,
@MatthewWhited Być może jeśli masz jedno goto w metodzie dziesięcioliniowej. Ale większość metod nie ma dziesięciu linii i przez większość czasu, gdy ludzie używają GOTO, nie używają go „tylko raz”.
Tulains Córdova,
2
@MatthewWhited W rzeczywistości termin spaguetti kod pochodzi z zawiłe, trudne do zrozumienia kod spowodowanego nieuporządkowanych struktur rozgałęzienia. A może kod spaguetti jest teraz łatwiejszy do czerwonego? Może. Winyl powraca, dlaczego GOTO nie miałby.
Tulains Córdova,
3
@Tulains Muszę wierzyć, że takie złe rzeczy istnieją, ponieważ ludzie wciąż narzekają, ale przez większość czasu nie widzę gotoprzykładów zawiłego kodu spaghetti, ale raczej dość proste rzeczy, często emulujące wzorce kontroli w językach, które nie ma do tego składnię: dwa najczęstsze przykłady to wyrwanie się z wielu pętli, a przykład w PO, w którym gotozastosowano implementację for- else.
1
Nieustrukturyzowany kod spaghetti pochodzi z języków, w których funkcje / metody nie istnieją. gotojest również świetny do takich rzeczy, jak ponawianie wyjątków, wycofywanie, automaty stanów.
Matthew Whited,
1

Nic złego w gotosamych stwierdzeniach. Zły są niektóre osoby, które niewłaściwie używają tego oświadczenia.

Oprócz tego, co powiedział JacquesB (obsługa błędów w C), używasz gotodo wyjścia z nie zagnieżdżonej pętli, co możesz zrobić za pomocą break. W takim przypadku lepiej użyć break.

Ale jeśli masz scenariusz z zagnieżdżoną pętlą, wówczas użycie gotobyłoby bardziej eleganckie / prostsze. Na przykład:

$allowed = false;

foreach ($ips as $i) {
    foreach ($ips2 as $i2) {
        if ($ip === $i && $ip === $i2) {
            $allowed = true;
            goto out;
        }
    }
}

out:

if (!$allowed) {
    throw new Exception('Not allowed');
}

Powyższy przykład nie ma sensu w twoim konkretnym problemie, ponieważ nie potrzebujesz zagnieżdżonej pętli. Ale mam nadzieję, że widzisz tylko część zagnieżdżonej pętli.

Bonous point: jeśli twoja lista adresów IP jest mała, twoja metoda jest w porządku. Ale jeśli lista rośnie, wiedz, że twoje podejście ma asymptotyczną najgorszą złożoność O (n) w czasie wykonywania . W miarę powiększania się listy możesz chcieć użyć innej metody, która osiąga O (log n) (np. Struktura drzewa) lub O (1) (tablica skrótów bez kolizji).

jaskiniowiec
źródło
6
Nie jestem pewien co do PHP, ale wiele języków będzie obsługiwać używanie breaki continuez liczbą (aby przerwać / kontynuować wiele warstw pętli) lub etykietą (aby przerwać / kontynuować pętlę z tą etykietą), z których każda powinna być lepiej niż używać gotodo zagnieżdżonych pętli.
8bittree,
@ 8bittree dobry punkt. Nie wiedziałem, że przerwa PHP jest taka wymyślna. Wiem tylko, że złamanie C nie jest takie fantazyjne. Przerwa Perla (nazywają ją ostatnią ) również była podobna do C (tj. Nie jest fantazyjna jak PHP), ale po pewnym czasie stała się fantazyjna. Wydaje mi się, że moja odpowiedź jest coraz mniej przydatna, ponieważ coraz więcej języków wprowadza wymyślne wersje break :)
jaskiniowiec
3
przerwa z wartością głębokości nie rozwiązałaby problemu bez dodatkowej zmiennej. Zmienna sama w sobie, która tworzy dodatkowy poziom złożoności.
Matthew Whited,
Oczywiście, ponieważ zagnieżdżone pętle nie oznaczają, że potrzebujesz goto, istnieją inne sposoby eleganckiego radzenia sobie z tą sytuacją.
NPSF3000,
@ NPSF3000 Czy możesz podać przykład zagnieżdżonej pętli, którą możesz zakończyć bez użycia instrukcji goto , a także używając języka, który nie ma fantazyjnej wersji break, która określa głębokość?
jaskiniowiec
0

Dzięki goto mogę pisać szybszy kod!

Prawdziwe. Nie obchodzi mnie to.

Goto istnieje w asemblerze! Nazywają to po prostu jmp.

Prawdziwe. Nie obchodzi mnie to.

Goto rozwiązuje problemy prościej.

Prawdziwe. Nie obchodzi mnie to.

W rękach zdyscyplinowanego kodu programisty używającego goto może być łatwiejszy do odczytania.

Prawdziwe. Jednak byłem zdyscyplinowanym programistą. Z czasem widziałem, co dzieje się z kodem. Gotozaczyna się dobrze. Potem pojawia się potrzeba ponownego użycia kodu. Dość szybko znalazłem się w punkcie przerwania, nie mając żadnego cholernego pojęcia, co się dzieje, nawet po sprawdzeniu stanu programu. Gotoutrudnia rozumowanie na temat kodu. Pracowaliśmy bardzo ciężko, tworzenie while, do while, for, for each switch, subroutines, functions, a wszystko dlatego, że robi to z rzeczy ifi gototrudno na mózg.

Więc nie. Nie chcemy patrzeć goto. Pewnie, że jest żywy i ma się dobrze w systemie binarnym, ale nie musimy tego widzieć w źródle. W rzeczywistości ifzaczyna wyglądać trochę niepewnie.

candied_orange
źródło
1
„Goto rozwiązuje problemy prościej”. / „Prawda. Nie obchodzi mnie to”. - Nie chciałbym widzieć twojego kodu, w którym celowo unikasz prostych rozwiązań na rzecz tego, co jest najbardziej rozszerzalne. (Słyszałeś o YAGNI?)
253751
1
@Wildcard if i goto są bardzo prostymi sposobami rozwiązywania problemów najlepiej rozwiązanymi innymi sposobami. Są nisko wiszącymi owocami. Nie chodzi o konkretny język. Gotopozwala wyodrębnić problem, czyniąc go innym problemem z kodami. Ifpozwala wybrać tę abstrakcję. Istnieją lepsze sposoby na abstrakcję i lepsze sposoby na wybór abstrakcji. Polimorfizm dla jednego.
candied_orange
2
Chociaż zgadzam się z tobą, jest to źle sformułowana odpowiedź. „Prawda, nie przejmuj się” nie jest przekonującym argumentem na nic. Myślę, że Twoim głównym przesłaniem jest to: chociaż goto jest atrakcyjne dla osób, które wolą korzystać z wydajnych i wydajnych narzędzi, prowadzi to do zaskakująco dużej ilości zmarnowanego czasu później, nawet jeśli zachowa się ostrożność.
Artelius
1
@artelius Motyw „prawda, nie przejmuj się” nie powinien być przekonujący. Ma to zilustrować wiele punktów, które przyznam, że mam goto i wciąż nie zgadzam się z tym. Zatem sprzeczanie się ze mną tymi punktami jest bezcelowe. Ponieważ nie o to mi chodzi. Złapać temat?
candied_orange
1
Po prostu wierzę, że dobra odpowiedź nie tylko przedstawia twoją opinię, ale także wyjaśnia ją i pozwala czytelnikowi na dokonanie własnego osądu. W tej chwili nie jest jasne, dlaczego dobre punkty goto nie wystarczą, aby przeważyć jego problemy. Większość z nas spędza godziny na ciągnięciu włosów, próbując znaleźć błędy, które nie wymagają goto. Nie jest to jedyny konstrukt, który trudno uzasadnić. Ktoś zadający to pytanie jest prawdopodobnie znacznie mniej doświadczony i potrzebuje rzeczy z perspektywy.
Artelius
-1

Języki asemblera zazwyczaj mają tylko skoki warunkowe / bezwarunkowe (odpowiednik GOTO. Starsze implementacje FORTRAN i BASIC nie miały instrukcji bloków sterujących poza zliczoną iteracją (pętla DO), pozostawiając wszystkie inne przepływy sterujące do IF i GOTO. języki te zostały zakończone oświadczeniem oznaczonym numerycznie, w wyniku czego kod napisany dla tych języków mógł być i często był trudny do naśladowania i podatny na błędy.

Aby to podkreślić, istnieje wymyślnie wymyślona instrukcja „ COME FROM ”.

Praktycznie nie ma potrzeby używania GOTO w językach takich jak C, C ++, C #, PASCAL, Java itp .; można zastosować alternatywne konstrukcje, które prawie na pewno będą tak samo wydajne i o wiele łatwiejsze w utrzymaniu. To prawda, że ​​jedno GOTO w pliku źródłowym nie będzie stanowić problemu. Problem polega na tym, że nie trzeba wielu, aby jednostka kodu była trudna do przestrzegania i podatna na błędy w utrzymaniu. Dlatego przyjętą mądrością jest unikanie GOTO, gdy tylko jest to możliwe.

Ten artykuł w Wikipedii na temat goto może być pomocny

Zenilogix
źródło