Введение: тернарный оператор — друг или враг?
Если вы пришли в Go из мира C, C++, Java или JavaScript, вы наверняка привыкли к лаконичной конструкции:
int max = (a > b) ? a : b;
Это — тернарный оператор. Он компактный, элегантный и позволяет записать простое ветвление в одну строку. Но когда вы начинаете писать на Go, вас ждёт сюрприз: в Go нет тернарного оператора. Вообще. Никакого ? :.
Почему создатели языка пошли на такой шаг? Как теперь писать короткие условные присваивания? И главное — как это делать идиоматично, то есть в стиле, который принят в сообществе Go?
В этой статье мы разберём:
- Почему в Go нет тернарного оператора (спойлер: это осознанное решение).
- Как заменить тернарник с помощью
if-else. - Продвинутые техники: функции, map, inlining.
- Когда можно (и нужно) нарушать правило.
Поехали!
Почему Go отказался от тернарного оператора?
Ответ прост и лежит в философии языка: читаемость важнее краткости.
Роберт Гриземер (один из создателей Go) однажды сказал: «Тернарный оператор — это источник багов. Он слишком легко читается неправильно, особенно когда вложен». И это правда. Вспомните хотя бы знаменитый пример из C:
int x = (a ? b : c) ? d : e;
Попробуйте сходу понять, что он делает? Вот и создатели Go решили, что лучше пожертвовать одной строкой, но сделать код очевидным.
В Go приоритет — ясность намерений. Если вы пишете условие, пусть оно будет явным. Тернарный оператор часто превращается в «магическое число»: вы смотрите на строку и не сразу понимаете логику. В Go такого быть не должно.
Базовая замена: if-else как идиома
Самый простой и правильный способ заменить тернарник — использовать обычный if-else. Да, это занимает 3 строки вместо 1, но зато читается без напряга.
Пример: найти максимум двух чисел.
На C:
int max = (a > b) ? a : b;
На Go (идиоматично):
var max int
if a > b {
max = a
} else {
max = b
}
«Но это же длиннее!» — скажете вы. Да. Но взгляните на это с другой стороны: через месяц вы откроете код и сразу увидите логику. Никаких скобок, никаких двусмысленностей. Просто «если a больше b, то max = a, иначе max = b».
Более того, Go-сообщество считает такой стиль единственно правильным для простых условий. Не пытайтесь «схитрить» и запихнуть всё в одну строку — это будет считаться плохим тоном.
Продвинутые техники: когда хочется краткости
Иногда вы пишете код, где if-else занимает 10 строк, хотя по сути — это простое присваивание. В таких случаях можно применить несколько шаблонов, которые сохраняют идиоматичность, но делают код компактнее.
1. Вспомогательная функция
Если вам часто нужно выбирать между двумя значениями, вынесите логику в функцию. Это особенно удобно для однострочных операций.
func ternary(cond bool, a, b string) string {
if cond {
return a
}
return b
}
// Использование:
greeting := ternary(isMorning, "Доброе утро", "Добрый вечер")
Важно: не называйте функцию ternary — это считается дурным тоном, так как вы пытаетесь «протащить» тернарник через чёрный ход. Лучше дать осмысленное имя, например ifElse или choose. Но если вы пишете для себя — почему бы и нет?
2. Использование map для выбора
Если условие сводится к выбору из двух вариантов, можно использовать map с булевыми ключами. Это выглядит необычно, но иногда читается даже лучше.
var result = map[bool]string{
true: "positive",
false: "negative",
}[value > 0]
Минус: вы создаёте map на каждый вызов (если не оптимизировать), что может быть дорого. Плюс: очень кратко. Используйте только в критических местах, где скорость не важна, а читаемость — да.
3. Inline-выражение с функцией (анонимная)
Можно создать анонимную функцию прямо на месте. Это редкость, но иногда оправдано в тестах или конфигах.
var status = func() string {
if isActive {
return "active"
}
return "inactive"
}()
Это всё ещё if-else, но упакованный в одну строку. Однако помните: такая конструкция может сбить с толку новичков. Используйте с осторожностью.
Реальные примеры из практики
Давайте посмотрим, как отсутствие тернарника влияет на реальный код. Вот несколько типичных ситуаций.
Пример 1: Выбор строки в зависимости от булева флага
Плохо (попытка имитировать тернарник):
msg := "Hello"
if isFormal {
msg = "Greetings"
}
Этот код работает, но он неочевиден: вы присваиваете значение, а потом перезаписываете. Лучше:
var msg string
if isFormal {
msg = "Greetings"
} else {
msg = "Hello"
}
Или ещё лучше — сразу присвоить значение по умолчанию, а потом перезаписать, если нужно. Но это уже дело вкуса.
Пример 2: Вычисление скидки
Представьте, что нужно посчитать цену со скидкой:
// C:
float price = isPremium ? basePrice * 0.9 : basePrice;
// Go:
var price float64
if isPremium {
price = basePrice * 0.9
} else {
price = basePrice
}
Кода больше, но зато сразу видно: «если премиум, то скидка 10%, иначе полная цена».
Пример 3: Проверка ошибок (очень частый паттерн в Go)
В Go вы постоянно проверяете ошибки. Тернарник тут был бы особенно вреден, потому что он скрывает логику обработки ошибок.
// Плохая идея (если бы тернарник был):
result, err := doSomething()
if err != nil {
return "", err
}