Подтвердить что ты не робот

Каков предпочтительный метод обработки неожиданных значений перечисления?

Предположим, что у нас есть метод, который принимает значение перечисления. После того, как этот метод проверяет, что значение действительно, оно switch es по возможным значениям. Итак, вопрос в том, какой предпочтительный метод обработки неожиданных значений после проверки диапазона значений?

Например:

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?
    }

Каков предпочтительный метод обработки случая default?

  • Оставить комментарий как // can never happen
  • Debug.Fail() (или Debug.Assert(false))
  • throw new NotImplementedException() (или любое другое исключение)
  • Как-то иначе я не думал о
4b9b3361

Ответ 1

Я предпочитаю throw new NotImplementedException("Unhandled Mood: " + mood). Дело в том, что перечисление может измениться в будущем, и этот метод не может быть соответствующим образом обновлен. Выбрасывание исключения, по-видимому, является самым безопасным методом.

Мне не нравится метод Debug.Fail(), потому что этот метод может быть частью библиотеки, а новые значения могут не тестироваться в режиме отладки. В этом случае другие приложения, использующие эту библиотеку, могут столкнуться с странным поведением в режиме исполнения, тогда как в случае выброса исключения ошибка будет немедленно известна.

Примечание: NotImplementedException существует в commons.lang.

Ответ 2

Я думаю, что большинство приведенных выше ответов действительно, но я не уверен, что они верны.

Правильный ответ: вы очень редко переключаетесь на язык OO, это означает, что вы неправильно делаете OO. В этом случае это прекрасный признак того, что ваш класс Enum имеет проблемы.

Вы должны просто вызвать Console.WriteLine(mood.moodMessage()) и определить moodMessage для каждого из состояний.

Если добавлено новое состояние - весь ваш код должен автоматически адаптироваться, ничто не должно терпеть неудачу, вызывать исключения или изменения.

Изменить: ответ на комментарий.

В вашем примере, чтобы быть "Хорошим OO", функциональность файлового режима будет контролироваться объектом FileMode. Он может содержать объект-делегат с операциями "open, read, write...", которые отличаются для каждого FileMode, поэтому File.open( "name", FileMode.Create) может быть реализован как (извините за отсутствие знакомства с 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);
}

Это намного опрятно, чем пытаться сделать это с помощью переключателей... (кстати, методы будут как частными, так и частными, и, возможно, делегированы классам "Mode" )

Когда OO выполняется хорошо, каждый метод выглядит как несколько строк действительно понятного простого кода - TOO просто. У вас всегда возникает ощущение, что есть какое-то большое грязное "Сырное ядро", держащее вместе все маленькие объекты nacho, но вы никогда не сможете его найти - это начо все время вниз...

Ответ 3

В Java стандартным способом является выброс AssertionError по двум причинам:

  • Это гарантирует, что даже если asserts отключено, выдается ошибка.
  • Вы утверждаете, что других значений перечисления нет, поэтому AssertionError документирует ваши предположения лучше, чем NotImplementedException (что Java не имеет в любом случае).

Ответ 4

Мое мнение состоит в том, что, поскольку это ошибка программиста, вы должны либо заявить об этом, либо бросить RuntimException (Java, или что-то подобное для других языков). У меня есть собственное исключение UnhandledEnumException, которое простирается от RuntimeException, которое я использую для этого.

Ответ 5

Правильный ответ программы будет умереть таким образом, чтобы разработчик мог легко выявить проблему. mmyers и JaredPar дали хорошие способы сделать это.

Зачем умирать? Это кажется настолько экстремальным!

Причина в том, что если вы неправильно обрабатываете значение перечисления и просто проваливаете, вы ставите свою программу в неожиданное состояние. Когда вы находитесь в неожиданном состоянии, кто знает, что происходит. Это может привести к плохим данным, ошибкам, которые сложнее отслеживать, или даже к уязвимостям безопасности.

Кроме того, если программа умирает, есть гораздо больший шанс, что вы поймаете ее в QA и, таким образом, она даже не выйдет за дверь.

Ответ 6

Для почти каждого оператора switch в моей базе кода у меня есть следующий случай по умолчанию

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

Метод будет генерировать исключение, детализирующее значение перечисления в точке, где была обнаружена ошибка.

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 });
}

Ответ 7

Для С# что-то стоит знать, что Enum.IsDefined() является опасным. Вы не можете полагаться на него так, как есть. Получение чего-то не от ожидаемых значений - хороший пример для того, чтобы бросить исключение и умереть громко.

В Java это отличается от того, что перечисления являются классами не целыми числами, поэтому вы действительно не можете получить неожиданные значения (если обновление не обновлено, а ваш оператор switch не является), что является одной большой причиной, почему я предпочитаю перечисления Java. Вы также должны обслуживать нулевые значения. Но получение ненулевого случая, который вы не распознаете, является хорошим примером для исключения исключений.

Ответ 8

У вас может быть трассировка по умолчанию, вызывающая значение переданного enum. Бросание исключений в порядке, но в большом приложении будет несколько мест, где ваш код не заботится о других значениях перечисления.
Итак, если вы не уверены, что код намеревается обрабатывать все возможные значения перечисления, вам придется вернуться позже и удалить исключение.

Ответ 9

Это один из тех вопросов, который доказывает, почему развитие, основанное на тестах, так важно.

В этом случае я бы выбрал исключение NotSupportedException, потому что буквально значение было необработанным и, следовательно, не поддерживалось. A NotImplementedException дает больше идеи: "Это еще не закончено";)

Вызывающий код должен иметь возможность обрабатывать такую ​​ситуацию, и могут быть созданы модульные тесты, чтобы легко протестировать такие ситуации.

Ответ 10

Назовите это мнением или предпочтением, но идея перечисления состоит в том, что он представляет полный список возможных значений. Если в коде передается "неожиданное значение", то перечисление (или назначение за перечислением) не обновляется. Мои личные предпочтения заключаются в том, что каждое перечисление имеет стандартное назначение Undefined. Учитывая, что перечисление является определенным списком, оно никогда не должно устаревать с вашим потребительским кодом.

Что касается того, что делать, если ваша функция получает неожиданное значение или Undefined в моем случае, общий ответ кажется невозможным. Для меня это зависит от контекста причины оценки значения перечисления: является ли ситуация, когда выполнение кода должно прекратиться, или может использоваться значение по умолчанию?

Ответ 11

Ответственность вызывающей функции заключается в предоставлении правильного ввода и неявно что-либо, не содержащееся в перечислении, является недействительным (кажется, подразумевает это программный программист). Тем не менее, это означает, что в любое время, когда вы меняете свой список, вы должны изменить ВСЕ код, который принимает его как вход (и код SOME, который дает его как вывод). Но это, вероятно, так или иначе. Если у вас есть перечисление, которое часто изменяется, вы, вероятно, должны использовать что-то иное, кроме перечисления, считая, что перечисления обычно являются объектами компиляции.

Ответ 12

Обычно я пытаюсь определить undefined значение (0):

enum Mood { Undefined = 0, Happy, Sad }

Таким образом я всегда могу сказать:

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 :-)
}

Это, по крайней мере, то, как я обычно это делаю.