2009-03-06 7 views
16

Załóżmy, że mamy metodę, która akceptuje wartość wyliczenia. Po sprawdzeniu, że ta wartość jest poprawna, metoda ta jest w stanie przekroczyć możliwe wartości. Pytanie brzmi: jaka jest preferowana metoda obsługi niespodziewanych wartości po sprawdzeniu zakresu wartości?Jaka jest preferowana metoda obsługi nieoczekiwanych wartości wyliczania?

Na przykład:

enum Mood { Happy, Sad } 

public void PrintMood(Mood mood) 
{ 
    if (!Enum.IsDefined(typeof(Mood), mood)) 
    { 
     throw new ArgumentOutOfRangeException("mood"); 
    } 

    switch (mood) 
    { 
     case Happy: Console.WriteLine("I am happy"); break; 
     case Sad: Console.WriteLine("I am sad"); break; 
     default: // what should we do here? 
    } 

Jaka jest preferowana metoda zajmuje się sprawą default?

  • komentarz jak // can never happen
  • Debug.Fail() (lub Debug.Assert(false))
  • throw new NotImplementedException() (lub innego wyjątku)
  • jakiś inny sposób nie myślałem o
+0

Zamieszczam odpowiedź z moją własną opinią. Jeśli masz inną opinię lub inny przypadek, który ją obsługuje, podziel się swoją wiedzą. Celem pytania jest zebranie pomysłów na ten temat, aby pomóc innym ludziom (i mnie) zmierzyć się z tą sytuacją. –

+0

dlaczego został oznaczony jako java? – OscarRyz

+0

@Oscar: Powinno to być niezależne od języka pytanie IMHO. Ludzie mogą znaleźć odpowiedź na oba języki tutaj. –

Odpowiedz

10

Wolę throw new NotImplementedException("Unhandled Mood: " + mood). Chodzi o to, że wyliczenie może się zmienić w przyszłości, a ta metoda może nie być odpowiednio aktualizowana. Rzucanie wyjątku wydaje się być najbezpieczniejszą metodą.

Nie podoba mi się metoda Debug.Fail(), ponieważ metoda może być częścią biblioteki, a nowe wartości mogą nie być testowane w trybie debugowania. Inne aplikacje korzystające z tej biblioteki mogą w tym przypadku napotkać dziwne zachowania środowiska wykonawczego, natomiast w przypadku wyrzucenia wyjątku błąd zostanie natychmiast rozpoznany.

Uwaga: NotImplementedException istnieje w commons.lang.

+0

To powinno zostać zmodyfikowane w oryginalnym wpisie. –

+1

@SnOrfus: Dlaczego? To jest całkowicie poprawna odpowiedź. –

+0

Jak już powiedziałem w mojej odpowiedzi, może to być poprawne, ale źle jest być okropnym OO ... –

1

Możliwe jest śledzenie domyślnego wywoływania wartości przekazanego wyliczenia. Zgłaszanie wyjątków jest OK, ale w dużej aplikacji będzie kilka miejsc, w których twój kod nie dba o inne wartości wyliczenia.
Tak więc, chyba że masz pewność, że kod zamierza obsłużyć wszystkie możliwe wartości wyliczenia, musisz wrócić później i usunąć wyjątek.

7

W Javie standardowym sposobem jest rzucać AssertionError, z dwóch powodów:

  1. Gwarantuje to, że nawet jeśli asserts są wyłączone, zostanie zgłoszony błąd.
  2. Twierdzisz, że nie ma innych wartości wyliczeniowych, więc AssertionError udokumentuje twoje założenia lepiej niż NotImplementedException (której Java i tak nie ma).
+0

Ale AssertionError rozszerza java.lang.Error, a nie java.lang.Exception. Oznaczałoby to, że inne warstwy kodu nie będą miały szansy na wychwycenie wyjątku i odzyskanie go. (Technicznie rzecz biorąc mogą, ale praktycznie większość ludzi nie (i nie powinna) złapać java.lang.Error.) –

+0

Jeśli już sprawdziłeś, czy jest poprawny, AssertionError nigdy nie powinien się zdarzyć (to znaczy, przecież mówisz). Jeśli tak się stanie, wszystko jest tak źle, że myślę, że nie ma sensu próbować go złapać. Ale widzę, że czasami może to być przydatne, aby było to łatwe do odszyfrowania. –

+0

Zobacz także dyskusję w tym pytaniu: http://stackoverflow.com/questions/398953/java-what-is-the-preferred-throwable-to-use-in-a-private-utility-class-construct –

2

Dla prawie wszystkich instrukcji switch w moim kodu bazowego, mam następujący przypadku domyślnym

switch(value) { 
... 
default: 
    Contract.InvalidEnumValue(value); 
} 

Sposób rzuci wyjątek wyszczególnieniem wartości wyliczenia w punkcie został wykryty błąd.

public static void InvalidEnumValue<T>(T value) where T: struct 
{ 
    ThrowIfFalse(typeof(T).IsEnum, "Expected an enum type"); 
    Violation("Invalid Enum value of Type {0} : {1}", new object[] { typeof(T).Name, value }); 
} 
+0

Jednak wartość może być prawidłowa, mimo że metoda nie została zaktualizowana, aby to odzwierciedlić. Myślę, że komunikat "Nieprawidłowa wartość" jest tu trochę mylący. –

+0

@Hosam, ale kod został napisany nie spodziewając się tej wartości, dlatego wybrałem nazwę. "Niespodziewana wartość" działałaby tak dobrze, jak przypuszczam, ale ma uchwycić, że wartość nie jest prawdziwie tym, co programista zamierzał osiągnąć. – JaredPar

+0

@JaredPar: Wystarczająco fair. Myślałem o bardziej "nieobsługiwanej wartości", co wyraźnie pokazuje, że jest to nieobsługiwany przypadek (IMHO). –

0

Nazwij to opinią lub preferencją, ale ideą enum jest to, że reprezentuje pełną listę możliwych wartości. Jeśli "nieoczekiwana wartość" jest przekazywana w kodzie, to enum (lub cel za enumem) nie jest aktualny. Moją osobistą preferencją jest to, że każde wyliczenie ma domyślny przydział Niezdefiniowany.Biorąc pod uwagę, że wyliczenie jest zdefiniowaną listą, nigdy nie powinno być nieaktualne z kodem użytkownika.

Jeśli chodzi o to, co zrobić, gdy funkcja uzyskuje albo nieoczekiwaną wartość, albo niezdefiniowaną w moim przypadku, ogólna odpowiedź nie wydaje się możliwa. Dla mnie zależy to od kontekstu powodu, dla którego warto ewaluować wartość wyliczeniową: czy jest to sytuacja, w której należy zatrzymać wykonywanie kodu, czy też można użyć wartości domyślnej?

3

Moja opinia jest taka, że ​​ponieważ jest to błąd programisty, powinieneś albo na nim wystąpić, albo rzucić wyjątek RuntimException (Java lub jakikolwiek inny odpowiednik dla innych języków). Mam własny wyjątek UnhandledEnumException, który rozciąga się od RuntimeException, którego używam do tego.

0

Obowiązkiem funkcji wywołującej jest podanie poprawnych danych wejściowych i implicite, czego nie ma w wyliczeniu, jest nieważne (wydaje się, że programista pragmatyczny implikuje to). To powiedziawszy, oznacza to, że za każdym razem, gdy zmieniasz swoje wyliczenie, musisz zmienić WSZYSTKIEGO kodu, który akceptuje je jako dane wejściowe (i NIEKTÓREKodu, który daje je jako dane wyjściowe). Ale to prawdopodobnie prawda. Jeśli masz wyliczenie, które często się zmienia, prawdopodobnie powinieneś używać czegoś innego niż wyliczenie, biorąc pod uwagę, że wyliczenia są zwykle obiektami kompilacji.

+0

Całkowicie się zgadzam, ale w prawdziwym świecie kilka metod może zostać pominiętych, gdy wyliczenie zostanie zaktualizowane, dlatego właśnie zadaję to pytanie. Co robisz w takich przypadkach? –

3

Prawidłową odpowiedzią programu będzie die w sposób, który pozwoli programiście łatwo wykryć problem. mmyers i JaredPar obie dały dobre sposoby na zrobienie tego.

Dlaczego umrzeć? To wydaje się takie ekstremalne!

Powód jest taki, że jeśli nie obsługuje się wartości wyliczeniowej prawidłowo i po prostu przechodzi przez, wprowadzasz swój program w nieoczekiwany stan. Gdy jesteś w nieoczekiwanym stanie, kto wie, co się dzieje. Może to prowadzić do złych danych, błędów trudniejszych do wykrycia, a nawet luk w zabezpieczeniach.

Ponadto, jeśli program zginie, istnieje o wiele większa szansa, że ​​złapiesz go w kontroli jakości, a zatem nawet nie wyjdzie za drzwi.

+0

Tak, wydaje się to tak ekstremalne! Czy wyrzucenie wyjątku nie wystarczy, w tym sensie, że aplikacja może odzyskać po błędzie? Co jeśli ta metoda nie jest bardzo ważna? Przypadki niepowodzenia nadal będą przechwytywane w ramach kontroli jakości, ponieważ przyniosą one inne wyniki niż oczekiwano. –

+0

Dobrze, odzyskujesz swój błąd, ale teraz nie wiesz, w jakim stanie jest twój program. Jak już wspomniałem, doprowadzi to do innych subtelniejszych problemów. Kontrola jakości zobaczy te subtelne błędy i zgłosi je, a cały czas problem nie zostanie rozwiązany. –

+0

To nie jest * ja *, który odzyskuje z błędu, ale wyższe warstwy w hierarchii połączeń. To ważne wyróżnienie IMHO. Jeśli błąd został zarejestrowany (co powinno być), dobry komunikat o błędzie bezpośrednio doprowadziłby do głównej przyczyny. –

0

I zwykle starają się określić wartość nieokreśloną (0):

enum Mood { Undefined = 0, Happy, Sad } 

ten sposób mogę zawsze powiedzieć:

switch (mood) 
{ 
    case Happy: Console.WriteLine("I am happy"); break; 
    case Sad: Console.WriteLine("I am sad"); break; 
    case Undefined: // handle undefined case 
    default: // at this point it is obvious that there is an unknown problem 
      // -> throw -> die :-) 
} 

to przynajmniej jak zwykle to zrobić.

+0

Chociaż pamiętam, że przeczytałem gdzieś w MSDN, że należy utworzyć wartość "Undefined" dla wyliczenia, uważam to za problem dla niektórych wyliczeń, które nie powinny mieć takiej wartości (np. FileMode). O ile nie jest to potrzebne, zmusza programistów do sprawdzania go za każdym razem, gdy piszą metodę. –

+1

A domyślny przypadek wciąż musi być obsłużony, i to jest sedno pytania. –

+0

Cóż, wspomniałem w komentarzu - rzuć i umrzyj. Wrzuciłbym własny wyjątek wywodzący się z klasy Wyjątek. –

1

To jedno z tych pytań, które dowodzą, dlaczego rozwój oparty na testach jest tak ważny.

W tym przypadku wybrałabym wyjątek NotSupportedException, ponieważ dosłownie wartość nie była obsługiwana i dlatego nie jest obsługiwana. Wyjątek NotImplementedException daje więcej pomysłu: "To jeszcze nie jest zakończone";)

Kod wywołujący powinien być w stanie obsłużyć taką sytuację, a testy jednostkowe mogą być tworzone w celu łatwego przetestowania tego rodzaju sytuacji.

+0

To jest dobra uwaga. Ale czasami można rzucić wyjątek NotSupportedException dla prawidłowych wartości, na przykład podczas tworzenia StreamReader z FileMode.Write. Myślałem, że omawiany przypadek jest na tyle inny, że nie należy go traktować jak innych przypadków. –

2

Dla C# warto wiedzieć, że Enum.IsDefined() is dangerous. Nie możesz polegać na nim tak jak Ty. Uzyskanie czegoś, co nie jest wartością oczekiwaną, jest dobrym argumentem za wyrzuceniem wyjątku i głośnym umieraniem.

W Javie jest inaczej, ponieważ wyliczenia nie są liczbami całkowitymi, więc naprawdę nie można uzyskać nieoczekiwanych wartości (chyba że wyliczenie jest zaktualizowane, a instrukcja switch nie jest), co jest jednym z głównych powodów, dla których preferuję wyliczenia Java .Musisz również uwzględnić wartości zerowe. Ale uzyskanie niezerowej sprawy, której nie rozpoznajesz, jest dobrym argumentem za wyrzuceniem wyjątku.

+0

Zdecydowanie preferuję Java wylicza do C#. Czy mógłbyś wyjaśnić, na który wyjątek powinieneś rzucić w przypadku nierozpoznanych wartości? –

+0

W Javie rzuciłbym IllegalStateException, jeśli otrzymałem wartość wyliczenia, której nie rozpoznałem. IllegalArgumentException jest OK, ale wydaje mi się, że jest to bardziej nielegalny stan. – cletus

11

Zgaduję, że większość z powyższych odpowiedzi jest poprawna, ale nie jestem pewien, czy są poprawne.

Prawidłowa odpowiedź brzmi: bardzo rzadko włączasz język OO, oznacza to, że robisz źle. W tym przypadku jest to doskonała wskazówka, że ​​klasa Enum ma problemy.

Powinieneś po prostu wywoływać Console.WriteLine (mood.moodMessage()) i definiować komunikat moodMessage dla każdego ze stanów.

Po dodaniu nowego stanu - Cały kod powinien być dostosowywany automatycznie, nic nie powinno zawieść, zgłaszać wyjątku lub wymagać zmian.

Edytuj: odpowiedź na komentarz.

W twoim przykładzie, aby być "dobrym OO", funkcjonalność trybu plików byłaby kontrolowana przez obiekt FileMode. Może zawierać obiekt delegata z operacjami "Otwórz, przeczytaj, zapisz ...", które są różne dla każdego FileMode, więc File.open ("name", FileMode.Create) może zostać zaimplementowane jako (przykro z powodu braku znajomości API):

open(String name, FileMode mode) { 
    // May throw an exception if, for instance, mode is Open and file doesn't exist 
    // May also create the file depending on Mode 
    FileHandle fh = mode.getHandle(name); 
    ... code to actually open fh here... 
    // Let Truncate and append do their special handling 
    mode.setPosition(fh); 
} 

jest to o wiele schludniej niż próbuje zrobić z przełącznikami ... (nawiasem mówiąc, metody byłoby zarówno „Mode” klasy pakietowe-prywatne i ewentualnie delegowane)

Kiedy OO jest dobrze zrobione, każda metoda wygląda jak kilka linii naprawdę zrozumiałego, prostego kodu - ZBYT prosta. Zawsze masz wrażenie, że jakiś wielki "Cheese Nucleus" trzyma razem wszystkie małe obiekty nacho, ale nigdy nie możesz go znaleźć - to nachos aż do końca ...

+0

Tak jak wszystko inne, wyliczenia są dobre, gdy są używane poprawnie. Na przykład, jeśli projektowałem interfejs API do obsługi plików, robiłbym 'File.Open (nazwa pliku, tryb FileMode)', gdzie FileMode jest prostym wyliczeniem wartości, bez żadnego zachowania związanego z tym. Nie widzę tego "robi OO źle". –

+2

Woah, nie powiedziałem nic o tym, że cudze są złe! UWIELBIAM wyliczenia typów niebezpiecznych! - a użycie, które tam podałeś, jest świetne. Jest to problematyczne dla instrukcji switch. Oznacza to, że zachowanie powinno zostać przeniesione do enum. –

+0

Przepraszam, jeśli źle zrozumiałem, ale pozostaje pytanie: jak zaimplementować 'File.Open' bez instrukcji switch? Mówiąc o sobie, nie dałbym obiektom FileMode metody otwierania pliku, ponieważ po prostu tam nie pasuje. Co sugerujesz jako alternatywę? –

Powiązane problemy