Czy instrukcje switch powinny zawsze zawierać domyślną klauzulę?

254

W jednej z moich pierwszych recenzji kodu (jakiś czas temu) powiedziano mi, że dobrą praktyką jest dołączanie domyślnej klauzuli do wszystkich instrukcji switch. Niedawno pamiętałem tę radę, ale nie pamiętam, jakie było uzasadnienie. Brzmi dla mnie dość dziwnie.

  1. Czy istnieje uzasadniony powód, aby zawsze dołączać domyślne oświadczenie?

  2. Czy ten język jest zależny? Nie pamiętam, jakiego języka używałem w tym czasie - może dotyczy to niektórych języków, a nie innych?

tttppp
źródło
9
Będzie to w dużej mierze zależne od języka
skaffman,

Odpowiedzi:

276

Obudowy przełączników powinny prawie zawsze mieć defaultobudowę.

Powody, dla których warto użyć default

1. Aby „złapać” nieoczekiwaną wartość

switch(type)
{
    case 1:
        //something
    case 2:
        //something else
    default:
        // unknown type! based on the language,
        // there should probably be some error-handling
        // here, maybe an exception
}

2. Aby obsłużyć „domyślne” akcje, w których przypadki dotyczą zachowania specjalnego.

Widać to DUŻO w programach sterowanych przez menu i skryptach powłoki bash. Możesz to również zobaczyć, gdy zmienna jest zadeklarowana poza przypadkiem przełączania, ale nie jest inicjowana, a każda sprawa inicjuje ją na coś innego. Tutaj domyślna również musi ją zainicjować, aby kod linii, który uzyskuje dostęp do zmiennej, nie powodował błędu.

3. Aby pokazać osobie czytającej Twój kod, że opisałeś tę sprawę.

variable = (variable == "value") ? 1 : 2;
switch(variable)
{
    case 1:
        // something
    case 2:
        // something else
    default:
        // will NOT execute because of the line preceding the switch.
}

To był zbyt uproszczony przykład, ale chodzi o to, że ktoś czytający kod nie powinien się zastanawiać, dlaczego variablenie może być czymś innym niż 1 lub 2.


Jedynym przypadkiem, o którym mogę NIE myśleć, defaultjest sytuacja, w której przełącznik sprawdza coś, co jest dość oczywiste, że każdą inną alternatywę można z radością zignorować

switch(keystroke)
{
    case 'w':
        // move up
    case 'a':
        // move left
    case 's':
        // move down
    case 'd':
        // move right
    // no default really required here
}
Vanwaril
źródło
25
Twój przykład dotyczący naciśnięć klawiszy jest sprzeczny z resztą tego, co właśnie powiedziałeś. : | Myślę, że nieco więcej wyjaśnień miałoby sens, na przykład: w tym przypadku nie przejmujesz się ustawieniem domyślnym, ponieważ jeśli zostanie naciśnięty inny klawisz, nie obchodzi cię to, ale jeśli przekazana zmienna jest inna niż oczekiwano, zależy nam .
Andrew,
1
Jednocześnie, jeśli sprawdzasz parametr GET, możesz nie chcieć zgłaszania wyjątku za każdym razem, gdy użytkownik zmienia adres URL pobierania na coś, co jest niepoprawne: [
Andrew,
4
@Andrew WASD jest wspólny dla ruchów postaci w grach komputerowych. Nie potrzebujesz domyślnej akcji, ponieważ inne klawisze można przypisać do innych interakcji. W tego rodzaju przełączniku dobrze jest wywoływać funkcje ruchu.
jemiloii
13
Niektóre kompilatory ostrzegają przy włączaniu wyliczenia, jeśli przypadek jest pominięty, a dodanie domyślnego przypadku spowoduje wyłączenie tego ostrzeżenia. Biorąc pod uwagę, że według mnie jest to bardzo częste źródło błędów (zapominanie o aktualizacji przełącznika gdzieś po dodaniu wartości wyliczeniowej), wydaje się to bardzo dobrym powodem do pominięcia domyślnego przypadku.
rdb
3
@virusrocks Jeśli twój stan jest wyliczeniem, nadal możesz mieć nieoczekiwane wartości. Na przykład, jeśli dodasz nową wartość do wyliczenia i zapomnisz zaktualizować przełącznik. Ponadto w niektórych językach można przypisywać wartości całkowite do wyliczeń. W C ++ enum MyEnum { FOO = 0, BAR = 1 }; MyEnum value = (MyEnum)2;tworzy poprawną MyEnuminstancję, która nie jest równa FOOlub BAR. Jeśli którykolwiek z tych problemów spowoduje błąd w projekcie, domyślny przypadek pomoże bardzo szybko znaleźć błąd, często bez konieczności debugowania!
cdgraham
54

Nie.

Co się stanie, jeśli nie ma domyślnej akcji, kontekst ma znaczenie. Co zrobić, jeśli zależy Ci tylko na kilku wartościach?

Weź przykład czytania klawiszy w grze

switch(a)
{
   case 'w':
     // Move Up
     break;
   case 's':
     // Move Down
     break;
   case 'a':
     // Move Left
     break;
   case 'd':
     // Move Right
     break;
}

Dodawanie:

default: // Do nothing

To tylko strata czasu i bez żadnego powodu zwiększa złożoność kodu.

Jared Kells
źródło
14
Dobry przykład. Ale w rzeczywistości dodanie domyślnej klauzuli z prostym // do nothingkomentarzem wyjaśnia, że ​​jest to „ok”, jeśli nie wszystkie przypadki są uwzględnione, w przeciwieństwie do innych instrukcji switch, w których byłoby to „nie w porządku”.
The Nail
8
Nie. Kodowanie to nie tylko obsługa niektórych przypadków. Służy również jako dokument. Pisząc domyślnie: i komentując // Nie rób nic ani nic innego, czytelność kodu staje się lepsza.
JongAm Park,
22
Nie zgadzaj się z innymi komentarzami. Dodanie domyślnego // Nie rób nic, dodaje niewiele, chyba że nie wiesz, jak działa kod. Mogę spojrzeć na instrukcję przełącznika bez wartości domyślnej i wiedzieć, że wartością domyślną jest nic nie robić. Dodanie bałaganu nie czyni tego bardziej oczywistym.
Robert Noack,
Dokładnie. Jest to ta sama koncepcja przekazania par postów. Zajmujesz się tymi, na których ci zależy, a nie innymi.
Jimmy Kane,
2
@jybrd Jeśli nie ufasz, że poprzedni programista umyślnie pominął domyślną wartość domyślną, dlaczego miałbyś ufać, że poprawnie wprowadzili ją? Wartość domyślna może być przypadkowo pusta, tak samo prosta jak całkowite jej pominięcie. Komentarz nie jest konieczny w przypadku czegoś tak trywialnego. To jest bałagan kodu. Zamiast tego napisz pełne testy jednostkowe zasięgu, aby pokazać swoje zamiary. (Tylko moja opinia)
Robert Noack
45

NIE posiadanie domyślnego przypadku może w rzeczywistości być korzystne w niektórych sytuacjach.

Jeśli twoje przypadki przełączania są wartościami wyliczanymi, nie mając domyślnego przypadku, możesz otrzymać ostrzeżenie kompilatora, jeśli brakuje jakichkolwiek spraw. W ten sposób, jeśli nowe wartości wyliczeniowe zostaną dodane w przyszłości i zapomnisz dodać przypadki dla tych wartości w przełączniku, możesz dowiedzieć się o problemie w czasie kompilacji. Nadal powinieneś upewnić się, że kod podejmie odpowiednie działanie dla nieobsługiwanych wartości, na wypadek gdyby do typu wyliczenia została użyta niepoprawna wartość. Może to najlepiej sprawdzać się w prostych przypadkach, w których można powrócić w przypadku wyliczenia, a nie zepsuć.

enum SomeEnum
{
    ENUM_1,
    ENUM_2,
    // More ENUM values may be added in future
};

int foo(SomeEnum value)
{
    switch (value)
    {
    case ENUM_1:
        return 1;
    case ENUM_2:
        return 2;
    }
    // handle invalid values here
    return 0;
 }
Harlan Kassler
źródło
To ważna obserwacja! Dotyczy to jeszcze bardziej w Javie, ponieważ nie pozwala na rzutowanie int na wyliczenia.
Lii,
2
W twoim przykładzie możesz zgłosić wyjątek zamiast zwracać po instrukcji switch. W ten sposób można zarówno sprawdzić statycznie przez kompilator, jak i uzyskać wyraźny błąd, jeśli wydarzy się coś nieoczekiwanego.
Lii,
1
Zgadzam się. Na przykład w Swift przełącznik musi być wyczerpujący, inaczej pojawi się błąd kompilatora! Podanie wartości domyślnej tylko wycisza ten użyteczny błąd. Z drugiej strony, podanie wartości domyślnej, jeśli wszystkie przypadki są obsługiwane, powoduje wygenerowanie ostrzeżenia kompilatora (nieosiągalna instrukcja).
Andy,
1
Właśnie naprawiłem błąd, który spowodowałby ostrzeżenie od kompilatora, gdyby nie było wartości domyślnej: więc ogólnie Przełączanie zmiennych wyliczeniowych nie powinno mieć wartości domyślnej.
notan
42

Zawsze używałbym domyślnej klauzuli, bez względu na to, w jakim języku pracujesz.

Sprawy mogą się nie udać. Wartości nie będą zgodne z oczekiwaniami i tak dalej.

Brak chęci dołączenia domyślnej klauzuli oznacza, że ​​masz pewność, że znasz zestaw możliwych wartości. Jeśli uważasz, że znasz zestaw możliwych wartości, to jeśli wartość jest poza tym zestawem możliwych wartości, powinieneś zostać o tym poinformowany - z pewnością jest to błąd.

Dlatego powinieneś zawsze używać domyślnej klauzuli i zgłaszać błąd, na przykład w Javie:

switch (myVar) {
   case 1: ......; break;
   case 2: ......; break;
   default: throw new RuntimeException("unreachable");
}

Nie ma powodu, aby podawać więcej informacji niż tylko ciąg „nieosiągalny”; jeśli tak się stanie, będziesz musiał spojrzeć na źródło i wartości zmiennych itp., a wyjątek stacktrace będzie zawierał ten numer wiersza, więc nie musisz marnować czasu na pisanie większej ilości tekstu w komunikacie o wyjątku.

Adrian Smith
źródło
39
Wolałbym, throw new RuntimeException("myVar invalid " + myVar);ponieważ może to dać ci wystarczająco dużo informacji, aby znaleźć i naprawić błąd bez konieczności używania debuggera i sprawdzania zmiennych, co może być trudne, jeśli jest to rzadko występujący problem, który jest trudny do odtworzenia.
Chris Dodd
1
Co jeśli nie jest to warunek błędu, aby wartość nie pasowała do jednego z przypadków?
Gabe,
4
Jeśli nie jest to warunek błędu, nie byłoby potrzeby default:. Ale częściej nie, pomija się wartość domyślną, ponieważ uważa się, że myVarnigdy nie może mieć innej wartości niż wymienione; nie mogę jednak policzyć, ile razy byłem zaskoczony, kiedy zmienna ma wartość inną niż wartości, które „mogłaby” mieć. W takich przypadkach byłem wdzięczny za wyjątek, który sprawił, że zobaczyłem to od razu, zamiast powodować później jakiś inny błąd (trudniejszy do debugowania) lub błędną odpowiedź (może zostać przeoczony podczas testowania).
Adrian Smith
2
Zgadzam się z Adrianem. Zawodzą mocno i zawodzą wcześnie.
Slappy
Wolę rzucać AssertionErrorzamiast RuntimeException, ponieważ ta sytuacja jest bardzo podobna do stwierdzenia, że myVarjest to jedna z obsługiwanych wartości. Również szansa, że ​​ktoś złapie i połknie błąd, jest mniejsza.
Philipp Wendler,
15

W mojej firmie piszemy oprogramowanie dla rynku awioniki i obrony i zawsze dołączamy domyślną instrukcję, ponieważ WSZYSTKIE przypadki w instrukcji przełączania muszą być jawnie obsługiwane (nawet jeśli jest to tylko komentarz z napisem „Nie rób nic”). Nie możemy sobie pozwolić na to, że oprogramowanie po prostu źle się zachowuje lub po prostu ulega awarii na nieoczekiwanych (lub nawet naszym zdaniem niemożliwych) wartościach.

Można dyskutować, że domyślny przypadek nie zawsze jest konieczny, ale zawsze go wymagając, jest łatwo sprawdzany przez nasze analizatory kodu.

Kurt Pattyn
źródło
1
Mam ten sam wymóg, w którym pracuję; piszemy kod dla wbudowanych µControllerów, które muszą przejść surowe kontrole bezpieczeństwa i często podlegają EMI. Nieodpowiedzialne byłoby założenie, że zmienna wyliczona nigdy nie będzie miała wartości, która nie znajduje się na liście wyliczeń.
oosterwal
12

Czy instrukcja „switch” powinna zawsze zawierać klauzulę domyślną? Nie powinna ona zazwyczaj zawierają domyślne.

Dołączenie domyślnej klauzuli ma sens tylko wtedy, gdy jest coś do zrobienia, na przykład stwierdzenie błędu lub zachowanie domyślne. Zawarcie jednego „tylko dlatego, że” to program kultowy i nie ma żadnej wartości. Jest to odpowiednik „przełącznika”, który mówi, że wszystkie instrukcje „jeśli” powinny zawierać „inne”.

Oto trywialny przykład tego, gdzie nie ma to sensu:

void PrintSign(int i)
{
    switch (Math.Sign(i))
    {
    case 1:
        Console.Write("positive ");
        break;
    case -1:
        Console.Write("negative ");
        break;
    default: // useless
    }
    Console.Write("integer");
}

Jest to odpowiednik:

void PrintSign(int i)
{
    int sgn = Math.Sign(i);
    if (sgn == 1)
        Console.Write("positive ");
    else if (sgn == -1)
        Console.Write("negative ");
    else // also useless
    {
    }
    Console.Write("integer");
}
Gabe
źródło
Nie zgadzam się. IMHO, jedyną sytuacją, w której domyślnie nie powinno istnieć, jest to, że na jakiś czas nie ma możliwości, aby zestaw danych wejściowych mógł się zmienić i masz zapewnioną każdą możliwą wartość. Najprostszym przypadkiem, jaki mogę wymyślić, jest wartość logiczna z bazy danych, w której jedyne odpowiedzi (aż do zmiany SQL!) To prawda, fałsz i NULL. W każdym innym przypadku posiadanie wartości domyślnej z twierdzeniem lub wyjątkiem „Nie powinno się zdarzyć”! ma sens. Jeśli kod się zmieni, a masz jedną lub więcej nowych wartości, możesz upewnić się, że je kodujesz, a jeśli nie, to testy wybuchną.
Harper Shelby,
4
@ Harper: Twój przykład należy do kategorii „stwierdzenie warunku błędu”.
Gabe,
Twierdzę, że mój przykład jest normą i że niewielka liczba przypadków, w których można być w 100% pewnym, że każdy możliwy przypadek jest objęty, a domyślna nie jest potrzebna, jest wyjątkiem. Twoja odpowiedź jest sformułowana w sposób, który sprawia, że ​​brzmi (przynajmniej dla mnie) tak, jakby domyślna opcja „nic nie rób” zdarzyła się częściej niż nie.
Harper Shelby
@ Harper: OK, zmieniłem sformułowanie, aby wskazać, że sytuacja „nic nie robić” jest mniej powszechna.
Gabe,
4
Myślę, że default:w tych przypadkach kodyfikuje twoje założenia. Na przykład, czy sgn==0drukowanie jest prawidłowe integer(ani dodatnie, ani ujemne), czy jest to błąd? Trudno mi powiedzieć, kiedy czytam ten kod. Zakładam, że nie chcesz pisać zerow tym przypadku integer, i że programista założył, że sgnmoże być tylko -1 lub +1. Gdyby tak było, posiadanie tej opcji default:pozwoliłoby programiście wcześnie wychwycić błąd założenia i zmienić kod.
Adrian Smith
7

O ile wiem, odpowiedź brzmi „domyślnie” jest opcjonalna, mówiąc, że przełącznik musi zawsze zawierać wartość domyślną, to tak, jakby każde „if-elseif” zawierało „else”. Jeśli istnieje domyślna logika, powinna tam być instrukcja „default”, ale w przeciwnym razie kod mógłby kontynuować wykonywanie bez robienia czegokolwiek.

Gershon Herczeg
źródło
6

Posiadanie domyślnej klauzuli, gdy nie jest tak naprawdę potrzebne, to programowanie defensywne. Zazwyczaj prowadzi to do nadmiernie złożonego kodu z powodu zbyt dużej liczby kodów błędów. Ten kod obsługi i wykrywania błędów szkodzi czytelności kodu, utrudnia konserwację i ostatecznie prowadzi do większej liczby błędów niż rozwiązuje.

Uważam więc, że jeśli domyślna wartość nie zostanie osiągnięta - nie trzeba jej dodawać.

Pamiętaj, że „nie powinno zostać osiągnięte” oznacza, że ​​jeśli to osiągnęło, jest to błąd w oprogramowaniu - musisz przetestować wartości, które mogą zawierać niepożądane wartości ze względu na dane wprowadzone przez użytkownika itp.

Ophir Yoktan
źródło
3
Kolejny częsty powód nieoczekiwanych przypadków: inne części kodu są modyfikowane, być może lata później.
Hendrik Brummermann
Rzeczywiście, jest to bardzo niebezpieczne zachowanie. Przynajmniej dodałbym klauzulę assert (). Następnie można go łatwo wykryć, gdy wystąpi błąd.
Kurt Pattyn,
5

Powiedziałbym, że zależy to od języka, ale w C, jeśli włączasz typ wyliczania i obsługujesz każdą możliwą wartość, prawdopodobnie lepiej nie uwzględniać domyślnego przypadku. W ten sposób, jeśli dodasz dodatkowy znacznik enum później i zapomnisz dodać go do przełącznika, kompetentny kompilator wyświetli ostrzeżenie o brakującej sprawie.

Chris Dodd
źródło
5
„Każda możliwa wartość” jest mało prawdopodobna dla wyliczeń. Możesz wyrzucić liczbę całkowitą do wyliczenia, nawet jeśli ta konkretna wartość nie jest wyraźnie zdefiniowana. A który kompilator ostrzega o brakującej sprawie?
TrueWill,
1
@TrueWill: Tak, możesz używać jawnych rzutowań do pisania zaciemnionego kodu, którego nie można zrozumieć; aby napisać zrozumiały kod, należy tego unikać. gcc -Wall(rodzaj najniższego wspólnego mianownika kompetentnych kompilatorów) daje ostrzeżenia o wyliczeniach nieobsługiwanych w instrukcjach switch.
Chris Dodd,
4

Jeśli wiesz, że instrukcja switch będzie miała zawsze ściśle określony zestaw etykiet lub wartości, po prostu zrób to, aby objąć podstawy, w ten sposób zawsze uzyskasz prawidłowy wynik. Po prostu ustaw domyślną wartość na etykiecie, która programowo / logicznie być najlepszym trenerem dla innych wartości.

switch(ResponseValue)
{
    default:
    case No:
        return false;
    case Yes;
        return true;
}
deegee
źródło
Czy dwukropek jest defaultnadal wymagany? Czy robienie tego pozwala na specjalną składnię, która pozwoliła ci je pominąć?
Ponkadoodle,
Okrężnica jest wymagana, to była literówka, dziękuję za zwrócenie jej mojej uwagi.
deegee,
3

Przynajmniej nie jest to obowiązkowe w Javie. Według JLS mówi, że w jednym przypadku może występować jeden domyślny przypadek. Co oznacza, że ​​domyślna wielkość liter jest niedopuszczalna. Czasami zależy to również od kontekstu, w którym używasz instrukcji switch. Na przykład w Javie następujący blok przełączników nie wymaga domyślnej wielkości liter

private static void switch1(String name) {
    switch (name) {
    case "Monday":
        System.out.println("Monday");
        break;
    case "Tuesday":
        System.out.println("Tuesday");
        break;
    }
}

Jednak w poniższej metodzie, która spodziewa się zwrócić ciąg znaków, przydatna jest domyślna wielkość liter, aby uniknąć błędów kompilacji

    private static String switch2(String name) {
    switch (name) {
    case "Monday":
        System.out.println("Monday");
        return name;

    case "Tuesday":
        System.out.println("Tuesday");
        return name;

    default:
        return name;
    }
}

chociaż można uniknąć błędu kompilacji dla powyższej metody bez domyślnej wielkości liter, po prostu posiadając instrukcję return na końcu, ale podanie domyślnej wielkości liter sprawia, że ​​jest bardziej czytelna.

Shiva Kumar
źródło
3

Tak mówią niektóre (nieaktualne) wytyczne, takie jak MISRA C :

Warunkiem końcowej klauzuli domyślnej jest programowanie defensywne. Klauzula ta podejmuje odpowiednie działania lub zawiera odpowiedni komentarz wyjaśniający, dlaczego nie podjęto żadnych działań.

Ta rada jest nieaktualna, ponieważ nie jest oparta na obecnie obowiązujących kryteriach. Rażące pominięcie jest tym, co powiedział Harlan Kassler:

Pozostawienie domyślnej wielkości liter pozwala kompilatorowi na opcjonalne ostrzeżenie lub błąd, gdy widzi nieobsługiwaną wielkość liter. Weryfikacja statyczna jest przecież lepsza niż jakakolwiek kontrola dynamiczna, a zatem nie jest godną poświęcenia, gdy potrzebujesz kontroli dynamicznej.

Jak wykazał również Harlan, funkcjonalny odpowiednik domyślnego przypadku można odtworzyć po przełączeniu. Co jest trywialne, gdy każdy przypadek jest wczesnym powrotem.

Typową potrzebą kontroli dynamicznej jest szeroko rozumiana obsługa danych wejściowych. Jeśli wartość pochodzi spoza kontroli programu, nie można jej ufać.

Tutaj także Misra zajmuje stanowisko ekstremalnego programowania obronnego, o ile tylko niepoprawna wartość jest fizycznie reprezentowalna, należy ją sprawdzić, niezależnie od tego, czy program jest poprawny. Ma to sens, jeśli oprogramowanie musi być jak najbardziej niezawodne w przypadku wystąpienia błędów sprzętowych. Ale jak powiedział Ophir Yoktan, większość oprogramowania lepiej nie „radzić sobie” z błędami. Ta ostatnia praktyka jest czasami nazywana programowaniem obraźliwym .

użytkownik2394284
źródło
2

Powinieneś mieć ustawienie domyślne, aby przechwytywać nieoczekiwane wartości.

Jednak nie zgadzam się z Adrianem Smithem, że domyślny komunikat o błędzie powinien być czymś zupełnie bez znaczenia. Może istnieć nieobsługiwany przypadek, którego nie przewidziałeś (co jest w tym sensie), że użytkownik ostatecznie zobaczy, a wiadomość typu „nieosiągalny” jest całkowicie bezcelowa i nie pomaga nikomu w takiej sytuacji.

Przykładowo, ile razy miałeś zupełnie bezsensowny BSOD? Czy może śmiertelny wyjątek @ 0x352FBB3C32342?

John Hunt
źródło
np. pamiętaj, że nie tylko programista zawsze widzi komunikaty o błędach. Żyjemy w prawdziwym świecie, ludzie popełniają błędy.
John Hunt
2

Jeśli wartość przełącznika ( przełącznik (zmienna )) nie może osiągnąć wielkości domyślnej, wówczas wielkość domyślna nie jest wcale potrzebna. Nawet jeśli zachowamy domyślny przypadek, nie zostanie on w ogóle wykonany. To martwy kod.

shiju
źródło
2

Jest to opcjonalna „konwencja” kodowania. W zależności od zastosowania jest to, czy jest to potrzebne. Osobiście uważam, że jeśli nie jest to potrzebne, nie powinno tam być. Po co uwzględniać coś, z czego użytkownik nie skorzysta lub do którego nie dotrze?

Jeśli możliwości przypadków są ograniczone (tj. Wartość logiczna), klauzula domyślna jest zbędna !

Danny Mahoney
źródło
2

Jeśli w switchinstrukcji nie ma domyślnego przypadku , zachowanie może być nieprzewidywalne, jeśli taki przypadek powstanie w pewnym momencie, czego nie można było przewidzieć na etapie opracowywania. Dobrą praktyką jest dołączanie defaultskrzynki.

switch ( x ){
  case 0 : { - - - -}
  case 1 : { - - - -}
}

/* What happens if case 2 arises and there is a pointer
* initialization to be made in the cases . In such a case ,
* we can end up with a NULL dereference */

Taka praktyka może spowodować błąd, taki jak zerowanie wartości NULL , wyciek pamięci, a także inne rodzaje poważnych błędów .

Na przykład zakładamy, że każdy warunek inicjuje wskaźnik. Ale jeśli defaultma się pojawić przypadek i jeśli nie zainicjujemy w tym przypadku, istnieje każda możliwość uzyskania wyjątku ze wskaźnikiem zerowym. Dlatego zaleca się stosowanie defaultinstrukcji case, nawet jeśli może być trywialna.

Trevor
źródło
2

Domyślny przypadek może nie być konieczny w przełączniku używanym przez enum. gdy przełącznik zawierał całą wartość, domyślny przypadek nigdy się nie wykona. W takim przypadku nie jest to konieczne.

OK
źródło
1

Zależy od tego, jak działa przełącznik w danym języku, jednak w większości języków, gdy żadna wielkość liter nie jest dopasowana, wykonanie przechodzi przez instrukcję przełącznika bez ostrzeżenia. Wyobraź sobie, że oczekiwałeś jakiegoś zestawu wartości i obsługiwałeś je w przełączniku, ale otrzymujesz inną wartość na wejściu. Nic się nie dzieje i nic nie wiesz. Jeśli złapałeś sprawę domyślnie, wiedziałbyś, że coś było nie tak.

Gabriel Ščerbák
źródło
1

Nie zgadzam się z najczęściej głosowaną odpowiedzią Vanwaril powyżej.

Każdy kod zwiększa złożoność. W tym celu należy również wykonać testy i dokumentację. Dlatego zawsze dobrze jest, jeśli możesz programować przy użyciu mniejszej ilości kodu. Uważam, że używam domyślnej klauzuli dla niewyczerpujących instrukcji przełączania, podczas gdy nie używam domyślnej klauzuli dla wyczerpujących instrukcji przełączania. Aby mieć pewność, że zrobiłem to dobrze, używam narzędzia do analizy kodu statycznego. Przejdźmy więc do szczegółów:

  1. Niewyczerpujące instrukcje przełączania: zawsze powinny mieć wartość domyślną. Jak sama nazwa wskazuje, są to stwierdzenia, które nie obejmują wszystkich możliwych wartości. Może to również nie być możliwe, np. Instrukcja switch na wartości całkowitej lub na String. W tym miejscu chciałbym użyć przykładu Vanwarila (należy wspomnieć, że myślę, że użył tego przykładu, aby sformułować błędną sugestię. Używam go tutaj, aby stwierdzić odwrotnie -> Użyj domyślnej instrukcji):

    switch(keystroke)
    {
      case 'w':
        // move up
      case 'a':
        // move left
      case 's':
        // move down
      case 'd':
        // move right
      default:          
        // cover all other values of the non-exhaustive switch statement
    }
    

    Gracz może nacisnąć dowolny inny klawisz. Wtedy nie moglibyśmy nic zrobić (może to być pokazane w kodzie po prostu poprzez dodanie komentarza do domyślnej sprawy) lub powinno na przykład wydrukować coś na ekranie. Ten przypadek jest istotny, ponieważ może się zdarzyć.

  2. Wyczerpujące instrukcje przełączania: te instrukcje przełączania obejmują wszystkie możliwe wartości, np. Instrukcje przełączania w wyliczeniu typów systemów ocen. Podczas opracowywania kodu za pierwszym razem łatwo jest uwzględnić wszystkie wartości. Ponieważ jednak jesteśmy ludźmi, istnieje niewielka szansa, aby o nich zapomnieć. Dodatkowo, jeśli dodasz wartość wyliczenia później, tak że wszystkie instrukcje przełączników muszą zostać dostosowane, aby stały się wyczerpujące, ponownie otwiera ścieżkę do błędu piekło. Prostym rozwiązaniem jest narzędzie do analizy kodu statycznego. Narzędzie powinno sprawdzić wszystkie instrukcje przełączników i sprawdzić, czy są wyczerpujące lub czy mają wartość domyślną. Oto przykład wyczerpującej instrukcji switch. Najpierw potrzebujemy wyliczenia:

    public enum GradeSystemType {System1To6, SystemAToD, System0To100}
    

    Następnie potrzebujemy zmiennej tego wyliczenia jak GradeSystemType type = ... . Wyczerpująca instrukcja przełącznika wyglądałaby wtedy następująco:

    switch(type)
    {
      case GradeSystemType.System1To6:
        // do something
      case GradeSystemType.SystemAToD:
        // do something
      case GradeSystemType.System0To100:
        // do something
    }
    

    Więc jeśli rozszerzymy GradeSystemType , na przykład, System1To3narzędzie do analizy kodu statycznego powinno wykryć, że nie ma domyślnej klauzuli, a instrukcja switch nie jest wyczerpująca, więc oszczędzamy.

Jeszcze jedna dodatkowa rzecz. Jeśli zawsze używamy defaultklauzuli, może się zdarzyć, że narzędzie do analizy kodu statycznego nie będzie w stanie wykryć wyczerpujących lub niewyczerpujących instrukcji przełączania, ponieważ zawsze wykrywa defaultklauzulę. Jest to bardzo złe, ponieważ nie zostaniemy poinformowani, jeśli rozszerzymy wyliczenie o inną wartość i zapomnimy dodać go do jednej instrukcji switch.

Popiół
źródło
0

Czy instrukcje switch powinny zawsze zawierać domyślną klauzulę? Przypadki przełączania nie mogą istnieć bez domyślnego przypadku, w przypadku domyślnego przypadku przełącznika wyzwoli wartość przełączenia switch(x)w tym przypadku x, gdy nie będzie on zgodny z żadnymi innymi wartościami przypadków.

Nisal Edu
źródło
0

Uważam, że jest to dość specyficzne dla języka, aw przypadku C ++ drobna zaleta dla typu klasy enum . Co wydaje się bezpieczniejsze niż tradycyjne Cum. ALE

Jeśli spojrzysz na implementację std :: byte, to coś takiego:

enum class byte : unsigned char {} ;

Źródło: https://en.cppreference.com/w/cpp/language/enum

A także rozważ to:

W przeciwnym razie, jeśli T jest typem wyliczenia, który ma albo zakres, albo nie jest ustalony ze stałym typem bazowym, i jeśli lista stężeń inicjowanych ma tylko jeden inicjator, i jeśli konwersja z inicjalizatora na typ bazowy nie zawęża się, i jeśli inicjalizacja to inicjalizacja listy bezpośredniej, a następnie inicjowanie wyliczenia w wyniku konwersji inicjalizatora na typ bazowy.

(od C ++ 17)

Źródło: https://en.cppreference.com/w/cpp/language/list_initialization

Jest to przykład klasy wyliczającej reprezentującej wartości, które nie są zdefiniowanymi modułami wyliczającymi. Z tego powodu nie można całkowicie zaufać wyliczeniom. W zależności od aplikacji może to być ważne.

Jednak bardzo podoba mi się to, co @Harlan Kassler powiedział w swoim poście i sam zacznę stosować tę strategię w niektórych sytuacjach.

Tylko przykład niebezpiecznej klasy enum:

enum class Numbers : unsigned
{
    One = 1u,
    Two = 2u
};

int main()
{
    Numbers zero{ 0u };
    return 0;
}
David Ledger
źródło