Тройного оператора считают вредным? [закрыто]

79

Например, вы бы предпочли эту однострочную

int median(int a, int b, int c) {
    return (a<b) ? (b<c) ? b : (a<c) ? c : a : (a<c) ? a : (b<c) ? c : b;
}

или решение if / else, включающее несколько операторов return?

Когда ?:уместно, а когда нет? Следует ли этому учить или скрывать от начинающих?

оборота FredOverflow
источник
221
Это конкретное использование его :)
karmajunkie
6
Кто это закодировал, и как выглядит их версия медианы для четырех чисел? Или пять?
Мейсон Уилер
3
Более правильное имя - «условный оператор». Это просто самый распространенный троичный оператор в использовании.
Алан Пирс
1
Это было задано более двух лет назад на stackoverflow. Мы собираемся переспросить все здесь и сейчас? stackoverflow.com/questions/160218/to-ternary-or-not-to-ternary
webbiedave
3
Я удивлен, почему такие вопросы продолжают возникать. Ответ всегда таков: «что бы ни работало и читалось». - последнее не менее важно.
Апур Хурасия

Ответы:

234

Тернарный оператор злой?

Нет, это благословение.

Когда?: Уместно?

Когда это что-то настолько простое, вы не хотите тратить много строк.

а когда нет?

Когда читаемость и ясность кода страдают, а вероятность ошибки из-за недостаточного внимания возрастает, например, со многими связанными операторами, как в вашем примере.


Лакмусовый тест - это когда вы начинаете сомневаться в том, что ваш код легко читается и поддерживается в долгосрочной перспективе. Тогда не делай этого.

user8685
источник
23
+1 за Когда читаемость и четкость кода страдают. Со многими связанными операторами, как в вашем примере. Пример требует больше времени для понимания, чем эквивалентный if / else.
Санге
23
+1 браво за отличное объяснение! Разработчики не склонны понимать, что некоторые вещи являются суждениями, они хотят, чтобы все было черно-белым. Это сводит меня с ума. Я сталкивался со многими людьми с мнением: «Х - зло, давайте никогда его не использовать». Я предпочитаю «X - это здорово, если ты используешь его для того, в чем он хорош».
Восстановить Монику
54
Это также зло, если оно используется: myVar = (someExpression)? правда: ложь; Aaaaarrgh!
Адамк
20
@adamk: попробуйте это для зла:myVar = someExpression ? false : true;
Дин Хардинг
8
Как насчет (someExpression ? var1 : var2)++:-)
fredoverflow
50

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

оборота мипади
источник
3
Это может считаться упрощением, но это очень простое руководство, и оно работает в большинстве случаев.
Алан Пирс
2
На самом деле это мое эмпирическое правило: вы никогда не должны их вкладывать, в противном случае замените их на if / else, так будет яснее.
Пиовезан
Если вы используете их и вкладываете их, то для любви к человечеству используйте скобки и пробелы, чтобы сделать их читабельными. Если иное можно сделать таким же безобразным. Не поддавайтесь желанию показать, насколько вы умны, написав то, что компиляторы могут читать, а люди - нет. Когда-нибудь вы станете человеком, который не может.
candied_orange
24

Когда это?

  • Когда это делает ваш код более кратким и читабельным.

а когда нет?

  • Когда это делает ваш код нечитаемым.
  • Если вы делаете это просто для того, чтобы понравиться инструменту рефакторинга, например ReSharper, а не человеку, который должен поддерживать код

Если у вас есть какие-то логические или функциональные вызовы внутри троичного выражения, вам будет ужасно на это смотреть.

realworldcoder
источник
Этот заслуживает много голосов!
Пиовезан
22

Одно отличие, на которое (я думаю) никто не указал, заключается в том, что if-else не может вернуть значение, тогда как троичный оператор может.

Исходя из F #, мне иногда нравится использовать троичный оператор для имитации сопоставления с образцом.

match val with
| A -> 1
| B -> 3
| _ -> 0

против

return val == A ? 1 : 
       val == B ? 3 : 
       0;
оборота Бенджол
источник
Это круто. Никогда не думал об этом.
Рей Миясака
+1 @ Benjol: я собирался указать на то же самое (что в F # все является выражением, включая if / elif / else). Я использую троичные, как в вашем примере тоже, пока также не обнаружил :). Еще одна вещь, которую я делал в Javascript, возможно, излишне: var res = function() {switch(input) {case 1: return "1"; case 2: return "2"; ...}}()эмулировать переключатели как выражения.
Стивен Свенсен
@Stephen, я собирался использовать слова expressionи , statementно я всегда беспокоюсь я получу их навыворот и сделать дураком :)
Benjol
@Benjol: я знаю, что ты имеешь в виду!
Стивен Свенсен
6
Также полезно в C и C ++ для инициализации constпеременных, которые не могут быть изменены позже.
Дэвид Торнли
13

Пример (IMHO) правильного использования:

printf("Success in %d %s\n", nr_of_tries, (nr_of_tries == 1 ? "try" : "tries"));

Это приводит к более удобочитаемому коду, чем наличие двух различных операторов печати. Вложенные примеры зависят: (понятно? Да: нет)

Хенно Брандсма
источник
11
Просто помните, что если вы делаете это, вы превращаете это в ад для тех, кто должен локализовать ваше приложение. Конечно, если это не проблема, продолжайте.
Анон.
1
Это мое правило: 1 уровень вложенности (при некоторых обстоятельствах).
Оливер Вейлер
7

Абсолютно не зло. На самом деле, это чисто , а если-то-иначе нет.

В функциональных языках, таких как Haskell, F #, ML и т. Д., Утверждения if-then-else считаются злыми.

Причина этого в том, что любое «действие», такое как императивный оператор if-then-else, требует, чтобы вы отделяли объявление переменной от ее определения и вводили состояние в вашу функцию.

Например, в следующем коде:

const var x = n % 3 == 1
    ? Parity.Even
    : Parity.Odd;

против

Parity x;
if (n % 3 == 1)
    x = Parity.Even;
else
    x = Parity.Odd;

Первый имеет два преимущества, кроме того, что он короче:

  1. x является константой, и поэтому дает гораздо меньше шансов на появление ошибок, и потенциально может быть оптимизировано так, как никогда не может быть второй.
  2. Тип становится понятным из выражения, поэтому компилятор может легко определить, что xдолжно быть типа Parity.

Заблуждение, что в функциональных языках троичный оператор часто называют if-then-else. В Хаскеле, вы могли бы сказать x = if n mod 3 == 1 then Odd else Even.

Рей Миясака
источник
да, это тоже точка @Benjol. Смотрите мой комментарий к его ответу для забавного способа эмулировать операторы switch как выражения в Javascript.
Стивен Свенсен
7

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

Тернарные операторы не являются злыми при правильном использовании. Они даже не должны быть отдельными строками; Длинный, хорошо отформатированный, может быть очень ясным и понятным:

return
      ( 'a' == $s ) ? 1
    : ( 'b' == $s ) ? 2
    : ( 'c' == $s ) ? 3
    :                 4;

Мне нравится это лучше, чем эквивалентная цепочка if / then / else:

if ( 'a' == $s ) {
    $retval = 1;
}
elsif ( 'b' == $s ) {
    $retval = 2;
}
elsif ( 'c' == $s ) {
    $retval = 3;
}
else {
    $retval = 4;
}

return $retval;

Я переформатирую их в:

if    ( 'a' == $s ) { $retval = 1; }
elsif ( 'b' == $s ) { $retval = 2; }
elsif ( 'c' == $s ) { $retval = 3; }
else                { $retval = 4; }

return $retval;

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

жестяной человек
источник
Почему я не могу поставить переносы в комментариях? Arrgghh!
Кристофер Махан
3

ReSharper в VS.NET иногда предлагает заменить if...elseна ?:оператора.

Кажется, что ReSharper предлагает, только если условия / блоки ниже определенного уровня сложности, в противном случае он остается неизменным if...else.

Уве Кейм
источник
4
Еще одна замечательная особенность, которую я люблю в ReSharper
Anonymous Type
2

Это можно переформатировать так, чтобы оно выглядело так же красиво, как комбинация if / else:

int median(int a, int b, int c)
{
    return
        (a<b)
        ?
            (b<c)
            ? b
            :
                (a<c)
                ? c
                : a
        :
            (a<c)
            ? a
            :
                (b<c)
                ? c
                : b;
}

Но проблема в том, что я не совсем уверен, правильно ли я понял отступ, чтобы представлять, что на самом деле произойдет. :-)

Зан Рысь
источник
3
+1 Я думаю, что это намного лучше, чем эквивалентная if-elseструктура. Ключ форматирования.
Orbling
13
Если бы я увидел это в кодовой базе, я бы серьезно подумал о поиске новой работы.
Ник Ларсен
+1 Не для этого фактического отступа, но это лучшее решение для этого примера.
Марк Херд
2
Только то, что другое случайное автоматическое форматирование уничтожило бы все эти отступы и время для следующего раунда реструктуризации - чрезвычайно продуктивного :)
nawfal
2

Отнюдь не злой, троичный оператор - находка.

  • Это наиболее полезно, когда вы хотите принять решение во вложенном выражении. Классическим примером является вызов функции:

    printf("I see %d evil construct%s in this program\n", n, n == 1 ? "" : "s");
    
  • В вашем конкретном примере троичный является почти безвозмездным, так как это выражение верхнего уровня под a return. Вы можете поднять условное выражение до уровня оператора, не дублируя ничего, кроме returnключевого слова.

NB Ничто никогда не сделает этот конкретный алгоритм для медианы легко читаемым.

Норман Рэмси
источник
Трудно быть настолько читабельным, что ты не можешь сделать его лучше. printf("I see %d evil construct%s in this program\n", n, "s" unless (n == 1) "s");
Pacerier
2
  1. Если не принимать во внимание «злые» аргументы, то по своему опыту я обнаружил высокую корреляцию между использованием программистом тернарного оператора и вероятностью того, что его или ее вся кодовая база будет трудна для чтения, отслеживания и обслуживания (если это не будет явным недокументированным). Если программист больше заботится о сохранении нескольких 1-2-символьных строк, чем кто-либо, способный понять его или ее код, то любая незначительная путаница в понимании троичного утверждения обычно является верхушкой айсберга.

  2. Тернарные операторы привлекают магические числа, как s ** t привлекает мух.

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

user8865
источник
2

Вот пример того, когда оно является злом:

oldValue = newValue >= 0 ? newValue : oldValue;

Это сбивает с толку и расточительно. Компилятор может оптимизировать второе выражение (oldValue = oldValue), но почему кодер сделал это в первую очередь?

Еще один унылый:

thingie = otherThingie != null ? otherThingie : null;

Некоторые люди просто не должны быть программистами ...

Грег говорит, что эквивалентно, если заявление «шумно». Это если вы пишете это с шумом. Но это то же самое, если можно записать как:

if ('a' == $s) return 1;
if ('b' == $s) return 2;
if ('c' == $s) return 3;
return 4;

Который не шумнее, чем троичный. Интересно, есть ли в тройных сокращениях; все выражения оцениваются?

оборота хапыбрийский
источник
Ваш второй пример напоминает мне о if (x != 0) x = 0;...
fredoverflow
2

Зло? Смотри, они просто разные.

ifэто утверждение. (test ? a : b)это выражение. Они не одно и то же.

Существуют выражения для выражения значений. Заявления существуют для выполнения действий. Выражения могут появляться внутри операторов, но не наоборот. Таким образом, вы можете использовать троичные выражения в других выражениях, например, для терминов в суммировании, или для аргументов метода, и так далее. Вам не нужно , но вы можете, если хотите . В этом нет ничего плохого. Некоторые люди могут сказать, что это зло, но это их мнение.

Одно из значений троичного выражения заключается в том, что оно позволяет обрабатывать как истинные, так и ложные случаи. ifзаявления не делают.

Если вы беспокоитесь о читабельности, вы можете отформатировать их.

Каким-то образом «зло» вползло в словарь программирования. Я хотел бы знать, кто первым бросил это. (На самом деле, у меня есть подозреваемый - он в Массачусетском технологическом институте.) Я бы предпочел, чтобы у нас были объективные причины для оценочных суждений в этой области, а не только вкус людей и их имена.

оборота Майк Данлавей
источник
1
Могу ли я получить подсказку о том, кто является подозреваемым? Просто чтобы немного улучшить свои знания в этой области.
mlvljr
1
@mlvljr: Ну, я могу ошибаться, так что лучше не делать этого.
Майк Данлавей
1

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

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

Tangurena
источник
1

Я не думаю, что троичный оператор это зло.

Вот гоча, которая поставила меня в тупик, хотя. Я был программистом на C для многих (10+) и в конце 1990-х перешел на веб-программирование приложений. Как веб-программист, я вскоре столкнулся с PHP, который также имеет троичный оператор. У меня была ошибка в PHP-программе, которую я, наконец, отследил до строки кода с вложенным троичным оператором. Оказывается, что троичный оператор PHP связан слева направо, но троичный оператор C (к которому я привык) ассоциируется справа налево.

leed25d
источник
1

Все, что делает ваш код более уродливым, является злом.

Если вы используете троичный код, чтобы сделать свой код чище, тогда обязательно используйте его. Иногда в таких как php замечательно делать встроенные замены, например

"Hello ".($Male?"Mr":"Ms")." $Name

Это экономит несколько строк, и это довольно ясно, но ваш пример требует, по крайней мере, лучшего форматирования, чтобы быть ясным, и троичный не очень хорош для многострочных, тогда вы могли бы также использовать if / else.

user11134
источник
1

Могу я это сказать? Мне не удается найти это конкретное применение троичной операции зла :

  1. операция, которую он выполняет, довольно тривиальна, и как только вы пройдете ее, вряд ли появятся какие-то ошибки;
  2. что он делает, четко указано в названии функции;
  3. взятие> 1 строки для чего-то столь очевидного и такого явно не улучшенного в будущем (если алгоритм магической медианы не будет обнаружен до сих пор).

Пожалуйста, помилуй, моя репутация уже довольно жалкая.

cbrandolino
источник
1

Крупнейшая победа: показывает, что существует единственная цель действия.

if ( $is_whatever )
    $foo = 'A';
else
    $foo = 'B';

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

if ( $is_whatever )
    $foo = 'A';
else
    $bar = 'B';

С троичным оператором ясно, что устанавливается только одна переменная.

$foo = $is_whatever ? 'A' : 'B';

На самом низком уровне это принцип СУХОЙ (не повторяй себя) в своей самой базовой форме. Если вы можете указать $fooтолько один раз, сделайте это.

Энди Лестер
источник
0

Если ... то ... еще раз имеет тенденцию подчеркивать условие и, следовательно, снимать акцент с выполняемых операций.

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

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

JK.
источник
0

Когда уместно, а когда нет?

Я думаю, что при разработке для однородной группы людей не возникает никаких проблем, но когда вам приходится иметь дело с людьми, которые работают с разными уровнями, этот вид oneliners только вводит дополнительный уровень сложности в код. Итак, моя политика в этом вопросе: код ясен и не объясняет, а не код короток и объясняет 123123 раза.

Следует ли этому учить или скрывать от начинающих?

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

guiman
источник
0

IMO, сам оператор не является злом, но синтаксис, используемый для него в C (и C ++), чрезмерно лаконичен. ИМО, Алгол 60 сделал это лучше, так что-то вроде этого:

A = x == y ? B : C;

будет выглядеть примерно так (но в целом придерживаться C-подобного синтаксиса):

A = if (x==y) B else C;

Несмотря на это, чрезмерно глубокое вложение может привести к проблемам с читабельностью, но, по крайней мере, A) любой, кто вообще занимался программированием, может понять это просто, и B) люди, которые понимают его, могут довольно легко справиться со значительно более глубоким вложением. ОТО, я бы также отметил, что в LISP (например) a condво многом напоминает троичное выражение - не набор операторов, а одно выражение, которое дает значение (опять же, большая часть LISP такая же). .)

Джерри Гроб
источник
Почему бы просто не сделать это для удобства чтения? A = (x==y) ? B : C
Джереми Хейлер
@Jeremy: в то время как некоторые люди считают , что круглые скобки полезны, даже в лучшем случае они не помогают много . Гнездитесь глубже, чем пара, и вам все еще нужно тщательное углубление (как минимум), чтобы все было улажено. Несомненно, в конечном итоге то же самое произойдет в Алголе, но я никогда не говорю, что в нем возникают проблемы, как я часто это делаю в C ...
Джерри Коффин
Я просто предположил, что все согласны с тем, что вложенность троичного оператора плохая. Я говорил конкретно о приведенных вами примерах. В частности, как первый может больше походить на второй в большинстве языков.
Джереми Хейлер
0

Магазин, который регулярно пишет 600-1200-строчные методы, не должен говорить мне, что троица "трудна для понимания". Любой магазин, который регулярно допускает пять условий для оценки ветви кода, не должен говорить мне, что конкретно сформулированные условия в троичной системе «трудны для чтения».

лесоруб
источник
0

Когда?: Уместно, а когда нет?

  • Если вы не получаете прирост производительности, не используйте его; это влияет на читабельность вашего кода.
  • Используйте его один раз и не вкладывайте его.
  • Сложнее отлаживать.

Следует ли этому учить или скрывать от начинающих?

Неважно, но это не должно быть преднамеренно скрыто, поскольку это не слишком сложно для "новичка", чтобы учиться.

Amir Rezaei
источник
-2

В вашем примере:

def median(a, b, c):
    if a < b < c: return b
    if a < c < b: return c
    if b < a < c: return a
    if b < c < a: return c
    if c < a < b: return a
    if c < b < a: return b

очень просто читать и очевидно. Переменная между <<является возвращаемым значением.

Обновить

то же самое, но меньше строк кода. Все еще просто, я думаю.

def median(a, b, c):
    if b<a<c or c<a<b: return a
    if a<b<c or c<b<a: return b
    if a<c<b or b<c<a: return c
Кристофер Махан
источник
Это требует 12 сравнений в худшем случае ...
fredoverflow
1
Возможно, но это разборчиво.
Кристофер Махан
-2

Это также необходимо для const

const int nLegs  = isChicken ? 2: 4 ;
Мартин Беккет
источник
Странный. Я думаю, что это C ++ или что-то. Я думал, что const всегда был постоянной времени компиляции (как в C #)
nawfal
@nawfal - если вы не знаете, isChicken до времени выполнения
Мартин Беккет,
да вот что. Я думаю, что constтак на определенных языках. В C # constвсегда должно быть время компиляции известного значения. что означает не const int nLegs = isChicken ? 2: 4 ;будет работать, но const int nLegs = true ? 2: 4 ;будет
Nawfal