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.
Czy istnieje uzasadniony powód, aby zawsze dołączać domyślne oświadczenie?
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?
default
switch-statement
tttppp
źródło
źródło
Odpowiedzi:
Obudowy przełączników powinny prawie zawsze mieć
default
obudowę.Powody, dla których warto użyć
default
1. Aby „złapać” nieoczekiwaną wartość
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ę.
To był zbyt uproszczony przykład, ale chodzi o to, że ktoś czytający kod nie powinien się zastanawiać, dlaczego
variable
nie może być czymś innym niż 1 lub 2.Jedynym przypadkiem, o którym mogę NIE myśleć,
default
jest sytuacja, w której przełącznik sprawdza coś, co jest dość oczywiste, że każdą inną alternatywę można z radością zignorowaćźródło
enum MyEnum { FOO = 0, BAR = 1 }; MyEnum value = (MyEnum)2;
tworzy poprawnąMyEnum
instancję, która nie jest równaFOO
lubBAR
. 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!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
Dodawanie:
To tylko strata czasu i bez żadnego powodu zwiększa złożoność kodu.
źródło
// do nothing
komentarzem 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”.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ć.
źródło
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:
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.
źródło
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.default:
. Ale częściej nie, pomija się wartość domyślną, ponieważ uważa się, żemyVar
nigdy 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).AssertionError
zamiastRuntimeException
, ponieważ ta sytuacja jest bardzo podobna do stwierdzenia, żemyVar
jest to jedna z obsługiwanych wartości. Również szansa, że ktoś złapie i połknie błąd, jest mniejsza.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.
źródło
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:
Jest to odpowiednik:
źródło
default:
w tych przypadkach kodyfikuje twoje założenia. Na przykład, czysgn==0
drukowanie jest prawidłoweinteger
(ani dodatnie, ani ujemne), czy jest to błąd? Trudno mi powiedzieć, kiedy czytam ten kod. Zakładam, że nie chcesz pisaćzero
w tym przypadkuinteger
, i że programista założył, żesgn
może być tylko -1 lub +1. Gdyby tak było, posiadanie tej opcjidefault:
pozwoliłoby programiście wcześnie wychwycić błąd założenia i zmienić kod.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.
źródło
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.
źródło
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.
źródło
gcc -Wall
(rodzaj najniższego wspólnego mianownika kompetentnych kompilatorów) daje ostrzeżenia o wyliczeniach nieobsługiwanych w instrukcjach switch.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.
źródło
default
nadal wymagany? Czy robienie tego pozwala na specjalną składnię, która pozwoliła ci je pominąć?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
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
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.
źródło
Tak mówią niektóre (nieaktualne) wytyczne, takie jak MISRA C :
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 .
źródło
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?
źródło
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.
źródło
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 !
źródło
Jeśli w
switch
instrukcji 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łączaniedefault
skrzynki.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
default
ma 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ę stosowaniedefault
instrukcji case, nawet jeśli może być trywialna.źródło
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.
źródło
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.
źródło
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:
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):
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ć.
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:
Następnie potrzebujemy zmiennej tego wyliczenia jak
GradeSystemType type = ...
. Wyczerpująca instrukcja przełącznika wyglądałaby wtedy następująco:Więc jeśli rozszerzymy
GradeSystemType
, na przykład,System1To3
narzę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
default
klauzuli, 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 wykrywadefault
klauzulę. Jest to bardzo złe, ponieważ nie zostaniemy poinformowani, jeśli rozszerzymy wyliczenie o inną wartość i zapomnimy dodać go do jednej instrukcji switch.źródło
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.źródło
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:
Źródło: https://en.cppreference.com/w/cpp/language/enum
A także rozważ to:
Ź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:
źródło