2009-03-06 2 views
16

Предположим, что у нас есть метод, который принимает значение перечисления. После того, как этот метод проверяет, что значение действительно, оно 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() (или любое другое исключение)
  • Некоторые другие так, как я не думал о
+0

Я отправляю ответ с моим собственным мнением. Если у вас другое мнение или другое дело, которое поддерживает этот, пожалуйста, поделитесь своими знаниями. Цель вопроса - собрать идеи по этой теме, помочь другим людям (и мне) столкнуться с этой ситуацией. –

+0

Почему это было отмечено как java? – OscarRyz

+0

@ Оскар: Это должен быть вопрос, не зависящий от языка ИМХО. Люди могут найти ответ для обоих языков здесь. –

ответ

10

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

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

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

+0

Это должно было быть отредактировано в ваш исходный пост. –

+1

@SnOrfus: Почему? Это совершенно правильный ответ. –

+0

Как я уже сказал в своем ответе, это может быть справедливо, но это неправильно быть ужасным OO ... –

1

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

7

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

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

Но AssertionError расширяет java.lang.Error, а не java.lang.Exception. Это означало бы, что другие слои кода не будут иметь возможности поймать исключение и восстановить его. (Технически говоря, они могут, но практически у большинства людей нет (и не должны) catch java.lang.Error.) –

+0

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

+0

См. Также обсуждение в этом вопросе: http://stackoverflow.com/questions/398953/java-what-is-the-preferred-throwable-to-use-in-a-private-utility-class-construct –

2

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

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

Но значение может быть действительным, даже если метод не был обновлен, чтобы отразить его. Я думаю, что сообщение «Недопустимое значение» здесь немного вводит в заблуждение. –

+0

@Hosam, но код был написан не ожидая этого значения, поэтому я выбрал имя. «Неожиданное значение» будет работать так же хорошо, как я полагаю, но он предназначен для того, чтобы понять, что значение действительно не соответствует программисту – JaredPar

+0

@ JaredPar: достаточно справедливо. Я больше думал о «необработанном значении», что делает его довольно понятным, что это необработанный случай (IMHO). –

0

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

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

3

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

0

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

+0

Я полностью согласен, но в реальном мире несколько методов могут быть пропущены при обновлении перечисления, поэтому я задаю вопрос. Что вы делаете в таких случаях? –

3

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

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

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

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

+0

Да, это кажется настолько экстремальным! Не было бы лишнего исключения, в том смысле, что приложение может восстановиться после ошибки? Что делать, если метод не очень важен? Случаи отказа будут по-прежнему фиксироваться в QA, поскольку они будут давать разные результаты, чем ожидалось. –

+0

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

+0

Это не * me *, который восстанавливается от ошибки, но более высоких уровней в иерархии вызовов. Это важное различие ИМХО. Если ошибка была зарегистрирована (что должно быть), хорошее сообщение об ошибке приведет непосредственно к первопричине. –

0

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

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

+0

Хотя я помню, что прочитал где-то в MSDN, что нужно создать значение «Undefined» для перечисления, я считаю, что это проблема для некоторых перечислений, которые не должны иметь такого значения (например, FileMode). Если это не требуется, это заставляет разработчиков проверять его каждый раз, когда они пишут метод. –

+1

И дело по умолчанию все еще нужно обрабатывать, и это вопрос. –

+0

Ну, я упомянул в комментарии - брось и умру. Я бы выбрал собственное исключение, полученное из класса Exception. –

1

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

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

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

+0

Это хороший момент. Но иногда вы бросаете NotSupportedException для допустимых значений, например, при создании StreamReader с FileMode.Write. Я думал, что обсуждаемое дело отличается от того, что его нельзя обрабатывать, как и другие случаи. –

2

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

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

+0

Я, конечно, предпочитаю перечисления Java для C#. Не могли бы вы рассказать о том, какое исключение вы бросите в случае непризнанных значений? –

+0

В Java я бы выбрал исключение IllegalStateException, если я получил значение перечисления, которое я не узнал. IllegalArgumentException в порядке, но для меня это похоже на незаконное состояние. – cletus

11

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

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

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

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

Редактировать: Ответить на комментарий.

В вашем примере, чтобы быть «хорошим OO», функциональность файлового режима будет контролироваться объектом FileMode. Он может содержать объект-делегат с операциями «open, read, write ...», которые отличаются для каждого FileMode, поэтому File.open («name», FileMode.Create) может быть реализован как (извините за отсутствие знакомства с АНИ):

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

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

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

+0

Как и все остальное, перечисления хороши, когда они используются правильно. Например, если я разрабатываю API для обработки файлов, я бы создал 'File.Open (имя файла строки, режим FileMode)', где FileMode - это простое перечисление значений, без привязки к нему. Я не вижу, что это «делает OO неправильно». –

+2

Woah, я ничего не говорил о том, что перечисления были плохими! Я ЛЮБЛЮ типичные перечисления! - и использование, которое вы указали, замечательно. Это проблема с коммутатором, которая является проблематичной. Это указывает на то, что поведение должно быть перенесено в перечисление. –

+0

Извините, если я неправильно понял, но вопрос все еще остается: как вы реализуете 'File.Open' без оператора switch? Говоря о себе, я бы не давал объектам FileMode метод открытия файла, потому что он просто не принадлежит. Что вы предлагаете в качестве альтернативы? –

Смежные вопросы