Switch instrukcji fall-through… czy powinno być dozwolone? [Zamknięte]

120

Odkąd pamiętam, unikałem używania instrukcji switch fall-through. Właściwie nie pamiętam, żeby kiedykolwiek wkroczyło do mojej świadomości jako możliwy sposób robienia rzeczy, ponieważ wcześnie wwiercono mi się w głowę, że to nic innego jak błąd w instrukcji przełącznika. Jednak dzisiaj natknąłem się na kod, który używa go zgodnie z projektem, co sprawiło, że od razu zacząłem się zastanawiać, co wszyscy w społeczności myślą o upadku instrukcji switch.

Czy jest to coś, na co język programowania nie powinien wyraźnie zezwalać (tak jak robi to C #, chociaż dostarcza obejścia), czy też jest to cecha dowolnego języka, która jest wystarczająco potężna, aby pozostawić ją w rękach programisty?

Edycja: Nie byłem wystarczająco szczegółowy, co miałem na myśli, mówiąc o upadku. Często używam tego typu:

    switch(m_loadAnimSubCt){
        case 0:
        case 1:
            // Do something
            break;
        case 2:
        case 3:
        case 4:
            // Do something
            break;
   }

Martwię się jednak o coś takiego.

   switch(m_loadAnimSubCt){
        case 0:
        case 1:
            // Do something, but fall through to the other cases
            // after doing it.

        case 2:
        case 3:
        case 4:
            // Do something else.
            break;
   }

W ten sposób za każdym razem, gdy wielkość liter wynosi 0, 1, zrobi wszystko w instrukcji switch. Widziałem to zgodnie z projektem i po prostu nie wiem, czy zgadzam się, że instrukcje przełączające powinny być używane w ten sposób. Myślę, że pierwszy przykład kodu jest bardzo przydatny i bezpieczny. Drugi wydaje się trochę niebezpieczny.

Fostah
źródło
51
Nie zgadzam się, że nie jest konstruktywna. 27 głosów za pytaniem mówi, że są inni, którzy też tak czują.
Wes Modes
2
„nie konstruktywny” to stary i nieco niejasny bliski powód. W dzisiejszych czasach takie pytania można i należy oznaczyć do zamknięcia jako czysto opiniotwórcze. SO dotyczy pytań na temat kodu lub projektu, na które można odpowiedzieć, a nie „jaka jest opinia„ społeczności ”[cokolwiek to jest] na temat mojej opinii o funkcji X”. Programmers.SE byłaby lepszym rozwiązaniem, ale nawet wtedy wartość tak szerokiego i opiniotwórczego pytania jest nadal wysoce wątpliwa.
underscore_d
1
Istnieją bardzo wyraźne przypadki użycia, w których używa się fallthrough, więc nie jestem pewien, dlaczego miałby być oparty na opinii. Zgadzam się, że to pytanie jest trochę niejasne, ale nie powinno być zamknięte jako nie konstruktywne. Może po prostu zredagowano tak, aby był jaśniejszy przypadek: „Kiedy używa się Fallthrough? Która ma jasną odpowiedź. Dobre teksty w C ++ omawiają to, jest to mylące dla nowych programistów (lub nawet bardziej doświadczonych programistów z innych języków), więc wydaje się być dobrym pytaniem dla SO. Rzeczywiście, wygląda to na prototypowo dobre pytanie SO. Nawet jeśli sposób, w jaki został zapytany, nie był doskonały.
eric

Odpowiedzi:

89

Może to zależeć od tego, co uważasz za przełomowe. Nie przeszkadza mi tego typu rzeczy:

switch (value)
{
  case 0:
    result = ZERO_DIGIT;
    break;

  case 1:
  case 3:
  case 5:
  case 7:
  case 9:
     result = ODD_DIGIT;
     break;

  case 2:
  case 4:
  case 6:
  case 8:
     result = EVEN_DIGIT;
     break;
}

Ale jeśli masz etykietę przypadku, po której następuje kod, który przechodzi do innej etykiety przypadku, prawie zawsze uważałbym to za zło. Być może lepszym pomysłem byłoby przeniesienie wspólnego kodu do funkcji i wywołanie z obu miejsc.

Pamiętaj, że używam definicji „zła” w C ++ FAQ

Fred Larson
źródło
Zgadzam się z tobą: nie myślę o twoim przykładzie jako o upadku, a jeśli jest przypadek, w którym chcę wykonać wiele bloków kodu przez fallthrough, prawdopodobnie jest na to lepszy sposób.
Dave DuPlantis
10
+1 po prostu za definicję „zła”. Pożyczę to, dzięki ;-)
Joachim Sauer
Rozumiem, o co ci chodzi i masz rację, ale twój przykład jest naprawdę zły. Nikt nie powinien testować cyfr w ten sposób. Scnr, twoja odpowiedź i tak jest prawidłowa.
Tim Büthe
Przykładem jest to, czego C # nie dopuszcza. Prawdopodobnie z jakiegoś powodu :-)
Joey
Niestety, definicja zła wydaje się być niedostępna
Mam nadzieję, że
57

To miecz obosieczny. Czasami jest to bardzo przydatne, ale często niebezpieczne.

Kiedy jest dobrze? Jeśli chcesz, aby 10 przypadków było przetwarzanych w ten sam sposób ...

switch (c) {
  case 1:
  case 2:
            ... Do some of the work ...
            /* FALLTHROUGH */
  case 17:
            ... Do something ...
            break;
  case 5:
  case 43:
            ... Do something else ...
            break;
}

Jedyną zasadą, która mi się podoba, jest to, że jeśli kiedykolwiek zrobisz coś wymyślnego i wykluczysz przerwę, potrzebujesz wyraźnego komentarza / * FALLTHROUGH * /, aby wskazać, że był to twój zamiar.

John M.
źródło
3
+1! Zgadzam się, że od czasu do czasu jest to właściwa odpowiedź i ZWYKLE zgadzam się, że gdy jest to zgodne z projektem, należy to skomentować. (W przeglądzie kodu ZROBIŁbym drugiemu programistowi komentarz jako niepuste przejście zgodnie z projektem. Nie żeby tak się stało; jesteśmy sklepem C #, w którym to nie jest obsługiwane :)
John Rudy
4
Ja sam używam komentarza / * FALL THROUGH * / w moim kodzie tam, gdzie jest to stosowne. =]
strager
2
Jeśli masz PC-Lint, musisz wstawić / * - fallthrough * /, w przeciwnym razie narzeka.
Steve Melnikoff
1
mimo to, jeśli chcesz wyjaśnić, że zachowanie jest zamierzone, równie dobrze możesz to zrobić, aby if(condition > 1 && condition <10) {condition = 1}; switch(...zapisać przypadki (dla wyraźnego spojrzenia), a każdy może zobaczyć, że naprawdę chciałeś, aby te przypadki wyzwoliły tę samą akcję.
Mark
1
To wciąż okropne kodowanie. Pierwszy przypadek powinien wywołać funkcję, drugi przypadek powinien wywołać tę samą funkcję, a następnie wywołać drugą funkcję.
BlueRaja - Danny Pflughoeft
21

Czy słyszałeś o urządzeniu Duffa ? To świetny przykład wykorzystania przełącznika Fallthrough.

Jest to funkcja, której można używać i można ją nadużywać, podobnie jak prawie wszystkie funkcje języka.

tzot
źródło
Dowiedz się czegoś nowego każdego dnia - dzięki! Jednak współczuję biednym deweloperom, którzy przechodzą przez takie skrzywienia.
Justin R.
Wow, to jest coś, co otacza twój mózg.
Fostah
6
Zwróć uwagę na cytowany komentarz Duffa w tym artykule: „Ten kod tworzy jakiś argument w tej debacie, ale nie jestem pewien, czy jest za czy przeciw”.
John Carter,
Urządzenie Duffa jest ewidentnie złe
Marjeta
21

Opadanie jest naprawdę poręczne, w zależności od tego, co robisz. Rozważ ten schludny i zrozumiały sposób aranżacji opcji:

switch ($someoption) {
  case 'a':
  case 'b':
  case 'c':
    // Do something
    break;

  case 'd':
  case 'e':
    // Do something else
    break;
}

Wyobraź sobie, że robisz to z if / else. To byłby bałagan.

Lucas Oman
źródło
1
Uważam, że ten typ upadku jest bardzo pomocny
Mitchel Sellers
jeśli „zrób coś” to coś więcej niż tylko trochę, to przełącznik będzie raczej bałaganem niż jeśli / jeszcze. Dzięki if / else wszędzie jest jasne, która zmienna jest testowana. Nawet ten mały przykład nie wyglądałby tak źle z if / else w moich oczach
grenix
10

Może to być bardzo przydatne kilka razy, ale ogólnie rzecz biorąc, brak awarii jest pożądanym zachowaniem. Przechodzenie powinno być dozwolone, ale nie dorozumiane.

Przykład aktualizacji starych wersji niektórych danych:

switch (version) {
    case 1:
        // Update some stuff
    case 2:
        // Update more stuff
    case 3:
        // Update even more stuff
    case 4:
        // And so on
}
Peter Mortensen
źródło
6

Chciałbym mieć inną składnię dla awaryjnych przełączników, coś w rodzaju, błąd ...

switch(myParam)
{
  case 0 or 1 or 2:
    // Do something;
    break;
  case 3 or 4:
    // Do something else;
    break;
}

Uwaga: byłoby to już możliwe w przypadku wyliczeń, jeśli zadeklarujesz wszystkie przypadki w swoim wyliczeniu za pomocą flag, prawda? To też nie brzmi tak źle; Sprawy mogłyby (powinny?) już być częścią twojego wyliczenia.

Może byłby to fajny przypadek (bez gry słów) dla płynnego interfejsu z metodami rozszerzającymi? Coś jak, błąd ...

int value = 10;
value.Switch()
  .Case(() => { /* Do something; */ }, new {0, 1, 2})
  .Case(() => { /* Do something else */ } new {3, 4})
  .Default(() => { /* Do the default case; */ });

Chociaż to chyba jeszcze mniej czytelne: P

Erik van Brakel
źródło
1
Pierwsza sugestia bardzo mi się podoba. Czyta się bardzo dobrze i oszczędza kilka wierszy kodu. Druga zajęła mi chwilę, żeby owinąć mózg, ale ma sens, ale wygląda na to, że to bałagan.
Fostah
1
Tak, tak też myślałem, to był po prostu przypadkowy pierdnięcie umysłu, który odłożyłem, próbując znaleźć inny sposób przełączania się za pomocą istniejących konstrukcji językowych.
Erik van Brakel
Przebiegłem przez to pytanie i naprawdę to lubię! Miałem pomysł, jak poprawić czytelność tego, ale SO nie pozwala mi publikować komentarzy w wielu wierszach :( Ktoś powinien zaimplementować to tak jak POC, wygląda fajnie w implementacji i używaniu (:
Victor Elias
5

Jak wszystko: jeśli jest używany ostrożnie, może być eleganckim narzędziem.

Uważam jednak, że wady bardziej niż usprawiedliwiają, aby go nie używać, a ostatecznie nie pozwalać już na to (C #). Wśród problemów są:

  • łatwo „zapomnieć” o przerwie
  • dla opiekunów kodu nie zawsze jest oczywiste, że pominięta przerwa była zamierzona

Dobre wykorzystanie przełącznika / obudowy:

switch (x)
{
case 1:
case 2:
case 3:
 Do something
 break;
}

Baaaaad użycie przełącznika / przełamania obudowy:

switch (x)
{
case 1:
    Some code
case 2:
    Some more code
case 3:
    Even more code
    break;
}

Można to przepisać przy użyciu konstrukcji if / else bez żadnych strat, moim zdaniem.

Moje ostatnie słowo: trzymaj się z daleka od błędnych etykiet przypadków, jak w złym przykładzie, chyba że zachowujesz starszy kod, w którym ten styl jest używany i dobrze rozumiany.

steffenj
źródło
3
Możesz ponownie napisać większość konstrukcji językowych za pomocą if () i goto ... to jednak nie usprawiedliwia porzucenia jawnej konstrukcji.
Shog9
2
Wiem, ale konstrukcje przełącznika / przypadku, które jawnie używają "przypadek 1: jakiś kod przypadek 2: trochę więcej kod przypadek 3: ostateczne złamanie kodu;" można i należy przepisać przy użyciu if / else if (moja opinia).
steffenj
1
Pamiętaj, że przełącznik może być wielokrotnie szybszy niż lista elementów if-els. Switch korzysta z tablicy skoków, a jeśli nie jest to możliwe, jego skoki warunkowe będą zorganizowane w drzewie decyzyjnym zamiast w postaci liniowej listy skoków warunkowych. Dobrze skonstruowane zagnieżdżone instrukcje If-else będą w najlepszym przypadku równie szybkie.
Jochem Kuijpers,
4

Jest potężny i niebezpieczny. Największym problemem związanym z przebiciem jest to, że nie jest on wyraźny. Na przykład, jeśli napotkasz często edytowany kod, który ma przełącznik z błędami, skąd wiesz, że jest to zamierzone, a nie błąd?

Gdziekolwiek go używam, zapewniam, że jest odpowiednio skomentowany:

switch($var) {
    case 'first':
        // Fall-through
    case 'second':
        i++;
        break;
 }
Dan Hulton
źródło
2
Zamiar jest oczywisty, jeśli nie ma kodu dla przypadku „pierwszy”. Jeśli kodujesz tam i chcesz również uruchomić kod dla przypadku „sekunda”, to komentarz jest ważny. Ale i tak uniknąłbym tego scenariusza.
Joel Coehoorn
1
@Joel - całkowicie się zgadzam. W moim sklepie ludzie wpadali w błąd w takich przypadkach i myślę, że zmniejsza to czytelność. Jeśli jednak jest tam kod, umieść komentarz zastępczy.
Fred Larson
Co mamy na myśli „niewyraźne”. Dlatego się łamiemy; Robią to w innych językach. Jest to problem tylko dla osób, które nie nauczyły się swoich języków w kolejności. C # żąda tego dla domyślnego przypadku ffs, bez powodu do imho.
mckenzm
3

Korzystanie z przejścia tak jak w pierwszym przykładzie jest oczywiście w porządku i nie uważałbym tego za prawdziwy upadek.

Drugi przykład jest niebezpieczny i (jeśli nie jest szeroko komentowany) nieoczywisty. Uczę moich uczniów, aby nie używali takich konstrukcji, chyba że że uznają, że warto poświęcić im blok komentarza, który opisuje, że jest to celowa wpadka i dlaczego to rozwiązanie jest lepsze niż alternatywy. To zniechęca do niechlujnego używania, ale nadal sprawia, że ​​jest to dozwolone w przypadkach, gdy jest wykorzystywane na korzyść.

Jest to mniej więcej równoważne temu, co robiliśmy w projektach kosmicznych, gdy ktoś chciał złamać standard kodowania: musiał ubiegać się o zwolnienie (a mnie wezwano, aby doradzić w sprawie wyroku).

Wouter van Ooijen
źródło
2

Nie podoba mi się, że moje switchoświadczenia upadają - są zbyt podatne na błędy i trudne do odczytania. Jedynym wyjątkiem jest sytuacja, gdy wiele caseinstrukcji działa dokładnie to samo.

Jeśli istnieje jakiś wspólny kod, którego chce użyć wiele gałęzi instrukcji switch, wyodrębniam go do oddzielnej wspólnej funkcji, którą można wywołać w dowolnej gałęzi.

Matt Dillard
źródło
1

W niektórych przypadkach używanie fall-through jest aktem lenistwa ze strony programisty - przydałoby mu się seria || na przykład instrukcje, ale zamiast tego używają serii przypadków przełączania typu „catch-all”.

Biorąc to pod uwagę, stwierdziłem, że są one szczególnie pomocne, gdy wiem, że ostatecznie i tak będę potrzebować opcji (na przykład w odpowiedzi menu), ale nie zaimplementowałem jeszcze wszystkich opcji. Podobnie, jeśli robisz upadek zarówno dla „a”, jak i „A”, uważam, że znacznie czystszym jest użycie przełącznika przelotowego niż złożonej instrukcji if.

Prawdopodobnie jest to kwestia stylu i tego, jak myślą programiści, ale generalnie nie przepadam za usuwaniem składników języka w imię `` bezpieczeństwa '' - dlatego bardziej skłaniam się ku C i jego wariantom / potomkom niż, powiedzmy, Jawa. Lubię być w stanie małpować ze wskazówkami i tym podobnymi, nawet jeśli nie mam do tego „powodu”.

królikarnia
źródło
Ściśle mówiąc. Instrukcje switch przeskakują przez ustawioną wartość do kilku określonych lokalizacji kodu. Chociaż kompilator prawdopodobnie wychwyciłby takie rzeczy, instrukcja switch ma wartość szybkości i poprawności w serii instrukcji lub. Jeśli p wynosi 0 lub p wynosi 1 lub p wynosi 2. W rzeczywistości musiałem ocenić, czy p wynosi 0, a p wynosi 1, zamiast przeskoczyć do lokalizacji p = 2 w kodzie. Który okazał się identyczny z p0 i p1. Ale nigdy nie musiałem ich sprawdzać.
Tatarize
1

Fall-through powinno być używane tylko wtedy, gdy jest używane jako tablica skoków do bloku kodu. Jeśli jakakolwiek część kodu zawiera bezwarunkową przerwę przed większą liczbą przypadków, wszystkie grupy przypadków powinny kończyć się w ten sposób.

Wszystko inne jest „złe”.

BCS
źródło