Нужно ли добавлять регистр по умолчанию при использовании коммутаторов?

41

Во время недавнего обзора кода меня попросили поместить defaultдела во все файлы, где бы ни использовался switchблок, даже если нечего делать default. Это означает, что я должен положить defaultдело и ничего не писать в нем.

Это правильная вещь? Какой цели это будет служить?

Анкит
источник
9
Это зависит от вас ... Я имею в виду стиль кода руководителя.
SD
1
Я думаю, что есть даже случаи, когда вы не хотите случай по умолчанию. Подумайте о переключении перечисления. Ваш переключатель определяет регистр для всех возможных значений. Теперь добавление значения в перечисление должно привести к добавлению регистра в этом переключателе. Если вы этого не сделали, это может быть ошибкой, поскольку вы хотите что-то сделать для этого нового значения перечисления, но ваш код этого не делает. Ваш компилятор может предупредить вас (я думаю; не уверен насчет Java) об этом, но только если вы не указали регистр по умолчанию. Кроме того, вы можете напечатать предупреждение в случае по умолчанию, когда вы уверены, что код никогда не достигнет его.
Leemes
6
Я и мои друзья называем это случаем «Исключение разработчика» - когда в коде происходит что-то, что, как вы знаете, никогда не должно происходить (по логическим причинам или из-за кода), мы добавляем «Исключение разработчика», которое выдается. Таким образом, если это когда-нибудь случится, выдается исключение для разработчика, и, по крайней мере, мы знаем, где что-то пошло не так.
Марко

Ответы:

31

Кажется, есть три случая, когда defaultутверждение не нужно:

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

  2. Вы знаете, как и где switch caseбудет использоваться и какие значения будут вводить его. Опять же, это может измениться, и может потребоваться дополнительная обработка.

  3. другие случаи не требуют специальной обработки. Если это так, я думаю, что вас попросят добавить default case, потому что это принятый стиль кодирования, и он делает ваш код более читабельным.

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

superM
источник
14
В случаях 1 и 2 регистр по умолчанию должен быть ошибкой. Если ваш стиль кодирования всегда использует default, то сделайте это, но сделайте так, чтобы он выдавал ошибку в 1 и 2. Возможно, он должен испускать его во время компиляции, но это не всегда возможно, если когда-либо в java. По крайней мере, я думаю, что для пользователя важно получить сообщение «Вы стреляете себе в ногу, передавая это значение»
martiert
11
Случай по умолчанию всегда является хорошей идеей, потому что, например, в случае перечисления, если кто-то добавляет новую запись в перечисление, ваш случай по умолчанию должен делать нечто похожее на то, throw new IllegalStateException("Unrecognised case "+myEnum)что показывает вам, где и в чем ошибка.
Богатое
Означает ли это, что у вас всегда должен быть elseпункт после каждого, if/else ifдаже если вы знаете, что этого не должно быть? Мне не кажется хорошей идеей ...
jadkik94
2
Если я могу добавить анекдот из моей работы: у меня был метод фабрики, который использовал enum для создания экземпляров некоторых классов. Я оставил инструкции, что всякий раз, когда вы добавляете новый класс в этот проект, вы должны добавлять значение в перечисление и регистр переключателя. Конечно, через неделю после того, как я закрыл код, он перестает работать, за одним исключением, которое никогда не должно вызываться. Я подхожу к программисту и спрашиваю его: «Вы добавили случай в коммутатор?» и, конечно же, он этого не сделал. В тот день я изменил исключение, сказав: «Если вы получите это сообщение, обвините последнего разработчика, который коснулся кода».
Зив
28

Правильно ли это сделать? Какой цели это будет служить?

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

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

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

Калеб
источник
1
что такое assert?
FLY
1
@FLY Это ключевое слово в Java и обычно макрос в C, C ++ и т. Д. В любом случае, assert используется для проверки того, что какое-то предположение остается верным, и выдает исключение или иным образом вызывает ошибку, если это не так.
Калеб
Я отредактировал свой предыдущий комментарий со ссылкой на википедию
FLY
Вы не должны оставлять пустое дело. Если ваш переключатель не охватывает все возможные условия, вы должны установить другие условия в вашем случае по умолчанию по той же причине, по которой вы предложили добавить утверждение в случае по умолчанию во втором абзаце.
rold2007
12

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

Артур
источник
1
+1 за обнаружение проблемы во время компиляции, а не во время выполнения.
SylvainD
Интересно, как полностью вводит в заблуждение ответ и принимается. Быстрый поиск в Google показывает, что Java-компилятор имеет такие предупреждения. С какой стати вы будете ждать ошибки во время выполнения, ребята?
Артур
Я не знал об этом поведении. Мой пример кода ideone ideone.com/WvytWq предлагает иное. Если вы скажете, что это правда, что произойдет, если кто-то добавит значения к типу перечисления, но вы не перекомпилируете свой код?
эмори
1
Очевидно, что новые значения просто не обрабатываются оператором switch без регистра по умолчанию. Но если вы не перекомпилируете свои реализации при изменении интерфейсов, ваша система сборки действительно сломана.
Артур
9

Во многих отношениях этот вопрос совпадает с часто задаваемым вопросом: нужен ли мне elseпункт в конце if/ else ifлестницы, который охватывает все варианты .

Синтаксически говоря, ответ - нет, нет. Но есть однако ...

Предложение defaultможет быть там по крайней мере по двум причинам:

  1. Как обработчик ошибок - конечно, никогда не должен попасть сюда! это требование, которое может быть сделано, но что, если это так? Повреждение данных или, что еще хуже, отсутствие проверки данных являются путями сбоя программы, если они не были должным образом перехвачены. В этом случае это не должно быть пустым предложением!
  2. Проектирование / охват кода - даже в простейшей форме блок-схемы есть два пути из оператора if и всегда otherwiseиз случая. Нет никаких оснований не включать их в исходный код.

Моя философия всегда довольно проста - оцените наихудший сценарий из двух вариантов и выберите самый безопасный. В случае пустого elseили defaultпредложения, худшие варианты:

  • Включите их: дополнительные три или четыре строки «избыточного» кода.
  • Не включайте их: фальсифицированные данные или непредвиденное состояние не попадают в ловушку, что может привести к сбою программы.

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


Кроме того, руководящие принципы MISRA-C {см. Профиль для присоединения} рекомендуют defaultпункт для каждогоswitch

Эндрю
источник
Не только в конце лестницы. Вопрос: мне нужно elseпосле if. Но, как вы сказали, нет, это не так.
ott--
В качестве обработчика ошибок я предлагаю слегка измененную форму, которая позволяет избежать пустых значений по умолчанию. Что касается убийства людей, я не думаю, что это на самом деле имеет значение - люди будут столь же мертвы, независимо от того, есть ли у вас пункт по умолчанию, но это поможет понять, почему они умерли.
Эмори
Хороший вопрос, @emory - это было не очень понятно, было ли это ... отредактировано :)
Эндрю
6

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

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

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

Для обработки действий по умолчанию - где переключатели для особого поведения. Например, переменная может быть объявлена ​​вне коммутатора, но не инициализирована, и каждый случай инициализирует ее чем-то другим. Значение по умолчанию в этом случае может инициализировать его значением по умолчанию, поэтому код сразу после переключателя не выдаст ошибку / не сгенерирует исключение.

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

Deco
источник
2

Я также добавил бы, что это зависит от философии языка, который вы используете. Например, в Erlang, где распространен подход «дать сбой», вы бы не определяли случай по умолчанию, но позволяли своему caseутверждению выдавать ошибку, если возникает ситуация, которая не была обработана.

Rodrigue
источник
0

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

Если вы действительно уверены, что вас не волнуют другие случаи, по умолчанию: break; // все равно, но значением по умолчанию должно быть что-то вроде default: throw new Error ("непредвиденное значение");

Аналогично, вы никогда не должны «пропустить» нетривиальную конструкцию case без добавления комментария к эффекту, который вы намереваетесь пропустить. Ява ошибалась на этапе проектирования.

ddyer
источник
-2

Рассмотреть возможность

enum Gender
{
       MALE ,
       FEMALE ;
}

а также

void run ( Gender g )
{
      switch ( g )
      {
           case MALE :
                 // code related to males
                 break ;
           default :
                 assert Gender . FEMALE . equals ( g ) ;
                 // code related to females
                 break ;
      }
}

Я бы сказал, что это лучше, чем случай MALE, случай FEMALE и случай по умолчанию, который вызывает исключение.

Во время фазы тестирования компьютер проверит, является ли g ЖЕЛЕЗНОЙ. Во время производства проверка будет опущена. (Ага микрооптимизация!)

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

Эмери
источник
1
1. Нет необходимости в микрооптимизации - устранение мертвого кода используется компиляторами в течение последних 30 лет и будет устранено JIT. 2. 100% -ное покрытие кода обычно не считается важным (с одной стороны, 100-процентное покрытие может не охватить все крайние случаи, с другой - ни один тест не отловит строки assert_not_reached в правильной программе). В этом случае отсутствие значения по умолчанию приведет к ошибке компиляции. С другой стороны - как бы вы проверили, работает ли assert (вы перекладываете проблему и скрываете ее на 100%, вместо того, чтобы иметь ту же строку «этого не произойдет, я обещаю»).
Мацей Пехотка
1
3. Когда Gender . FEMALE . equals ( FEMALE ) ;оценивать false? Вы имели в виду, Gender.FEMALE.equals (g)но так как вы можете зависеть от стабильности ссылок, вы можете просто написать g == Gender.FEMALE. 4. (Личное мнение) такой код намного сложнее для чтения и приведет к немного более запутанным ошибкам.
Мацей Пехотка
@MaciejPiechotka хороший улов о g. Да, микрооптимизация не приносит пользы, но и не является недостатком. Покрытие 100% кода является преимуществом, если это является одной из ваших целей, и ваш инструмент покрытия кода не проверяет утверждения (что не должно).
Эмори
Тогда почему это будет одной из моих целей? «Интересная» часть должна быть протестирована для всех крайних случаев (независимо от того, отображаются ли они на строки кода), а «скучные» части можно просто пропустить, чтобы сэкономить время на тестировании. ИМХО охват кода плохая метрика тестов.
Мачей Печотка