Надлежащее использование резервных операторов переключения

14

Когда уместно использовать сквозной (классический) оператор switch? Рекомендуется и поощряется ли такое использование или его следует избегать любой ценой?

shabunc
источник
Не все языки допускают переход на операторы switch.
Отредактировано
@ Отредактировано, отредактировано, добавлено «классическое» слово. Не все языки допускают
провал
3
Если вы говорите об устройстве Даффса , это одно.
Отредактировано
6
@Oded: Это первое, что думает большинство людей, но вряд ли это «уместно». В соответствии с этим Дафф сам сказал: «Это определенно дает аргумент в дискуссии о том, должен ли переключатель проваливаться по умолчанию, но я не уверен, является ли аргумент за или против».
BlueRaja - Дэнни Пфлюгофт

Ответы:

15

Вот пример, где это было бы полезно.

public Collection<LogItems> GetAllLogItems(Level level) {
    Collection<LogItems> result = new Collection<LogItems>();
    switch (level) {
        // Note: fall through here is INTENTIONAL
        case All:
        case Info:
             result.Add(GetItemsForLevel(Info));
        case Warning:
             result.Add(GetItemsForLevel(Warning));
        case Error:
             result.Add(GetItemsForLevel(Error));
        case Critical:
             result.Add(GetItemsForLevel(Critical));
        case None:
    }
    return result;
}

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

конфигуратор
источник
13
Этот пример, хотя и интересный, не содержит необходимого // INTENTIONAL FALL THROUGH HEREкомментария с заглавными буквами.
Рассел Борогове
Так же легко написать этот код с провалом, вызвав GetItemsForLevel(Info)вызов GetItemsForLevel(Warning)и так далее.
Калеб
@RussellBorogove: Абсолютно верно.
конфигуратор
@Caleb: в этом случае да. Но что, если звонки были немного сложнее? Это может стать немного волосатым, в то время как провал делает это простым. Должен отметить, что на самом деле я не одобряю использование провала - я просто говорю, что это может быть полезно в определенных ситуациях.
конфигуратор
Не лучший пример. Существует упорядочение уровней предупреждения. Определяя этот порядок, этот метод сводит к одной строке элементов фильтрации кода, когда уровень элемента по меньшей мере равен желаемому уровню.
Кевин Клайн
8

Я использую их, когда определенные функции должны применяться для более чем одного значения. Например, допустим, у вас есть объект со свойством с именем operationCode. Если код равен 1, 2, 3 или 4, вы хотите запуститьOperationX (). Если это 5 или 6, вы хотите запуститьOperationY () и 7 вы запускаете OperationZ (). Зачем иметь 7 полных случаев с функциональностью и пробелами, когда вы можете использовать провалы?

Я думаю, что это вполне допустимо в определенных ситуациях, особенно если оно избегает 100 операторов if-else. знак равно

Yatrix
источник
4
То, что вы описываете, представляет собой switchнесколько caseобъектов, привязанных к одному и тому же коду. Это отличается от провала, когда выполнение одного caseпродолжается в следующем из-за отсутствия breakмежду ними.
Blrfl
3
Это не имеет значения, провал - это просто отсутствие оператора break, поэтому он «проваливается» в следующий случай.
Ятрикс
Я бы перефразировал сценарий в вашем ответе, чтобы отразить это.
Blrfl
2
@Yatrix: я не согласен. Различия в последовательных случаях, когда все исполняют один и тот же блок кода, значительно отличаются от пропуска разрыва. Один из них рутинный, другой далеко от него (то есть) - и вероятность неправильного прочтения и непреднамеренного потока управления намного выше.
Квентин Старин
3
@ Да, все по-другому, но они оба проваливаются. Он спросил, когда / будет ли это уместно. Я привел пример того, когда это будет. Это не должно было быть всеобъемлющим примером. В зависимости от языка наличие утверждений перед неудачей может не сработать. Я не думаю, что это будет с C # stackoverflow.com/questions/174155/…
Yatrix
8

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

gbjbaanb
источник
7

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

Например (обратите внимание на пояснительные комментарии):

public boolean isAvailable(Server server, HealthStatus health) {
  switch(health) {
    // Equivalent positive cases
    case HEALTHY:
    case UNDER_LOAD:
      return true;

    // Equivalent negative cases
    case FAULT_REPORTED:
    case UNKNOWN:
    case CANNOT_COMMUNICATE:
      return false;

    // Unknown enumeration!
    default:
      LOG.warn("Unknown enumeration " + health);
      return false;
  }
}

Я считаю этот вид использования вполне приемлемым.

Bringer128
источник
Обратите внимание, что между case X: case Y: doSomething()и есть большая разница case X: doSomething(); case Y: doAnotherThing(). В первом случае намерение является довольно явным, а во втором - провал может быть намеренным или нет. По моему опыту, первый пример никогда не вызывает никаких предупреждений в компиляторах / анализаторах кода, в то время как второй делает. Лично я бы назвал второй пример «провалившимся», хотя и не уверен насчет «официального» определения.
Йенс Баннманн
6

Это зависит от:

  • ваши личные предпочтения
  • стандарты кодирования вашего работодателя
  • количество риска

Две основные проблемы, связанные с переходом одного дела на другое:

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

  2. Не очевидно, что код для одного случая включает в себя код для одного или нескольких последующих случаев.

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

Калеб
источник
4

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

function get_julian_day (date) {
    int utc_date = date.getUTCDate();
    int utc_month = date.getUTCMonth();

    int julian_day = 0;

    switch (utc_month) {
        case 11: julian_day += 30;
        case 10: julian_day += 31;
        case 9:  julian_day += 30;
        case 8:  julian_day += 31;
        case 7:  julian_day += 31;
        case 6:  julian_day += 30;
        case 5:  julian_day += 31;
        case 4:  julian_day += 30;
        case 3:  julian_day += 31;
        case 2:  julian_day += 28;
        case 1:  julian_day += 31;
        default: break;
    }

    return julian_day + utc_date;
}
AaronDanielson
источник
5
Хотя это довольно умно, время, которое вы экономите от этого, будет намного перевешивать время, которое требуется другим людям, чтобы понять, что это делает. Кроме того, вы можете получить более высокую производительность, удалив сквозной аспект, то есть 31 + 28 + 31 всегда равен 90. Если вы собираетесь это сделать, вы можете просто сложить итоги в случаях, так как есть только 11 для рассмотрения.
JimmyJames
Вы правы, мой пример определенно надуманный :) Я скажу, однако, легче обнаружить скрытую ошибку, когда число дней указано так, а не предварительно вычислять все накопленные дни для каждого случая переключения ,
AaronDanielson
2
Я дам вам это, но делать это в постоянном объявлении было бы предпочтительнее, например, FEB_START = 31; MAR_START = FEB_START + 28; и т.д. не уверен, что это за язык, но во многих он будет вычислен во время компиляции. Большая проблема здесь в том, что это немного тупо. Не настолько, что это плохая идея, просто нетипично. Если нет действительно большой выгоды, как правило, лучше придерживаться общих идиом.
JimmyJames
1
Отличная альтернатива, использование констант для стартовых дней в этом примере. В любом случае, суть в том, что накопительная сумма - отличное применение для аварийного переключения. Я согласен на 100% использовать общие идиомы, но природа этого вопроса заключается в более эзотерических чертах, где трудно найти общие идиомы.
AaronDanielson
3

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

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

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

Квентин-starin
источник