fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
3 minut

Jedyny przypadek gdy GOTO nie jest FUJ


22.08.2008

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

0 0 votes
Article Rating
10 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
dario-g
15 years ago

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
arkadiusz.wasniewski
15 years ago

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
Uno
15 years ago

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
15 years ago

@arek:
Oczywiście, że ten kod wymaga refaktoryzacji. Pewnie stąd w treści posta znalazł się link do strony http://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
Wojciech Gebczyk
15 years ago

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
Gregi
15 years ago

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
arkadiusz.wasniewski
15 years ago

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
15 years ago

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
15 years ago

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

chojrak tchórzliwy pies
chojrak tchórzliwy pies
13 years ago

przejrzyjcie źródła glibc, jądra linuxa i innych ważnych projektów. aż roi się tam od goto i to nie dlatego, że pisały go lamy (choć takie jest pewno podejście większości). co prawda użycie goto często powoduje, że kod w C niewiele zaczyna różnić się od asemblera, ale daje programiście wolność w pełnym zakresie i niejednokrotnie pozwala pisać wydajniejszy kod. słusznie przytoczono wyjście z zagnieżdżonych pętli, czy wręcz – wyjście z wstawki asemblerowej do konkretnego miejsca we fragmencie C. goto, jak każda inna konstrukcja języka programowania, powinno być stosowane w sytuacjach, w których jest to właściwe. pal sześć czytelność jeśli można zyskać 30% wydajności w ciasnej pętli wykonywanej milion razy.

Kurs Gita

Zaawansowany frontend

Szkolenie z Testów

Szkolenie z baz danych

Książka

Zobacz również