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?


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


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).


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.


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.


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
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.