Jedyny przypadek gdy GOTO nie jest FUJ

Jedna z zasad, której młodzi programiści uczą się na początku swojej kariery (żeby nie powiedzieć "wysysają z mlekiem swojego nauczyciela") brzmi:

"instrukcja GOTO w językach programowania poziomu wyższego niż asembler istnieje po to i tylko po to, aby świadomie ignorować jej egzystencję"

Prawda? I co tu dużo gadać, ciężko się z tą teorią nie zgodzić. Jedyne do czego prowadzi używanie tej instrukcji to powstanie tzw "unmaintable spaghetti code".

Chyba że...
Jest moim zdaniem jeden scenariusz, w którym instrukcja GOTO czasami się w C# przydaje. Chodzi mianowicie o "switch fallthrough" - spójrzmy na następujący kod:

1:   switch (number)
2:   {
3:   	case 0:
4:   		DoSomething();
5:   	case 1:
6:   		DoSomethingElse();
7:   		break;
8:   	default:
9:   		throw new Exception();
10:   }

W Javie, w C, w C++ i w milionie innych języków taka kontrukcja spowoduje wykonanie obu metod dla number==0 oraz jednej metody dla number==1. A co się stanie w C#? W C# dostaniemy w twarz błędem kompilacji: "Control cannot fall through from one case label ('case 0:') to another" (wtrącenie: coś mi się kojarzy, że w C# 1.0 taka konstrukcja była poprawna, jednak nie mam zainstalowanego VS 2003 żeby to sprawdzić i nie dam sobie niczego uciąć). Dobrze to czy źle... nie mnie teraz oceniać. Zainteresowanych odsyłam do uzasadnienia w dziale Visual C# Developer Center. Idźmy jednak dalej: aby osiągnąć takie samo zachowanie w C# musimy jawnie je opisać w ten sposób:

1:   switch (number)
2:   {
3:   	case 0:
4:   		DoSomething();
5:   		goto case 1;
6:   	case 1:
7:   		DoSomethingElse();
8:   		break;
9:   	default:
10:   		throw new Exception();
11:   }

Inaczej po prostu się nie da, i jest to moim zdaniem jedyny (a 1>0!) uzasadniony scenariusz użycia konstrukcji GOTO w C#.

Na koniec jeszcze kilka słów, coby nie powstało niezamierzone "misandersztendiś": pomiędzy liniami tego posta nie ma stwierdzenia "switch z goto jest dobry". Instrukcja switch sama w sobie trochę zalatuje makaronem (na szczęście da się z tym coś zrobić). Jeśli jednak i tak mamy w kodzie switcha to równie dobrze możemy weń wstawić goto, lepsze to niż wielokrotne pisanie tych samych instrukcji dla różnych case'ów (ileż amerykanizmów i anglizmów w jednym zdaniu! aż poczułem hamburgera w ustach i krople deszczu na czole...).


UWAGA! Stosować z rozwagą! Głupota i bezmyślność może dość szybko skończyć się wiecznym gniciem w programistycznej "hall of shame", czyli The Daily WTF. I żeby potem nie było na mnie;).

Opublikowane 22 sierpnia 08 08:40 przez Procent
Filed under:

Powiadamianie o komentarzach

Jeżeli chciałbyś otrzymywać email gdy ta wypowiedź zostanie zaktualizowana, to zarejestruj się tutaj

Subskrybuj komentarze za pomocą RSS

Komentarze:

# dario-g said on sierpnia 22, 2008 09:31:

Można opakować switcha i zrobić też tak:

0: void GotoSwitch(int number){

1:   switch (number)

2:   {

3:   case 0:

4:   DoSomething();GotoSwitch(1);

5:   break;

6:   case 1:

7:   DoSomethingElse();

8:   break;

9:   default:

10:   throw new Exception();

11:   }

12: }

;))

# arkadiusz.wasniewski said on sierpnia 22, 2008 10:06:

Jak dla mnie powyższe uzasadnienie jest mało przekonujące. Patrząc na kod mogę stwierdzić, iż wymaga on refaktoryzacji. Metoda DoSometing() powinna wywoływać wewnętrznie DoSometingElse().

Pozdrawiam

Arek

# Uno said on sierpnia 22, 2008 10:19:

goto przede wszystkim uzywa sie do wyjscia z zagniezdzonych petli. Poza tym twoj kod duzo lepiej zapisac prostolinijnie:

1:   switch (number)

2:   {

3:   case 0:

4:   DoSomething();

7:   DoSomethingElse();

5:   break;

6:   case 1:

7:   DoSomethingElse();

8:   break;

9:   default:

10:   throw new Exception();

11:   }

niz stosowac "uzasadnione uzycie goto"

# Procent said on sierpnia 22, 2008 10:28:

@arek:

Oczywiście, że ten kod wymaga refaktoryzacji. Pewnie stąd w treści posta znalazł się link do strony www.refactoring.com :) Dodatkowo - warunkowe wywołanie DoSomethingElse() niekoniecznie musi być dobrym pomysłem.

@Uno:

Goto używa się w wielu sytuacjach, w tym także do "wyjścia z zagnieżdżonych pętli". Publikując tą notkę chciałem ujawnić moje stanowisko w tej sprawie - uważam że nie powinno się stosować goto do wyjścia z zagnieżdżonych pętli, od tego mamy 'break'.

Co do przedstawionego "zapisu prostolinijnego", o tym też napisałem: "lepsze to [goto] niż wielokrotne pisanie tych samych instrukcji dla różnych case'ów".

# Wojciech Gebczyk said on sierpnia 22, 2008 11:29:

Przypomnialo mi sie ze ostatnio (wczoraj) widzialem w kodzie C (dokladniej kodzie C ktory chcial uchodzic za C++ - czyli mial klasy i.... tyle :P).

Byla tam 17 stronnicowa metoda (po wydrukowaniu z VS) gdzie dosc duzo bylo goto. Uzyte to bylo do zakonczenia przetwarzania jakis obliczen gdy uzytkownik anulowal operacje. Skok bylo do miejsca "sprzatania". Mozna bylo to latwo uproscic doadjac metode z wlasciym kodem operacji gdzie zamiwst goto bylby return a zwenetrzna funkcja by po zwracanym wyniku wiedziala czy sprztaca czy robic cos innego.

To wlasciwy komentarz.

1. Nie zawsze break zadziala jesli mamy petle w petli i wychodzimy z najglebszego miesjca. Najczesciej goto mozna zastapic nowa metoda i returnem. BTW: co w sporej czesci przypadkow nie jako przy okazji poprawia czytelnosc kodu.

2. Zgodze sie ze zdarzaja sie napraaaaawde rzaadkie przypadki gdzie goto ma sens.

3. Jesli uzywa sie goto, to wedlug mnie skok nie moze byc poza "czesc wizualna aktualnego kodu" - najlepiej pare linijek wta czy w druga.

4. Jesli skok jest duzy to warto zastanowic sie nad metoda i returnem.

5. Ogolnie: kompilatorowi jest wsio jedno czy ma goto czy return. Dla piszacego kod tez wsio jedno poki nie musi wrocic do tego kodu. Rzecz wogole cala z tym czytelnym kodem jest taka ze to MA BYC MAINTAINABLE (jak to po pl napisac???). Czyli ze jak ktos inny czy samemu sie pozniej siadzie bedzie sie w stanie szybko zrozumiec.

6. koniec gadki :P

# Gregi said on sierpnia 22, 2008 11:38:

Jak sie wychodzi z zagniezdzonych petli uzywajac (tylko) break?

Uwazam, ze goto powinno sie stosowac, bo inaczej mamy potworki w stylu funkcji GotoSwitch, albo znanej konstrukcji do{ } while(0);

Zdecydowanie goto sie przydaje - moze do jego uzycia trzeba po prostu dojrzec? ;-)

# arkadiusz.wasniewski said on sierpnia 22, 2008 11:47:

Programuję 10 lat i nigdy nie zdarzyło mi się mieć potrzeby użycia goto. Jeśli kod jest dobrze napisany to nie ma szans na goto.

# Gutek said on sierpnia 22, 2008 11:51:

Zgadzam sie za Arkiem.

Jezeli w ktoryms momencie dochodze do tego, ze jestm i potrzebne GoTo to zastanawiam sie gdzie poplenilem blad i poprawiam kod tak by GoTo nie bylo.

Gutek

# Procent said on sierpnia 22, 2008 12:06:

@Gregi:

Nawet jeżeli nie tylko break, to na pewno nie powinno się tego robić za pomocą goto. A dojrzewanie... moim zdaniem to raczej w drugą stronę: 'faktycznie, goto zmniejsza czytelność, utrudnia modyfikacje, i ogolnie sux' ;).

@Arek, Gutek:

No to chyba... git. Mi zdarzyło się raz, właśnie w opisanym scenariuszu.

Co o tym myślisz?

(wymagane) 
(opcjonalne)
(wymagane) 

  
Wprowadź kod: (wymagane)

About Procent