Каков предпочтительный метод для обработки неожиданных значений перечисления? - PullRequest
16 голосов
/ 06 марта 2009

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

Например:

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() (или любое другое исключение)
  • Каким-то другим способом, о котором я не думал

Ответы [ 12 ]

11 голосов
/ 07 марта 2009

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

Правильный ответ: вы очень редко переключаетесь на язык ОО, это указывает на то, что вы делаете свой ОО неправильно. В этом случае это прекрасный признак того, что у вашего класса 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")

Когда ОО выполняется хорошо, каждый отдельный метод выглядит как несколько строк действительно понятного, простого кода - СЛИШКОМ простого. У вас всегда возникает ощущение, что есть какое-то большое грязное «сырное ядро», в котором собраны все маленькие начо-объекты, но вы никогда не сможете его найти - это начос до самого конца ...

10 голосов
/ 06 марта 2009

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

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

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

7 голосов
/ 06 марта 2009

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

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

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

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

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

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

3 голосов
/ 06 марта 2009

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

2 голосов
/ 07 марта 2009

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

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

2 голосов
/ 06 марта 2009

Почти для каждого оператора 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 });
}
1 голос
/ 06 марта 2009

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

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

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

1 голос
/ 06 марта 2009

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

0 голосов
/ 06 марта 2009

Я обычно пытаюсь определить неопределенное значение (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 :-)
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...