оператор switch - обработка случая по умолчанию, когда он не может быть достигнут

14

Если я использую оператор switch для обработки значений из перечисления (принадлежащего моему классу) и у меня есть регистр для каждого возможного значения - стоит ли добавлять код для обработки случая «по умолчанию»?

enum MyEnum
{
    MyFoo,
    MyBar,
    MyBat
}

MyEnum myEnum = GetMyEnum();
switch (myEnum)
{
    case MyFoo:
        DoFoo();
        break;
    case MyBar:
        DoBar();
        break;
    case MyBat:
        DoBat();
        break;
    default:
        Log("Unexpected value");
        throw new ArgumentException() 
}

Я не думаю, что это потому, что этот код никогда не может быть достигнут (даже с юнит-тестами). Мой коллега не согласен и считает, что это защищает нас от неожиданного поведения, вызванного добавлением новых значений в MyEnum.

Что скажете вы, сообщество?

SD
источник
Допустим, MyEnum является ненулевым типом.
сд
3
«Сегодня» это не обнуляемый. Как насчет завтра, когда вы больше не будете поддерживать код. Или как насчет того, когда «MyBiz» добавляется в перечисление, но не в случае? Комментарии Калеба по обслуживанию очень уместны.
1
Научите ваш компилятор, что это фатальная ошибка, если есть переключатель, не охватывающий все случаи.
Что, если кто-то преобразует недопустимое значение, а MyEnumзатем передает его через ваш переключатель?
Mawg говорит восстановить Monica
1
Какой язык? Если Java, вы должны поместить метод в Enum и просто вызвать его (полиморфизм), полностью исключив switchоператор.
user949300

Ответы:

34

Включение регистра по умолчанию не меняет способ работы вашего кода, но делает его более понятным. Делая код очевидным образом (регистрируйте сообщение и создавайте исключение), вы добавляете большую красную стрелку для стажера, которого ваша компания наймет следующим летом, чтобы добавить пару функций. Стрелка говорит: «Эй, ты! Да, я разговариваю с тобой! Если ты собираешься добавить еще одно значение в перечисление, тебе лучше добавить и здесь дело». Это дополнительное усилие может добавить несколько байтов к скомпилированной программе, что необходимо учитывать. Но это также спасет кого-то (может быть, даже вас в будущем) где-то между часом и днем ​​непродуктивной царапины на голове.


Обновление: описанная выше ситуация, то есть защита от значений, добавленных в перечисление в более позднее время, также может быть обнаружена компилятором. Clang (и gcc, я думаю) по умолчанию выдаст предупреждение, если вы включите перечислимый тип, но у вас нет регистра, который бы охватывал все возможные значения в перечислении. Так, например, если вы удалите defaultрегистр из переключателя и добавите новое значение MyBazв перечисление, вы получите предупреждение:

Enumeration value 'MyBaz' not handled in switch

Разрешение компилятору обнаруживать нераскрытые случаи заключается в том, что он в значительной степени устраняет необходимость в этом недоступном defaultслучае, который вдохновил ваш вопрос в первую очередь.

Калеб
источник
2
Хорошо, вы меня убедили :) Мне просто нужно принять вмятину в моих кодах покрытия номеров.
сд
@st Нет причины, по которой вы не можете проверить этот код. Просто сделайте тестовую сборку, которая условно компилирует дополнительное значение в вашем перечислении, а затем напишите модульный тест, который его использует. Возможно, это не идеально, но, вероятно, это не единственный случай, когда вам нужно протестировать код, который обычно никогда не будет достигнут.
Калеб
Или вы приводите значение non-enum к вашему типу enum и используете его.
Mawg говорит восстановить Monica
5

Я только что говорил с коллегой об этом и сегодня утром - это очень прискорбно, но я думаю, что обработка по умолчанию необходима для безопасности по двум причинам:

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

Что еще более важно, в зависимости от языка / компилятора, может быть возможным иметь значения, которые не являются членами перечисления в вашей переключаемой переменной. Например, в C #:

MyEnum myEnum = (MyEnum) 3; // This could come from anywhere, maybe parsed from text?
// ... code goes on for a while

switch ( myEnum )
{
    case MyEnum.A:
        // ... handle A case
        break;
    case MyEnum.B:
        // ... handle B case
        break;
}

// ... code that expects either A or B to have happened

Добавляя простое case default:и выбрасывая исключение, вы защищаете себя от этого странного случая, когда «ничего» не происходит, но «что-то» должно было случиться.

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

Крис Филлипс
источник
3

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

Ryathal
источник
2

Я говорю:

Попробуйте добавить другой тип MyEnum. Затем измените эту строку:

MyEnum myEnum = GetMyEnum();

в

MyEnum myEnum = SomethingElse;

Затем запустите ваш код с регистром по умолчанию и без регистра по умолчанию. Какое поведение вы предпочитаете?

Наличие регистра по умолчанию также может быть полезно для захвата NULLзначений и предотвращения NullPointerExceptions.

FrustratedWithFormsDesigner
источник
-1

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

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

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

mattnz
источник
Почему все -1?
Mattnz
2
Я не -1 ты, но я думаю, это будет из-за твоего отношения;)
Friek