Odpowiednie zastosowania instrukcji przełączania awaryjnego

14

Kiedy należy zastosować zastępczą (klasyczną) instrukcję switch? Czy takie użycie jest zalecane i zalecane, czy należy tego unikać za wszelką cenę?

shabunc
źródło
Nie wszystkie języki pozwalają na awarię instrukcji switch.
Oded
@Oded, edytowane, dodane słowo „klasyczne”. Nie wszystkie języki pozwolić spaść poprzez jednak nalegam to klasyczny)
shabunc
3
Jeśli mówisz o urządzeniu Duffa , to jedno.
Oded
6
@Oded: To pierwsza rzecz, o której myśli większość ludzi, ale nie jest to „właściwe”. Zgodnie z tym , sam Duff powiedział: „To zdecydowanie stanowi argument w dyskusji, czy przełącznik powinien przejść domyślnie, ale nie jestem pewien, czy argument jest za, czy przeciw”.
BlueRaja - Danny Pflughoeft

Odpowiedzi:

15

Oto przykład, w którym byłoby to przydatne.

public Collection<LogItems> GetAllLogItems(Level level) {
    Collection<LogItems> result = new Collection<LogItems>();
    switch (level) {
        // Note: fall through here is INTENTIONAL
        case All:
        case Info:
             result.Add(GetItemsForLevel(Info));
        case Warning:
             result.Add(GetItemsForLevel(Warning));
        case Error:
             result.Add(GetItemsForLevel(Error));
        case Critical:
             result.Add(GetItemsForLevel(Critical));
        case None:
    }
    return result;
}

Wydaje mi się, że tego rodzaju rzeczy (gdzie jeden przypadek obejmuje drugi), są raczej rzadkie i dlatego niektóre nowsze języki albo nie pozwalają na awarię, albo wymagają specjalnej składni.

konfigurator
źródło
13
W tym przykładzie, choć interesującym, brakuje wymaganego // INTENTIONAL FALL THROUGH HEREkomentarza wielkimi literami.
Russell Borogove
Równie łatwo jest napisać ten kod, gdy się przewróci, GetItemsForLevel(Info)wywołując wywołanie GetItemsForLevel(Warning)i tak dalej.
Caleb
@RussellBorogove: absolutnie poprawne.
konfigurator
@Caleb: W tym przypadku tak. Ale co, jeśli rozmowy byłyby trochę bardziej skomplikowane? Może stać się trochę owłosione, a upadek sprawia, że ​​wydaje się to proste. Powinienem zauważyć, że tak naprawdę nie jestem zwolennikiem używania wpadki - mówię tylko, że może to być przydatne w niektórych sytuacjach.
konfigurator
Nie najlepszy przykład. Istnieje kolejność poziomów ostrzegawczych. Definiując tę ​​kolejność, ta metoda ogranicza się do jednego wiersza filtrowania kodu pozycji, gdy poziom pozycji jest co najmniej równy pożądanemu poziomowi.
kevin cline
8

Używam ich, gdy pewne funkcje muszą być zastosowane dla więcej niż jednej wartości. Załóżmy na przykład, że masz obiekt o właściwości o nazwie operationCode. Jeśli kod jest równy 1, 2, 3 lub 4, chcesz uruchomićOperationX (). Jeśli jest to 5 lub 6, chcesz uruchomićOperationY (), a 7 - startOperationZ (). Dlaczego masz 7 kompletnych skrzynek z funkcjonalnością i przerwami, kiedy możesz skorzystać z awariów?

Myślę, że jest całkowicie poprawny w pewnych sytuacjach, szczególnie jeśli unika 100 instrukcji if-else. =)

Yatrix
źródło
4
To, co opisujesz, to switchwielokrotność casepowiązana z tym samym kodem. Różni się to od upadku, w którym wykonanie jednego casetrwa do następnego z powodu braku breakmiędzy nimi.
Blrfl,
3
To naprawdę nie ma znaczenia, awarią jest po prostu brak instrukcji break, więc „przechodzi” do następnej sprawy.
Yatrix,
Chciałbym ponownie sformułować scenariusz w twojej odpowiedzi, aby to odzwierciedlić.
Blrfl,
2
@Yatrix: Nie zgadzam się. Zupełnie inaczej jest, gdy kolejne przypadki wykonują dokładnie ten sam blok kodu, niż pominąć przerwę. Jedna jest rutyną, druga daleka od niej (ime) - a prawdopodobieństwo błędnego odczytania i niezamierzonego przepływu kontroli jest znacznie większe.
quentin-starin
3
@ pyta tak, jest inaczej, ale oba są błędne. Zapytał, kiedy / czy byłoby to właściwe. Podałem przykład, kiedy to zrobi. To nie miał być kompleksowy przykład. W zależności od języka posiadanie instrukcji przed awarią może nie działać. Nie sądzę, że tak będzie w przypadku C # stackoverflow.com/questions/174155/…
Yatrix
8

Używam ich od czasu do czasu, myślę, że zawsze jest to właściwe użycie - ale tylko wtedy, gdy jest dołączone do odpowiedniego komentarza.

gbjbaanb
źródło
7

Przypadki wypadania są w porządku. Często stwierdzam, że wyliczenie jest używane w wielu miejscach i że gdy nie trzeba rozróżniać niektórych przypadków, łatwiej jest zastosować logikę awaryjną.

Na przykład (zwróć uwagę na komentarze wyjaśniające):

public boolean isAvailable(Server server, HealthStatus health) {
  switch(health) {
    // Equivalent positive cases
    case HEALTHY:
    case UNDER_LOAD:
      return true;

    // Equivalent negative cases
    case FAULT_REPORTED:
    case UNKNOWN:
    case CANNOT_COMMUNICATE:
      return false;

    // Unknown enumeration!
    default:
      LOG.warn("Unknown enumeration " + health);
      return false;
  }
}

Uważam, że tego rodzaju użycie jest całkowicie akceptowalne.

Bringer128
źródło
Zauważ, że istnieje duża różnica między case X: case Y: doSomething()i case X: doSomething(); case Y: doAnotherThing(). W pierwszym przypadku intencja jest dość wyraźna, w drugim zaś upadek może być celowy lub nie. Z mojego doświadczenia wynika, że ​​pierwszy przykład nigdy nie uruchamia żadnych ostrzeżeń w kompilatorach / analizatorach kodów, podczas gdy drugi tak robi. Osobiście nazwałbym drugi przykład tylko „wpadnięciem” - nie jestem jednak pewien co do „oficjalnej” definicji.
Jens Bannmann
6

To zależy od:

  • twoje osobiste preferencje
  • standardy kodowania twojego pracodawcy
  • związane z tym ryzyko

Dwa główne problemy związane z przejściem jednej sprawy do drugiej to:

  1. To uzależnia Twój kod od kolejności instrukcji case. Nie dzieje się tak, jeśli nigdy się nie przewrócisz, a to powoduje, że stopień złożoności jest często niepożądany.

  2. Nie jest oczywiste, że kod dla jednej sprawy zawiera kod dla jednej lub więcej kolejnych spraw.

Niektóre miejsca wyraźnie zabraniają wpadania. Jeśli nie pracujesz w takim miejscu i jeśli czujesz się swobodnie z praktyką, i jeśli złamanie tego kodu nie spowoduje żadnego prawdziwego cierpienia, może to nie być najgorsze na świecie. Jeśli to zrobisz, pamiętaj o umieszczeniu przyciągającego uwagę komentarza w pobliżu, aby ostrzec tych, którzy przyjdą później (w tym ciebie przyszłego).

Caleb
źródło
4

Oto szybki (co prawda niekompletny (bez specjalnego obchodzenia roku przestępnego)) przykład upadku, który upraszcza moje życie:

function get_julian_day (date) {
    int utc_date = date.getUTCDate();
    int utc_month = date.getUTCMonth();

    int julian_day = 0;

    switch (utc_month) {
        case 11: julian_day += 30;
        case 10: julian_day += 31;
        case 9:  julian_day += 30;
        case 8:  julian_day += 31;
        case 7:  julian_day += 31;
        case 6:  julian_day += 30;
        case 5:  julian_day += 31;
        case 4:  julian_day += 30;
        case 3:  julian_day += 31;
        case 2:  julian_day += 28;
        case 1:  julian_day += 31;
        default: break;
    }

    return julian_day + utc_date;
}
AaronDanielson
źródło
5
Chociaż jest to dość sprytne, czas, który zaoszczędzisz na tym, będzie znacznie większy niż czas potrzebny innym ludziom na zrozumienie, co to robi. Możesz również uzyskać lepszą wydajność, usuwając aspekt awaryjny, tj. 31 + 28 + 31 to zawsze 90. Jeśli masz zamiar to zrobić, równie dobrze możesz po prostu umieścić sumy w przypadkach, ponieważ jest tylko 11 do rozważenia.
JimmyJames
Masz rację, mój przykład jest zdecydowanie wymyślony :) Powiem jednak, że łatwiej jest wykryć ukryty błąd, gdy liczba dni jest określona w ten sposób, zamiast wstępnie obliczać wszystkie skumulowane dni dla każdego przypadku zmiany .
AaronDanielson
2
Przyznaję ci to, ale lepiej byłoby zrobić to w stałej deklaracji, np. FEB_START = 31; MAR_START = FEB_START + 28; itd. nie jestem pewien, jaki to język, ale w wielu przypadkach byłby on obliczany w czasie kompilacji. Większy problem polega na tym, że jest to trochę tępy. Nie tyle, że to zły pomysł, tylko nietypowy. O ile nie ma naprawdę dużej korzyści, ogólnie lepiej trzymać się wspólnych idiomów.
JimmyJames
1
Doskonała alternatywa, wykorzystująca w tym przykładzie stałe dla dni początkowych. W każdym razie chodzi tutaj o to, że skumulowana suma jest świetnym zastosowaniem w przypadku awarii skrzynki rozdzielczej. Zgadzam się w 100% z używaniem wspólnych idiomów, ale charakter tego pytania dotyczy bardziej ezoterycznych cech, w których trudno jest znaleźć wspólne idiomy.
AaronDanielson
3

Jeśli czuję potrzebę przechodzenia od jednej sprawy do drugiej (co rzadkie, co prawda), wolę być bardzo wyraźny i goto case, oczywiście, zakładając, że twój język to obsługuje.

Ponieważ wpadanie jest tak rzadkie i bardzo łatwe do przeoczenia podczas czytania kodu, uważam, że właściwe jest, aby być wyraźnym - a goto, nawet jeśli jest to przypadek, powinno wyróżniać się jak obolały kciuk.

Pomaga to również uniknąć błędów, które mogą wystąpić podczas zmiany kolejności instrukcji case.

quentin-starin
źródło