Comments 67
public class Test {
public static void main(String[] argv) {
lab1: for (int i=0; i<10; i++) {
for (int j=0; j<10; j++) {
if (j>=5 && i>5)
break lab1;
System.out.println(i + " / " + j);
}
}
}
}
Не часто пользуюсь, но в принципе местами бывает удобна. На if-ах это сделать уже значительно сложнее без дополнительных переменных.
Тоже самое есть и в JavaScript: https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Statements/label
var i, j;
loop1:
for (i = 0; i < 3; i++) { //Первый цикл, обозначенный меткой "loop1"
loop2:
for (j = 0; j < 3; j++) { //Второй цикл, обозначенный меткой "loop2"
if (i == 1 && j == 1) {
continue loop1;
}
console.log("i = " + i + ", j = " + j);
}
}
Отдельно можно сказать про проверку на null. Она нужна в любом случае, т.к. передача null в метод это, как правило, ошибка, и об этом надо сообщить. Что бы ни было внутри метода, switch, if, или просто «Hello World», а проверка входных данных должна быть.
ИМХО проблемы надуманные
Я всячески старался избегать слова проблема, больше старался подчеркивать личная неприязнь, но справдливости ради скажу, что проблемы if VS switch находятся при review кода, как в одну так и в другую стороны.
Отдельно можно сказать про проверку на null. Она нужна в любом случае, т.к. передача null в метод ...
Бесспорно! Но жизнь такова, что многие проверки не делают и в результате получаем, в одном случае крайне неинформативную ошибку NPE, а в другом просто недостаточно информативную (если из примера «Неподдерживаемый канал связи null»)
Во-первых, тогда человеку разбирая алгоритм не нужно искать второй и последующий if чтобы понять, что это не просто проверка, а выбор из альтернатив.
Во-вторых, компилятор или интерпретатор смогут лучше оптимизировать свою работу если код точнее следует алгоритму. Используя серию if вы подменяете алгоритм одной из возможных его реализаций и заменяете одну конструкцию целой серией других, то есть усложняете алгоритм.
Однако ничто не заставит писать код “как правильно”, когда хочется “как хочется".
А в целом, всему своё место.
в нормальных IDE подобные ошибки подсвечиваются
public List<String> getTypes2(Channel channel) {
Channel modifiedChannel = DoSomethingWith(channel);
if (INTERNET == modifiedChannel) {
return Arrays.asList("SMS", "MAC");
}
if (HOME_PHONE == modifiedChannel || DEVICE_PHONE == channel) { // <- сравниваем не ту ссылку
return Arrays.asList("CHECK_LIST");
}
throw new IllegalArgumentException("Неподдерживаемый канал связи " + channel);
}
Ни один статический анализатор тут ошибку не найдет.
зато тесты найдут. либо через юнит либо через мутацию
да.
P.S. вы подменили смысл моего первого комментария — одно дело, когда в последующем if есть условие ранее проверенное другим if (например следствие copy-paste) и совсем другое неявная логическая ошибка в условиях (может вам именно такая проверка и нужна была)
Мы же все понимаем, что копипаста это плохо, вторая посылка заключается в том, что switch избавляет от копипасты: мы пишем только один раз имя переменной и возможные паттерны, а в if'е мы должны в каждой ветке скопипастить имя и не дай Бог не ошибиться. Единственное, что может расстраивать, это многословность swtich case, но сама конструкция в этом не виновата.
в силу предпочтений линейного кода против древовидного внутри одного метода, для себя давно решил, что switch только в тех случаях, когда внутри нет return — в остальных случаях строго if.
Да и чаще всего, логика несколько сложнее чем просто проверка 1-in-1 константы, и тут снова if в выигрыше
Странно, что никто не вспомнил старый добрый паскаль. Уже больше 15 лет, как перешёл на C-подобные языки, но каждый раз, как пишу switch (и иногда for) — с тоской думаю о том, что просрали язык, где это было сделано по уму...
Да, с for сейчас за счёт всевозможных foreach полегче стало.
Switch с break по дефолту — гуд, но ещё желательна адекватная запись (уж на что 20 лет назад апологеты C ругали паскаль за многословность — такой кучи слов case в нём нет) и поддержка диапазонов (типа 0..31).
Радует запись в Haskell, но он всё же весьма специфичен и не мейнстрим ни разу.
Актуальные реализации паскаля — это прекрасно, но будем смотреть правде в глаза: паскаль мёртв. Его вытеснили другие языки. 2% распространённости — это агония, вероятно, на поддержке старых проектов.
А вот украсть из него (в линейку си-подобных) удачные решения — было бы неплохо.
Ну, справедливости ради — не все. Тот же ущербный C-шный синтаксис switch-case остался, добавилась лишь защита от совсем уж дурацких ошибок.
С for понятно — foreach решает практически все задачи, кроме циклов в вычмате.
Expr Simplify(Expr e)
{
switch (e) {
case Mult(Const(0), *): return Const(0);
case Mult(*, Const(0)): return Const(0);
case Mult(Const(1), var x): return Simplify(x);
case Mult(var x, Const(1)): return Simplify(x);
case Mult(Const(var l), Const(var r)): return Const(l*r);
case Add(Const(0), var x): return Simplify(x);
case Add(var x, Const(0)): return Simplify(x);
case Add(Const(var l), Const(var r)): return Const(l+r);
case Neg(Const(var k)): return Const(-k);
default: return e;
}
}
и облегченный вариант (когда все ветки возвращают значение):
var areas =
from primitive in primitives
let area = primitive switch (
case Line l: 0,
case Rectangle r: r.Width * r.Height,
case Circle c: Math.PI * c.Radius * c.Radius,
case *: throw new ApplicationException()
)
select new { Primitive = primitive, Area = area };
Хотя копи-пастить case раздражает, но в целом ничего супер-страшного это не несет.
Взять из ML (F#) pattern matching — конечно, тоже польза, но он хорош для функционального стиля.
А я говорил про совсем простую вещь для императивщины — диапазоны значений в case.
Ну и убрать слово case у меток, как ненужное.
Ничем не плох, но не покрывает некоторые нужды.
К примеру, привычная задача посимвольной обработки строки. Характерный код на паскале:
case c of
#0..#31 : skip_char();
' ' : process_space();
'0'..'9' : process_digit(c);
'A'..'Z' : process_alpha(c);
end;
Как записать просто и понятно? (да, я в курсе, что жизнь стала сложнее и символ по нынешним временам может занимать 4 байта, а то и более, но мне хотя бы в рамках ASCII)
switch(shape)
{
case Circle c:
WriteLine($"circle with radius {c.Radius}");
break;
case Rectangle s when (s.Length == s.Height):
WriteLine($"{s.Length} x {s.Height} square");
break;
case Rectangle r:
WriteLine($"{r.Length} x {r.Height} rectangle");
break;
default:
WriteLine("<unknown shape>");
break;
case null:
throw new ArgumentNullException(nameof(shape));
}
Это всё прекрасно и отлично решает свои задачи (диспатч по типу и декомпозиция объекта — просто, понятно, без оверхеда и места для глупых ошибок).
Но я говорил о более простых вещах. Обратите внимание на диапазоны в моём примере. #0..#31, '0'..'9', 'A'..'Z'. Дико бесит, что в C вместо них сделали какой-то невнятный костыль с проваливанием в следующий кейс, если не написал break.
Ну так используйте: case char c when c > '0' && c < '9' ...
И вот мы пришли к
if (c>='0' && c<='9')
с которого начинали :-)
Серьёзно: case не нужен (c у нас всегда char, это не часть алгебраического типа), when от if ничем не отличается (у нас императивщина, не забыли?)
Обратите внимание на диапазоны в моём примере. #0..#31, '0'..'9', 'A'..'Z'.
При желании для любой пары ЯП можно подобрать пример, в котором один язык выглядит удобнее другого. Для сколь-нибудь сложного парсинга и удобнее и логичнее использовать генераторы парсеров.
Дико бесит, что в C вместо них сделали какой-то невнятный костыль с проваливанием в следующий кейс
Линейное исполнение программы теперь называется "костылём"?
Линейное исполнение программы теперь называется "костылём"?
Учитывая, что во всех последющих языках вроде джавы и шарпа от этого отказались (в том же шарпе это вызывает ошибку компиляции), то стоит задуматься о "линейности" goto-like оператора.
Учитывая, что во всех последющих языках вроде джавы и шарпа от этого отказались (в том же шарпе это вызывает ошибку компиляции), то стоит задуматься о "линейности" goto-like оператора.
Ага, в Java отказались. Видимо все ставят break
/return
в ветках case по привычке, а всякие статические анализаторы распознают специальный комментарий fallthrough
просто смеха ради.
При желании для любой пары ЯП можно подобрать пример, в котором один язык выглядит удобнее другого.
Ну да. Когда я перешёл с Паскаля/Дельфи на C++ — я получил кучу вкусностей. И некоторое количество минусов (собственно, связанных с языком всего три: case, for, with; ну, плюс тормоза компиляции из-за подключения модулей через include). Смена используемых библиотек повлияла куда сильнее.
Да, пожалуй, как раз то, чего хотелось бы (особенно учитывая обобщение на не-константные выражения, чего порой не хватало в Паскале и Си). Но, к сожалению, как и Паскаль — тоже не мейнстрим.
Оператор этот удобнее, но с точки зрения команды родного мне шарпа ввод новых ключевых слов они ОЧЕНЬ не любят. Поэтому и придумывают 100500 контекстов уже существующих конструкций. И не любят новый синтаксис вводить (вроде стрелочек в этом примере), хотя это и выглядит естественнее.
99% того, что было в C# 1.0 спёрто из Java по понятным причинам, а уже Java позаимствовал эти идеи из C/C++.
Хотя копи-пастить case раздражает
«Копипастить», «раздражает», ключевое слово из 4 букв, вы серьёзно?
99% того, что было в C# 1.0 спёрто из Java по понятным причинам, а уже Java позаимствовал эти идеи из C/C++.
Тот же Рихтер считает, что родословная шарпа идет напрямую из С++, хотя и под влиянием джавы.
«Копипастить», «раздражает», ключевое слово из 4 букв, вы серьёзно?
Из 4, из 10 или из 2: какая разница? Все равно оно не несет никакого смысла. Никому не стало бы хуже от:
switch(shape)
{
Circle c:
WriteLine($"circle with radius {c.Radius}");
break;
Rectangle s when (s.Length == s.Height):
WriteLine($"{s.Length} x {s.Height} square");
break;
Rectangle r:
WriteLine($"{r.Length} x {r.Height} rectangle");
break;
default:
WriteLine("<unknown shape>");
break;
}
Но, как и в большинстве таких случаях, причина: историческая, а не потому, что так лучше. К примеру про приоритет побитового И в if-е:
Let me sum up:
- & and | are almost always used as arithmetic operators, and therefore should have higher precedence than equality, just like the other arithmetic operators.
- The «lazy» && and || have lower precedence than equality. That's a good thing. For consistency, the «eager» & and | operators should have lower precedence as well, right?
- By that argument, && and & should both have higher precedence than || and |, but that's not the case either.
Conclusion: It's a mess. Why does C# do it this way? Because that's how C does it. Why? I give you the words of the late designer of C, Dennis Ritchie:
In retrospect it would have been better to go ahead and change the precedence of & to higher than ==, but it seemed safer just to split & and && without moving & past an existing operator. (After all, we had several hundred kilobytes of source code, and maybe [three] installations....)
Ritchie's wry remark illustrates the lesson. To avoid the cost of fixing a few thousand lines of code on a handful of machines, we ended up with this design error repeated in many successor languages that now have a corpus of who-knows-how-many billion lines of code. If you're going to make a backward-compatibility-breaking change, no time is better than now; things will be worse in the future.
Кстати, то же самое относится и к копипасте брейков в конце.
Красота это pattern matching. А по поводу switch, когда писал на Java всегда использовал if. Так и не нашел варианта когда он полезен.
Переход на Scala для меня решил проблему боли от ненужного и невыразительного кода который приходится писать используя Java.
public List<String> getTypes1(Channel channel) {
channel.visit(new Channel.UnsupportedVisitor<List<String>>() {
@Override List<String> visitInternet() {
return Arrays.asList("SMS", "MAC");
}
@Override List<String> visitHomePhone() {
return Arrays.asList("CHECK_LIST");
}
@Override List<String> visitDevicePhone() {
return visitHomePhone();
}
});
}
1. Визиторы могут обязывать реализовывать все ветки. (реализация интерфейса/абстрактного класса визитора)
2. Визиторы могут бросать исключение если ветка нереализована. (перекрытие дефолтной реализации визитора, бросающего исключение во всех ветках)
Никакие языковые конструкции организации ветвления не могут обязать писать код одним из перечисленных выше способов.
Эстетическая красота: Switch vs If