Zawsze wiedziałem, że goto
to 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, goto
muszę 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, goto
gdy jest używane w tak oczywistych i niemądrych przypadkach?
Odpowiedzi:
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
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:
gdzie
$list_contains_ip
jest twoja jedyna zmienna stanu lub pośrednio: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:
A ktokolwiek musi debugować ten kod, przeklnie ciebie i twoje dzieci.
Dijkstra przedstawia to następująco:
I dlatego GOTO jest uważane za szkodliwe.
źródło
$list_contains_ip = false;
stwierdzenie wydaje się niewłaściweIstnieje 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):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.GOTO
umoż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.źródło
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ć.goto
pomocą gniazda szczurów flagi i warunki warunkowe (jak w drugim przykładzie OP) jest znacznie mniej czytelny niż juty za pomocą agoto
.Jak powiedzieli inni, problem nie dotyczy
goto
samego siebie; problem polega na tym, jak ludzie używajągoto
i jak może to utrudnić zrozumienie i utrzymanie kodu.Załóżmy następujący fragment kodu:
Dla jakiej wartości drukowane
i
? Kiedy zostanie wydrukowany? Dopóki nie rozliczysz każdego wystąpieniagoto label
funkcji, 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 15goto
gałęzi w obu kierunkach. Na początku był to zły kod, ale obecność tych kodówgoto
znacznie go pogorszyła. Około 2 tygodni zajęło mojemu współpracownikowi ustalenie przepływu kontroli. Co więcej,goto
spowodował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
goto
sobą, 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
:if
lubfor
lubwhile
rachunku);goto
zamiast struktury kontrolnejSą 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ę:Mamy teraz problem - co jeśli jedno z
malloc
połą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
malloc
połączenia i podjąć odpowiednie działanie.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żywaniegoto
nie jest od razu oznaką złej praktyki lub projektu, jest to jedno z niewielu.źródło
goto
nawet 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 koszmaremgoto
jeśli zostały napisane zgodnie z tymi zasadami.GOTO
, w tym słynnych (nie) znanych Fortrana i arytmetycznych gotów, jeśli dobrze pamiętam.return
,break
,continue
Ithrow
/catch
są 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ś!)
źródło
return
break
continue
throw
catch
GOTO
jest 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,
GOTO
chcieli ich całkowicie zakazać.Jak na ironię,
GOTO
nie 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
GOTO
może być bardziej czytelne.” Studenci tego nie zrozumieją. Będą nadużywać,GOTO
aby stworzyć nieczytelny kod.Zamiast tego mówisz „
GOTO
zły.GOTO
Zły. NieGOTO
zdać egzaminu”. Uczniowie zrozumieją , że !źródło
Z wyjątkiem
goto
wszystkich 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:
Niezależnie od tego, co konstrukt kontrola
foo
jest (if
,while
, itd.), Istnieją tylko pewne dozwolony nakazya
,b
ic
.Możesz mieć
a
-b
-c
luba
-c
lub naweta
-b
-b
-b
-c
. Ale nigdy nie możesz miećb
-c
ania
-b
-a
-c
.... chyba że masz
goto
.goto
(w szczególności do tyługoto
) może być na tyle kłopotliwy, że najlepiej po prostu zostawić go w spokoju i zastosować hierarchiczne, blokowane konstrukcje przepływu.goto
mają 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.
źródło
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.
źródło
goto
moż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.goto
przykł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órymgoto
zastosowano implementacjęfor
-else
.goto
jest również świetny do takich rzeczy, jak ponawianie wyjątków, wycofywanie, automaty stanów.Nic złego w
goto
samych 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
goto
do 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
goto
byłoby bardziej eleganckie / prostsze. Na przykład: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).
źródło
break
icontinue
z 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ćgoto
do zagnieżdżonych pętli.Prawdziwe. Nie obchodzi mnie to.
Prawdziwe. Nie obchodzi mnie to.
Prawdziwe. Nie obchodzi mnie to.
Prawdziwe. Jednak byłem zdyscyplinowanym programistą. Z czasem widziałem, co dzieje się z kodem.
Goto
zaczyna 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.Goto
utrudnia rozumowanie na temat kodu. Pracowaliśmy bardzo ciężko, tworzeniewhile
,do while
,for
,for each
switch
,subroutines
,functions
, a wszystko dlatego, że robi to z rzeczyif
igoto
trudno 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ściif
zaczyna wyglądać trochę niepewnie.źródło
Goto
pozwala wyodrębnić problem, czyniąc go innym problemem z kodami.If
pozwala wybrać tę abstrakcję. Istnieją lepsze sposoby na abstrakcję i lepsze sposoby na wybór abstrakcji. Polimorfizm dla jednego.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
źródło