czwartek, 19 listopada 2009

Dlaczego goto nie jest złe

Studia, świat i moda każe mówić, że goto, to gówno totalne i nie ma racji bytu. Idąc pod prąd chcemy udowodnić, że tak nie zawsze jest. Myślę, że każde narzędzie języka może prowadzić to spaghetti code, trudności w jego języku i nieporozumień. Tak samo jak użycie wielu poziomów klas i polimorfizmu w niewłaściwy sposób, extension methody czy genericsy. Wszystko jest dla ludzi, tylko trzeba z tego kurwa umieć korzystać.

Poniżej kilka przykładów.

1. Wyjście z bardzo wielokrotnie zagnieżdżonej pętli.
  
List<MainElement> mainElements = new List<MainElement>();
bool breakForeach = false;
foreach (MainElement mainElement in mainElements)
{
  foreach (SecondaryElement secondaryElement in mainElement.SeconaryElements)
  {
    foreach (UnimportantElement unimportantElement in secondaryElement.UnimportantElements)
    {
      // warunek, który wymaga od nas wyjścia z bardzo zagnieżdzonej pętli
      // i ruszenia dalej do reszyt funkcji
      if (ComplicateCondition(mainElement, secondaryElement, unimportantElement))
      {
         SomeOperation(mainElement, secondaryElement, unimportantElement);
         breakForeach = true;
         break;
      }
    }
    if (breakForeach)
    break;
  }
  if (breakForeach)
  break;
}
// reszta funkcji
Funkcja ma 25 linii kodu i jeśli dołoży się do niej więcej logiki, to zupełnie przestanie być czytelna.
To samo w wersji z goto:
List<MainElement> mainElements = new List<MainElement>();
bool breakForeach = false;
foreach (MainElement mainElement in mainElements)
{
  foreach (SecondaryElement secondaryElement in mainElement.SeconaryElements)
  {
    foreach (UnimportantElement unimportantElement in secondaryElement.UnimportantElements)
    {
      // warunek, który wymaga od nas wyjścia z bardzo zagnieżdzonej pętli
      // i ruszenia dalej do reszyt funkcji
      if (ComplicateCondition(mainElement, secondaryElement, unimportantElement))
      {
         SomeOperation(mainElement, secondaryElement, unimportantElement);
         goto EndForeach;
      }
    }
  }
}
EndForeach:
Console.Out.WriteLine("jade dalej");
// reszta funkcji
Podejście z goto ma tą przewagę, że nie musimy sie martwić o dodatkową logikę, która może sie pojawić przy wychodzeniu z naszych pętli wewnętrznych. Możemy dowolnie zaciemnić kod, a dalej wyjdziemy z wszystkich trzech pętli od razu. Sprawa jest kurewsko prosta. Wyskakujemy i tyle. Resharper nam pomoże, bo po kliknięciu z ctrl przeskakuje jak należy na właściwe miejsce.

2. Wykonanie operacji aż do skutku.
Powiedzmy, że chcemy usunąć plik, ale może sie okazać, że ktoś go jeszcze blokuje. Więc czekamy chwile i próbujemy znowu. Zobaczmy przykład wykonania tego z goto i bez goto

public void Move1(string fileName, string destination)
{
  if (File.Exists(fileName))
  {
     int tries = 0;
     do
     {
       try
       {
          File.Move(fileName, destination);
          break;
       }
       catch (IOException)
       {
          tries++;
          Thread.Sleep(2000);
       }
     }
     while (tries < 5);
  }
}


I przykład z goto:

public void Move2(string fileName, string destination)
{
  if (File.Exists(fileName))
  {
     int tries = 0;
  move:
     try
     {
        File.Move(fileName, destination);
     }
     catch (IOException)
     {
        Thread.Sleep(2000);
        tries++;
        if (tries < 5) goto move;
     }
  }
}

Dużej różnicy nie widać, ale logika funkcji ulega zajebistej zmianie. Czytając pierwszy przykład trafiamy na pętle, która kieruje nasze myślenie na to, że będziemy coś robić kurewsko kilka razy, przy czym w 99% przypadków wszystko wykona się raz. Tylko jak coś się spierdoli pętla wykona się jeszcze raz. Czytając kod nasze myślenie jest kierowane na zjebane tory.
W przypadku z Goto. Widzimy labelke „move”, która zwraca naszą uwagę, ale nic nam nie przeszkadza, jest tylko ozdobnikiem. Czytając dalej trafiamy na najważniejszą funkcję „move” i dopiero w bloku catach widząc obsługę wyjątku. Jak coś się nie uda widzimy, że próbujemy 5 razy wyskakiwać do wcześniejszej labelki.

Podsumowując chciałbym powiedzieć, że wszystko kurwa jest dla ludzi i czasem jedno goto w funkcji sprawi, że algorytm będzie bardziej zajebisty. Oczywiście więcej goto może zadziałać odwrotnie.


całusy dla wszystkich. Szczególnie dla foki.


bizon



6 komentarzy:

  1. Aj tam kurwa, gówno prawda. Pierdolenie że świat, studia, moda... GOTO po prostu JEST do dupy i już, nie ma co jebać smutów i na siłę udowadniać, że jest inaczej.

    Pierwszy przykład: nie powinno w ogóle dojść do sytuacji zapierdalania po trzech zagnieżdżonych pętlach, bo to do chuja niepodobne. A jeśli już nawet to wystarczy wypierdolić całość do osobnej metody i jebnąć "return" - będzie dużo bardziej czytelnie.

    Przykład drugi jest jeszcze głupszy, wystarczy że zamienisz do/while na zwykłe while(tries<5) i masz zajebiście identyczne przesłanie co w twojej wersji z GOTO - tyle że bez jego zjebaności.

    Moim zdaniem za stosowanie GOTO powinno się upierdalać rency przy samej dupie, żeby skurwysyn jeden z drugim więcej tak nie robił.

    P.S. Przyznaję że wulgaryzmy są zajebiste i dodają kolorytu i swobody, ale nie w takim stylu jak ja ich teraz użyłem. Ani nie jak ty ich użyłeś w poście. Poświęć trochę czasu na SENSOWNE ich wykorzystanie i będzie git - bo póki co to jest megachujowo, pierwsze lepsze chipchopowe gówno wyglada lepiej niż to.

    OdpowiedzUsuń
  2. Procent,
    Bardzo dziękuje za zajebistą krytykę. Spodziewałem się takich właśnie argumentów. Iść pod prąd znaczy naginać prawdę, aż czarne będzie zielone, a białe nie będzie.

    Odnośnie pierwszego argumentu, że za chuja nie powinno być trzech pętli, albo osobna funkcja robiąca return…
    Zgadzam się, ale nie zawsze.
    Wiadomo, że teraz już się algorytmów nie pisze, bo wszystkie są zrobione ;) ale czasem robimy coś bardziej skomplikowanego jak implementacje algorytmu wyszukiwania ścieżki w pokolorowanym grafie. Wtedy tworzymy jebczą metodę i chcemy zostawić całość tak jak pokazałem. W smaltalku albo innym paskudztwie można było wyskoczyć z kilu pętli naraz (takie break 3). Chciałem pokazać przykład takiej implementacji. Wg mnie to działa.

    Nie pisze, że przykład drugi jest głupszy. O głupotach nie ma co gadać. Może być chujowy :D. Dla mnie chujowe jest robienie pętli, kiedy nie chce robić pętli, tylko czasem chce spróbować wrócić i zrobić coś jeszcze raz. Uważam, że to tak fajne jak linki w htmlu.

    Ja bym nie ujebywał rąk za używanie goto. Ja bym ujebywał za bezmyślne przeklejanie kodu. Jeśli ktoś myśli i potrafi coś umotywować sensownie, to mogę się nie zgodzić, ale lubię inne spojrzenie na coś co jest niekochane i odrzucone.

    Ostatecznym argumentem jest jednak to, że żaden twórca języka nie wypierdolił goto. Zawsze nad tym myślał i w końcu zostawiał. Coś posrało tych twórców, czy co? Ja tam osobiście bardziej się boje dynamic w c# 4.0.

    Z przekleństwami masz racje. Trochę za bardzo skupiłem się na treści zamiast na fantazji pisania o której foka wspomina w pierwszym wpisie. Będziemy nad tym pracować i mam nadzieje, że uda się kiedyś dojebać takie przekleństwa, że polski hura hop by się nie powstydził!

    OdpowiedzUsuń
  3. Ten komentarz został usunięty przez autora.

    OdpowiedzUsuń
  4. Co do drugiego przykładu, to właśnie while jest moim zdaniem hujowy. Bo jako czytelnik kodu włażę na pętlę i rozumuję, że operację będziemy wykonywać wiele razy a w wyjątkowym wypadku przerwać pętlę za pierwszą iteracją :) Kupa.

    Logika jest taka, żeby wykonać operację raz a w wyjątkowym przypadku powtórzyć ją, więc pętla jak dla mnie ni przypiął ni wypiął.

    Co do goto to sie nie wypowiadam w ogolnosci bo nie jestem az tak odwazny jak bizon i ide z pradem :)

    OdpowiedzUsuń
  5. Przykład 1. Podaj naprawdę sensowny przykład w którym powstrzymasz się od wyciągnięcia kodu do metody w przypadku potrójnie zagnieżdżonej pętli. Bo ja osobiście skłaniam się w 100% do tego co napisał w tym względzie %.

    Przykład 2. Ten przykład to akurat dydaktyczna porażka. Nie można wychodzić z założenia że jak zasób nie jest dostępny to dobijam się do niego w nadziei że może zaraz się zwolni. A jeżeli się nie uda po tych 5 próbach to równie dobrze można było wywalić jakiekolwiek powtarzanie i pozwolić wykonać się operacji raz. Następnie zamiast kminić jak tu wpleść goto żeby nie zaśmiecić kodu pomyśleć lepiej nad jakimś sensownym mechanizmem operacji na pliku z obsługą sytuacji wyjątkowych.

    Podsumowując Twoje przykłady jak na razie nie sprawiły, że zmieniłem swoje negatywne zdanie o goto.

    P.S. Takiego posta nie powstydziłby się chyba nawet Simon w swojej kontrowersyjnej serii o kodzie czytelnym :P

    OdpowiedzUsuń
  6. Goto było dobre jak wszystko pisało się ciurkiem - jak pies ssał.
    Trudno było się w tym wszystkim połapać wiec robiło się goto i czapka. Dochodził warunek? No problem: dopisywało się brand-new-code i na koniec wystarczyło jebnąć goto exit (czy cos w ten deseń). Czytam czwarty raz tą zajebistą pętlę w pętli pętli i jakoś czaje bez problemu gdzie się kursor jebnie jak wejdzie w breakpoint - fakt jest to z deczka nieczytelne - ale to już pomijam, wiadomo autor miał za zadanie napisać hujowy kod i zdał egzamin na pięć.

    OdpowiedzUsuń