Текущий case
Один из худших атавизмов в программировании – это протекающий оператор
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 в коде быть не должно – только
паттерн-матчинг, который гарантирует, что сработает только одна ветка. А второе
– не знаю, как можно жить с языком без макросов. Это словно красть у себя
самого.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter