Один из худших атавизмов в программировании – это протекающий оператор switch/case. Он ведет родословную со времен Си и в настоящее время есть во всех императивных языках.

switch (expression) {
    case constant1:
        // Code to execute if expression == constant1
        break;
    case constant2:
        // Code to execute if expression == constant2
        break;
    default:
        // Code to execute if no case matches
}

В чем проблема? В том, что без оператора break управление передается в ниже. Если expression подходит под разные условия, сработают две ветки одна за другой. Счастливой отладки.

Сколько людей погорело на протечках в switch/case – я даже боюсь предположить.

В Джаве switch/case устроен аналогично, но к счастью там это поправили. Есть другой switch/case со стрелками, который гарантирует, что будет выполнена только одна ветка. Вдобавок он возвращает значение:

int result = switch (input) {
    case "A" -> {
        System.out.println("Processing A");
        yield 1;
    }
    case "B" -> {
        System.out.println("Processing B");
        yield 2;
    }
    default -> 0;
};

В последних Джавах много внимания уделяют паттерн-матчингу. Можно сказать, что любой switch лучше свести к паттерн-матчингу, потому что иначе получается минное поле из-за протечек.

В Кложе, например, макрос case гарантирует, что сработает только одна ветка. При этом выражения должны быть литералами, потому что компилятор вычисляет от них хеши для таблицы переходов:

(case (:status item)

  "active"
  (process-active item)

  "pending"
  (process-pending item)

  ;; default
  (throw-error "wrong status"))

Есть макрос cond для условий, которые вычисляются в рантайме. Будет вычеслена первая ветка, для которой условие вернет истину:

(cond

  (-> item :status (= "active"))
  (process-active item)

  (or (-> item :status (= "pending"))
      (queue-is-blocked ...)
  (process-pending item)

  :else
  (default-case "..."))

Наконец, есть скользящий, “протекающий” cond-> со стрелкой. Он принимает пары (условие->выражение) и пропускает сквозь них исходное выражение.

(cond-> []

  :always
  (conj (get-some-item ..))

  (item-is-active? item)
  (conj the-item)

  :finally
  (into (get-additional-items)))

Пишу я это потому, что погорел со switch/case в Питоне. Хотя с версии 3.10 в него завезли оператор match, многие до сих пор пишут каскады if...elif...elif...elif...else. И вот я добавил новую ветку в середину, а вместо elif написал if:

if action == "foobar":
  do_this()

elif action == "kek":
  do_that()

if action == "some_action":
  do_lol()

else:
  throw Exception("...")

Ветка выполняется, но первый оператор if обрывается и начинается второй. Для него условие не находится и срабатывает else, где бросается исключение. Получилось так, что действие выполняется, но из-за того, что было исключение, задача запускается еще раз. В итоге мой код вызвал действие три раза. Линтер ничего не заметил.

На мой взгляд, старого оператора switch/case в коде быть не должно – только паттерн-матчинг, который гарантирует, что сработает только одна ветка. А второе – не знаю, как можно жить с языком без макросов. Это словно красть у себя самого.