Dlaczego Go zawiera instrukcję „goto”

110

Zaskoczyło mnie, że Go zawiera stwierdzenie „goto” . Zawsze uczono mnie, że instrukcje „goto” należą do przeszłości i są złe, ponieważ blokują rzeczywisty przepływ programu, a funkcje lub metody są zawsze lepszym sposobem kontrolowania przepływu.

Muszę czegoś przegapić. Dlaczego Google to umieściło?

zaszkodzić
źródło
5
Są chwile, kiedy naprawdę potrzebujesz instrukcji goto. Goto są złe tylko wtedy, gdy są używane bezkrytycznie. Na przykład, jeśli napisanie parsera maszyny skończonej bez instrukcji goto jest bardzo trudne, jeśli nie niemożliwe.
xbonez
5
Nie jest to specyficzne dla Go, ale dla dobrej dyskusji na temat tego, dlaczego języki zachowują to oświadczenie i aby zobaczyć argumenty przeciwko jego użyciu, sprawdź ten post . Pytanie zawiera kilka dobrych odniesień. Edycja: oto kolejny .
Cᴏʀʏ
3
Aby uchronić OP przed grepowaniem przez dostarczone dyskusje SO, oto dyskusja na temat LKML, która prawie podsumowuje, dlaczego gotojest przydatna w niektórych przypadkach. Przeczytaj po przestudiowaniu odpowiedzi @ Kissaki.
kostix
1
Powiązane: programmers.stackexchange.com/q/566/33478 (i zobacz moją odpowiedź ).
Keith Thompson,
Jest to przydatne do implementacji wzorca kontynuacji, w którym oszczędzasz stos, a następnie wracasz do miejsca, w którym byłeś, kiedy chcesz wznowić.
Justin Dennahower

Odpowiedzi:

78

Kiedy faktycznie sprawdzimy kod źródłowy standardowej biblioteki Go, możemy zobaczyć, gdzie gotosą one właściwie dobrze zastosowane.

Na przykład w math/gamma.gopliku oświadczenie jest używany :goto

  for x < 0 {
    if x > -1e-09 {
      goto small
    }
    z = z / x
    x = x + 1
  }
  for x < 2 {
    if x < 1e-09 {
      goto small
    }
    z = z / x
    x = x + 1
  }

  if x == 2 {
    return z
  }

  x = x - 2
  p = (((((x*_gamP[0]+_gamP[1])*x+_gamP[2])*x+_gamP[3])*x+_gamP[4])*x+_gamP[5])*x + _gamP[6]
  q = ((((((x*_gamQ[0]+_gamQ[1])*x+_gamQ[2])*x+_gamQ[3])*x+_gamQ[4])*x+_gamQ[5])*x+_gamQ[6])*x + _gamQ[7]
  return z * p / q

small:
  if x == 0 {
    return Inf(1)
  }
  return z / ((1 + Euler*x) * x)
}

W gototym przypadku chroni nas przed wprowadzeniem kolejnej (boolowskiej) zmiennej używanej tylko do sterowania przepływem, sprawdzanej na końcu. W tym przypadku , gotooświadczenie czyni kod właściwie lepsze i łatwiejsze do odczytania obserwacji (dość w przeciwieństwie do argumentu przeciwko gotowspomniałeś).

Należy również zauważyć, że gotoinstrukcja ma bardzo specyficzny przypadek użycia. Specyfikacja języka w goto stwierdza, że ​​nie może przeskakiwać zmiennych wchodzących w zakres (deklarowanych) i nie może przeskakiwać do innych bloków (kodu).

Kissaki
źródło
69
W twoim przykładzie, dlaczego zamiast tego po prostu wprowadzić funkcję small(x,z)do wywołania? W ten sposób nie musimy myśleć o tym, jakie zmienne są dostępne w small:etykiecie. Podejrzewam, że powód go nadal nie obsługuje pewnych typów wbudowanej obsługi w kompilatorze.
Thomas Ahle
5
@Jessta: Właśnie to mamy widoczność i pole do tego, prawda?
Thomas Ahle,
4
@ ogc-nick Przepraszam, nie było to jasne, chodziło mi o to, że funkcje można deklarować w zakresie, w którym są potrzebne, więc nie są widoczne dla kodu, który ich nie potrzebuje. Nie mówiłem o goto i zakresie.
Thomas Ahle,
4
@MosheRevah Przywoływany kod nie jest zoptymalizowany pod kątem czytelności. Jest zoptymalizowany pod kątem surowej wydajności, używając goto, który obejmuje 22 linie w ramach jednej funkcji. (A propozycja Thomasa Ahle'a jest jeszcze bardziej czytelna dla moich oczu.)
joel.neely
30

Goto to dobry pomysł, gdy żadna z wbudowanych funkcji kontrolnych nie robi tego, co chcesz, i kiedy możesz wyrazić to, co chcesz, za pomocą goto. (Szkoda w tych przypadkach w niektórych językach, gdy nie masz goto. W końcu nadużywasz jakiejś funkcji kontrolnej, używasz flag logicznych lub używasz innych rozwiązań gorszych niż goto.)

Jeśli jakaś inna funkcja kontrolna (używana w dość oczywisty sposób) może zrobić to, co chcesz, powinieneś użyć jej zamiast goto. Jeśli nie, bądź odważny i użyj goto!

Na koniec warto zauważyć, że Go's goto ma pewne ograniczenia zaprojektowane w celu uniknięcia niektórych niejasnych błędów. Zobacz te ograniczenia w specyfikacji.

Sonia
źródło
7

Wypowiedzi Goto spotykały się z dużym uznaniem od czasów kodu Spaghetti w latach 60. i 70. W tamtych czasach metodologia tworzenia oprogramowania była bardzo słaba lub żadna. Jednak Goto nie są natywnie złe, ale oczywiście mogą być nadużywane i nadużywane przez leniwych lub niewykwalifikowanych programistów. Wiele problemów z nadużywanymi Gotos można rozwiązać za pomocą procesów programistycznych, takich jak przeglądy kodu zespołu.

gotosą skokami w taki sam sposób techniczny jak continue, breaki return. Można by argumentować, że te stwierdzenia są złe w ten sam sposób, ale tak nie jest.

Dlaczego zespół Go włączył Gotos, prawdopodobnie wynika z faktu, że jest to powszechny prymityw kontroli przepływu. Ponadto, miejmy nadzieję, doszli do wniosku, że zakres Go wyklucza uniemożliwienie nadużywania języka bezpiecznego dla idioty.

Gustav
źródło
continue, breaki returnsą bardzo różne w jednym szczególnym kluczu: określają tylko „zostaw otaczający zakres”. Nie tylko zachęcają, ale wyraźnie wymagają, aby deweloper rozważał strukturę swojego kodu i polegał na strukturalnych prymitywach programowania (w przypadku pętli, funkcji i instrukcji switch). Jedyną zaletą gotoinstrukcji jest to, że pozwalają one na pisanie asemblacji w HLL, gdy optymalizator kompilatora nie jest w stanie sprostać zadaniu, ale odbywa się to kosztem czytelności i łatwości utrzymania.
Parthian Shot
Powodem tego jest tak zaskakujące, aby znaleźć się w ruchu jest to, że w każdym innym przypadku, w którym twórcy golang miał wybór między strukturze paradygmatu programowania i „wyjątkowej kontroli przepływu” / manipulacje wskaźnik instrukcji podoba setjmp, longjmp, goto, i try / except / finallywybrali zaważyć na ostrożności. goto, fwict, jest jedynym przyzwoleniem na przepływ sterowania z góry „programowania strukturalnego”.
Parthian Shot