Czy konieczne jest dodanie domyślnej skrzynki podczas korzystania ze skrzynek przełączników?

41

Podczas niedawnego przeglądu kodu poproszono mnie o umieszczenie defaultskrzynek we wszystkich plikach wszędzie tam, gdzie switchużywany jest blok, nawet jeśli nie ma nic do zrobienia default. Oznacza to, że muszę włożyć defaultsprawę i nic w niej nie pisać.

Czy to jest właściwe? W jakim celu miałoby to służyć?

Ankit
źródło
9
To zależy od twojego ... Mam na myśli styl kodu przełożonego.
SD
1
Myślę, że są nawet przypadki, w których nie chcesz skrzynki domyślnej. Rozważ zamianę wyliczenia. Twój przełącznik określa wielkość liter dla wszystkich możliwych wartości. Teraz dodanie wartości do wyliczenia powinno skutkować dodaniem wielkości liter w tym przełączniku. Jeśli tego nie zrobiłeś, może to być błąd, ponieważ chcesz coś zrobić dla tej nowej wartości wyliczeniowej, ale kod nie. Twój kompilator może cię ostrzec (myślę, nie wiem, o Java) o tym, ale tylko jeśli nie nie umieścić domyślny przypadek. Alternatywnie możesz wydrukować ostrzeżenie w domyślnym przypadku, gdy masz pewność, że kod nigdy go nie osiągnie.
leem
6
Ja i moi przyjaciele nazywamy to przypadkiem „wyjątku programisty” - gdy zdarza się coś w kodzie, o którym wiesz, że nigdy nie powinno się wydarzyć (z przyczyn logicznych / kodu), dodajemy „zgłoszony wyjątek programisty”. W ten sposób, jeśli kiedykolwiek tak się stanie, zostanie zgłoszony wyjątek dla programistów i przynajmniej wiemy, gdzie coś poszło nie tak.
Marco

Odpowiedzi:

31

Wydaje się, że istnieją trzy przypadki, w których defaultoświadczenie nie jest konieczne:

  1. nie pozostały żadne inne przypadki, ponieważ istnieje ograniczony zestaw wartości, które wchodzą do switch case. Ale to może się zmieniać z czasem (umyślnie lub przypadkowo) i dobrze byłoby, default casegdyby coś się zmieniło _ możesz zalogować lub ostrzec użytkownika o złej wartości.

  2. wiesz, jak i gdzie switch casezostanie użyty oraz jakie wartości go wprowadzą. Ponownie może się to zmienić i może być konieczne dodatkowe przetwarzanie.

  3. inne przypadki nie wymagają specjalnego przetwarzania. Jeśli tak jest, myślę, że zostaniesz poproszony o dodanie default case, ponieważ jest to akceptowany styl kodowania i sprawia, że ​​kod jest bardziej czytelny.

Pierwsze dwa przypadki opierają się na założeniach. Tak więc (zakładając, że pracujesz w niezbyt małym zespole, ponieważ masz regularne przeglądy kodu), nie możesz sobie pozwolić na takie założenia. Nie wiesz, kto będzie pracował z twoim kodem lub nawoływał do funkcji / wywoływania metod w twoim kodzie. Podobnie może być konieczna praca z kodem innej osoby. Posiadanie tego samego stylu kodowania ułatwi radzenie sobie z czyimś kodem (w tym z Twoim).

superM
źródło
14
W przypadku 1 i 2 domyślnym przypadkiem powinien być błąd. Jeśli twój styl kodowania zawsze używa domyślnego, zrób to, ale spraw, aby emitował błąd w 1 i 2. Jest to możliwe, powinno być emitowane w czasie kompilacji, ale nie zawsze będzie to możliwe, jeśli kiedykolwiek w java. Myślę, że przynajmniej ważne jest, aby użytkownik otrzymał wiadomość, że „strzelasz sobie w stopę, przekazując tę ​​wartość”
martiert
11
Przypadek domyślny jest zawsze dobrym pomysłem, ponieważ na przykład w przypadku wyliczenia, jeśli ktoś doda nowy wpis do wyliczenia, przypadek domyślny powinien zrobić coś podobnego, throw new IllegalStateException("Unrecognised case "+myEnum)co pokazuje, gdzie i jaki jest błąd.
Bogaty
Czy to oznacza, że ​​zawsze powinieneś mieć elseklauzulę po każdym, if/else ifnawet jeśli wiesz, że to nie powinno się zdarzyć? Nie wydaje mi się to dobrym pomysłem ...
jadkik94 9.01.13
2
Jeśli mogę dodać anegdotę z mojej pracy: miałem metodę fabryczną, która używała wyliczenia do utworzenia niektórych klas. Zostawiłem instrukcje, że ilekroć dodajesz nową klasę do tego projektu, musisz dodać wartość do wyliczenia i przypadek zmiany. Oczywiście tydzień po tym, jak zamknąłem kod, przestaje on działać, z jednym z wyjątków, którego nigdy nie należy zgłaszać. Podchodzę do programisty i pytam go: „czy dodałeś skrzynkę do przełącznika?” i na pewno nie zrobił tego. Tego dnia zmieniłem wyjątek i powiedziałem: „jeśli dostaniesz tę wiadomość, obwiniaj ostatniego programistę, który dotknął kodu”.
Ziv
28

Czy to jest właściwe? W jakim celu miałoby to służyć?

Często zdarza się, że standardy kodowania firm wymagają domyślnego przypadku dla wszystkich switchinstrukcji. Jednym z powodów jest to, że ułatwia czytelnikom znalezienie końca switch. Innym, prawdopodobnie lepszym powodem jest to, że sprawia, że ​​zastanawiasz się przez chwilę, co powinien zrobić Twój kod, gdy warunek nie spełnia twoich oczekiwań. Bez względu na powód wymagania, jeśli jest to standard firmy, należy go przestrzegać, chyba że istnieje uzasadniony powód, aby tego nie robić.

Jeśli uważasz, że switchobejmuje to przypadki dla każdego możliwego warunku, dobrze jest umieścić assertoświadczenie w przypadku domyślnym. W ten sposób, gdy ktoś zmieni kod i niechcący doda warunek, którego switchnie obejmuje Twój , uderzy asserti zda sobie sprawę, że musi zająć się tą częścią kodu.

Jeśli spełniasz switchtylko kilka możliwych warunków, ale dla innych nie musisz robić nic specjalnego, możesz pozostawić domyślną skrzynkę pustą. W takim przypadku dobrym pomysłem jest dodanie komentarza wskazującego, że przypadek domyślny jest celowo pusty, ponieważ warunki, które go dotyczą, nie wymagają żadnej pracy.

Caleb
źródło
1
co to jest assert?
FLY
1
@FLY Jest to słowo kluczowe w Javie, a zwykle makro w C, C ++ itp. Tak czy inaczej, do sprawdzenia, czy pewne założenie pozostaje prawdziwe i rzucenie i wyjątek lub w inny sposób spowodować błąd, jeśli tak nie jest, stosuje się twierdzenie.
Caleb
Mój poprzedni komentarz zredagowałem linkiem do wikipedii
FLY
Nie powinieneś zostawiać pustej skrzynki. Jeśli twój przełącznik nie obejmuje wszystkich możliwych warunków, powinieneś potwierdzić pozostałe warunki w domyślnej sprawie z tego samego powodu, dla którego zasugerowałeś dodanie aser w domyślnej sprawie w drugim akapicie.
rold2007
12

Jeśli „przełączysz” na czysty typ wyliczenia, niebezpiecznym jest mieć domyślną rezerwę. Gdy później dodasz wartości do typu wyliczenia, kompilator podświetli przełączniki z nowymi nieobjętymi wartościami. Jeśli masz tam domyślną klauzulę, kompilator pozostanie cichy i możesz go przegapić.

Artur
źródło
1
+1 za wychwycenie problemu w czasie kompilacji, a nie w czasie wykonywania.
SylvainD
Zastanawiam się, jak całkowicie błędna odpowiedź jest głosowana i akceptowana. Szybkie przeglądanie w Google pokazuje, że kompilator Java ma takie ostrzeżenie. Dlaczego, do cholery, czekacie na błąd w czasie wykonywania, ludzie?
Artur
Nie wiedziałem o tym zachowaniu. Mój przykładowy kod ideone ideone.com/WvytWq sugeruje inaczej. Zakładając, że uważasz, że to prawda, co się stanie, jeśli ktoś doda wartości do typu wyliczenia, ale nie przekompilujesz kodu?
emory
1
Oczywiście nowe wartości nie są obsługiwane przez instrukcję switch bez domyślnej wielkości liter. Ale jeśli nie dokonasz ponownej kompilacji swoich implementacji przy zmianie interfejsów, twój system kompilacji jest naprawdę zepsuty.
Artur
9

Pod wieloma względami to pytanie jest takie samo, jak często zadawane pytanie Czy potrzebuję elseklauzuli na końcu drabiny if/ else ifobejmującej każdą opcję .

Odpowiedź brzmi składniowo: nie, nie. Ale jest jednak ...

defaultPunkt może być nie do (co najmniej) dwóch powodów:

  1. Jako program obsługi błędów - na pewno nigdy nie powinien się tu dostać! to roszczenie, które można złożyć, ale co jeśli tak będzie? Uszkodzenie danych lub, co gorsza, brak sprawdzania poprawności danych to drogi do niepowodzenia programu, jeśli nie zostaną właściwie uwięzione. W takim przypadku nie powinna to być pusta klauzula!
  2. Projekt / pokrycie kodu - nawet w najprostszej formie schematu blokowego istnieją dwie drogi od instrukcji if i zawsze otherwiseod przypadku. Nie ma żadnego powodu, aby nie włączać ich do kodu źródłowego.

Moja filozofia jest zawsze bardzo prosta - oceń najgorszy możliwy scenariusz z dwóch opcji i wybierz najbezpieczniejszy. W przypadku pustego elselub defaultklauzuli najgorszych przypadków są opcje:

  • Dołącz je: dodatkowe trzy lub cztery wiersze „nadmiarowego” kodu.
  • Nie dołączaj ich: nieuczciwe dane lub nieoczekiwany stan nie są uwięzione, co może spowodować awarię programu.

Overdramatic? Może ... ale wtedy moje oprogramowanie może potencjalnie zabijać ludzi, jeśli pójdzie nie tak. Wolałbym nie ryzykować.


Tak na marginesie, że wytyczne MISRA-C {zobaczyć profil dla przynależności} polecić defaultklauzuli dla każdegoswitch

Andrzej
źródło
Nie tylko na końcu drabiny. Pytanie brzmi: czy potrzebuję elsepo if. Ale jak powiedziałeś, nie, nie jest.
ott--
Jako program obsługi błędów sugeruję nieco zmodyfikowaną formę, która unika pustych wartości domyślnych. Jeśli chodzi o zabijanie ludzi, nie sądzę, żeby to miało znaczenie - ludzie będą tak samo martwi, niezależnie od tego, czy masz domyślną klauzulę, ale ułatwi to ustalenie, dlaczego umarli.
emory
Dobra uwaga, @emory - to nie było bardzo jasne, czy to ... zostało zredagowane :)
Andrew
6

Java nie zmusza cię do posiadania „domyślnej” instrukcji, ale dobrą praktyką jest posiadanie jej przez cały czas, nawet jeśli kod nigdy nie zostanie osiągnięty (w tej chwili). Oto kilka powodów:

Dzięki nieosiągalnej klauzuli domyślnej pokazujesz czytelnikowi swojego kodu, że wziąłeś pod uwagę wartości i wiesz, co robisz. Zezwalasz także na przyszłe zmiany, powiedzmy na przykład: dodana jest nowa wartość wyliczeniowa, przełącznik nie powinien po cichu ignorować nowej wartości; zamiast tego możesz rzucić tam wyjątek lub zrobić coś innego.

Aby złapać nieoczekiwaną wartość (na wypadek, gdy nie włączasz wyliczenia), która jest przekazywana - może ona być na przykład większa lub mniejsza niż oczekiwana.

Do obsługi „domyślnych” akcji - gdzie przełączniki służą do specjalnego zachowania. Na przykład zmienną można zadeklarować poza przełącznikiem, ale nie można jej zainicjować, a każdy przypadek inicjuje ją na coś innego. Wartość domyślna w tym przypadku może zainicjować ją na wartość domyślną, aby kod natychmiast po przełączniku nie powodował błędu / nie zgłosił wyjątku.

Nawet jeśli zdecydujesz się nie umieszczać niczego w ustawieniach domyślnych (bez wyjątków, rejestrowania itp.), To nawet komentarz z informacją, że uważasz, że domyślnie nigdy się nie zdarzy, może pomóc w czytelności twojego kodu; ale sprowadza się to do osobistych preferencji.

Deco
źródło
2

Dodałbym również, że zależy to od filozofii używanego języka. Na przykład w Erlang, gdzie jest powszechne podejście do „pozwól mu się zawiesić”, nie zdefiniowałbyś domyślnego przypadku, ale pozwoliłbyś, aby twoja caseinstrukcja wygenerowała błąd, jeśli wystąpi sytuacja, która nie jest zaspokojona.

Rodrigue
źródło
0

Zawsze powinieneś mieć domyślne, chyba że udowodniłeś i trwale objąłeś wszystkie sprawy. To nic nie kosztuje, a to ubezpieczenie od nieprzestrzegania instrukcji zmiany w ewoluującym programie.

Jeśli naprawdę jesteś pewien, że nie obchodzą Cię żadne inne przypadki, domyślnie: break; // nie przejmuj się, ale domyślna wartość domyślna powinna być podobna do domyślnej: wyrzuć nowy Błąd („nieoczekiwana wartość”);

Podobnie, nigdy nie powinieneś „odrzucać” nietrywialnej konstrukcji przypadku bez dodawania komentarza do efektu, który zamierzasz pominąć. Java pomyliła się na etapie projektowania.

ddyer
źródło
-2

Rozważać

enum Gender
{
       MALE ,
       FEMALE ;
}

i

void run ( Gender g )
{
      switch ( g )
      {
           case MALE :
                 // code related to males
                 break ;
           default :
                 assert Gender . FEMALE . equals ( g ) ;
                 // code related to females
                 break ;
      }
}

Argumentowałbym, że jest to lepsze niż posiadanie sprawy MALE, sprawy FEMALE i sprawy domyślnej, która zgłasza wyjątek.

Podczas fazy testowania komputer sprawdzi, czy g jest KOBIETĄ. Podczas produkcji kontrola zostanie pominięta. (Tak, mikrooptymalizacja!)

Co ważniejsze, utrzymasz swój cel 100% pokrycia kodu. Jeśli napisałeś przypadek domyślny, który zgłasza wyjątek, jak byś go kiedykolwiek przetestował?

emory
źródło
1
1. Nie ma potrzeby mikrooptymalizacji - eliminacja martwego kodu jest stosowana w kompilatorach od około 30 lat i zostanie wyeliminowana przez JIT. 2. 100% pokrycie kodu zwykle nie jest uważane za ważne (z jednej strony 100% pokrycie może nie wychwycić wszystkich przypadków krawędzi, z drugiej żaden test nie złapie linii assert_not_reached na odpowiednim programie). W takim przypadku pozostawienie domyślnej wielkości nie spowoduje błędu kompilatora. Z drugiej strony - w jaki sposób sprawdziłbyś, czy aser działa (przesuwasz problem i ukrywasz go o 100% zasięgu, zamiast mieć tę samą linię „to się nie stanie, obiecuję”).
Maciej Piechotka
1
3. Kiedy Gender . FEMALE . equals ( FEMALE ) ;ocenia się false? Miałeś na myśli, Gender.FEMALE.equals (g)ale ponieważ możesz polegać na tym, że odniesienia do stabilizacji są stabilne, możesz po prostu pisać g == Gender.FEMALE. 4. (Osobista opinia) taki kod jest znacznie trudniejszy do odczytania i spowoduje nieco więcej mylących błędów.
Maciej Piechotka
@MaciejPiechotka dobry połów na temat g. Tak, mikrooptymalizacja nie jest korzyścią, ale nie jest też wadą. 100% pokrycia kodu jest korzyścią, która jest jednym z twoich celów, a twoje narzędzie do pokrycia kodu nie sprawdza stwierdzeń (czego nie powinno).
emory
Więc dlaczego miałby to być jeden z moich celów? „Interesującą” część należy przetestować dla wszystkich przypadków na krawędziach (niezależnie od tego, czy są one mapowane do linii kodu), a „nudne” części można po prostu pominąć, aby zaoszczędzić czas na testowaniu. Pokrycie kodu IMHO jest złym miernikiem testów.
Maciej Piechotka