to-GOTO-or-not-to-GOTO

Podobno pierwszymi dwoma zdaniami, jakie wypowiada każdy programista są: „Będę zwalniał pamięć.” oraz „Nigdy, przenigdy nie będę używał instrukcji GOTO (oprócz języków assemblerowych)”. Wpojone zasady dobrze mieć na uwadze, ale czy zdarzają się sytuacje, w których użycie Instrukcji-Której-Nazwy-Nie-Wolno-Wymawiać jest konieczne? Przychodzą mi do głowy dwa przypadki:

  1. tzw. „switch fallthrough”, gdy w instrukcji switch chcemy przejść z wykonaniem kodu z case'a do case'a bezpośrednio pod nim, przykład (C++) poniżej:
  2. switch (variable)
    {
        case 0:
            myFunction1();
        case 1:
            myFunction2();
            break;
        case 2:
            myFunction3();
    }
    

    W przypadku, gdy zmienna variable będzie miała wartość 0, wykona się funkcja myFunction1() ORAZ myFunction2(). Taka konstrukcja możliwa jest też w Javie. Jednak w przypadku próby kompilacji poniższego kodu w C# (.NET 3.5)

    switch (variable)
    {
        case 0:
            myFunction1();
        case 1:
            myFunction2();
            break;
        case 2:
            myFunction3();
            break;
        default:
            throw new Exception("invalid variable value");
     }
    

    zakończy się radosnym komunikatem, którym uraczy nas Visual Studio: Error: Control cannot fall through from one case label ('case 0:') to another.
    Klops? Otóż niekoniecznie. Na ratunek (o, ironio!) przychodzi nam właśnie zapomniana, od dawna leżąca w kącie, zakurzona instrukcja GOTO, którą musimy explicite umieścić w kodzie:

    switch (variable)
    {
        case 0:
            myFunction1();
            goto case 1;
        case 1:
            myFunction2();
            break;
        case 2:
            myFunction3();
            break;
        default:
            throw new Exception("invalid variable value");
    }
    

    w ten sposób osiągniemy pożądany efekt.

  3. wychodzenie z wielokrotnie zagnieżdżonych pętli.
  4. int outI = 0, outJ = 0, outK = 0;
    for (int i = 0; i < N; ++i)
        for (int j = 0; j < M; ++j)
            for (int k = 0; k < P; ++k)
            {
                if (!myFunction(i, j, k)) continue;
                outI = i;
                outJ = j;
                outK = k;
                goto Outside;
            }
    Outside:
    // Do something with outI, outJ, outK..
    

    Ten przykład jest jednak mniej edukacyjny, gdyż powyższy kod da się obejść np. za pomocą odpowiednio napisanej metody.

Rozważając używanie w kodzie instrukcji GOTO, pamiętajmy o jednym: GOTO zaciemnia kod! Kod z kolei ma tą właściwość, że czasem się do niego wraca, mogą to robić również ludzie, którzy po nas ów kod przejmują.
Do zdań wypowiadanych jako pierwsze przez programistę dorzućmy więc kolejne:

„Będę organizował kod tak, jakby osoba która go po mnie przejmie była seryjnym mordercą, który wie, gdzie mieszkam.”

Be Sociable, Share!
czoper opublikowano dnia 2010-2-12 Kategoria: Programowanie | Tagi:,

Zostaw odpowiedź

(Ctrl + Enter)