Термінальний оператор вважає шкідливим? [зачинено]


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, що включає декілька операторів повернення?

Коли це ?:доречно, а коли ні? Чи слід цього навчати або ховати від початківців?


221
Це його особливе використання :)
karmajunkie

6
Хто це зашифрував і як виглядає їх версія медіани на чотири числа? Або п’ять?
Мейсон Уілер

3
Більш правильна назва - "умовний оператор". Це просто буває найпоширенішим термінальним оператором, який використовується.
Алан Пірс

1
Про це запитували понад два роки тому в режимі stackoverflow. Ми зараз перепросимо все тут? stackoverflow.com/questions/160218/to-ternary-or-not-to-ternary
webbiedave

3
Я здивований, чому такі питання продовжують виникати. Відповідь завжди "все, що працює і читається". - остання однаково важлива.
Апоорв Хурасія

Відповіді:


234

Чи потрійний оператор злий?

Ні, це благо.

Коли?: Підходить?

Коли це щось таке просте, ви не хочете витрачати багато рядків.

а коли це не так?

Коли читабельність і чіткість коду страждає і потенціал помилки через недостатню увагу збільшується, наприклад, для багатьох прикованих операторів, як у вашому прикладі.


Тест лакмусу - це коли ви починаєте сумніватися, що ваш код легко читається та підтримується в довгостроковій перспективі. Тоді не робіть цього.


23
+1 для Коли читається і чіткість коду. З багатьма прикованими операторами, як у вашому прикладі. Приклад займає більше часу для розуміння, ніж еквівалент, якщо / else's.
sange

23
+1 браво за чудове пояснення! Розробники не схильні усвідомлювати, що деякі речі - це виклики судження, вони хочуть, щоб усе було чорно-білим. Це ганяє мене. Я стикався з багатьма людьми думки "Х - це зло, нехай ніколи його не використовує". Я вважаю за краще "X - це здорово, якщо ви використовуєте його для того, у чому це добре".
Доктор Джонс

54
Це також зло, якщо воно використовується: myVar = (деякий вираз)? правда: хибна; Аааааррх!
адамк

20
@adamk: Спробуйте це для зла:myVar = someExpression ? false : true;
Дін Хардінг

8
Як щодо (someExpression ? var1 : var2)++:-)
fredoverflow

50

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


3
Це може вважатися надмірним спрощенням, але це дуже легке керівництво, яке слід дотримуватися, і воно працює в більшості випадків.
Алан Пірс

2
Це насправді моє правило: ви ніколи не повинні їх гніздовати, інакше замініть їх на if / else, це стане зрозумілішим таким чином.
Півевезан

Якщо ви використовуєте їх і гніздите їх, то для любові до людства використовуйте дужки та простір, щоб зробити його читабельним. Якщо ще можна зробити так само потворно. Не встояйте перед бажанням показати, наскільки ви "розумні", написавши речі, які компілятори вміють читати, але люди не можуть. Колись ти будеш людиною, яка не може.
candied_orange

24

Коли це?: Підходить

  • Коли він робить ваш код більш стислим і читабельним.

а коли це не так?

  • Коли він робить ваш код нечитабельним.
  • Якщо ви робите це просто для того, щоб порадувати інструмент рефакторингу, як ReSharper, а не особу, яка повинна підтримувати код

Якщо у вас є якісь логічні або функціональні виклики всередині потрійного виразу, ви робите це жахливим.


Цей заслуговує на багато оновлень!
Півевезан

22

Одна відмінність, яку (я думаю) ніхто не зазначив - це те, що якщо не можна не повернути значення, тоді як потрійний оператор може.

Починаючи з 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"));

Це призводить до більш читабельного коду, ніж 2 різних заяви про друк. Вкладені приклади залежать: (зрозуміло? Так: ні)


11
Просто майте на увазі, що якщо ви це зробите, ви робите це пекло для того, хто повинен локалізувати вашу заявку. Звичайно, якщо це не проблема, йдіть прямо вперед.
Анон.

1
Це моє правило: 1 рівень гніздування (за певних обставин).
Олівер Вайлер

7

Зовсім не зло. Насправді це чисто , а якщо ще - ні.

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

Причиною цього є те, що будь-яка "дія" на зразок імперативного оператора if-then-else вимагає від вас відокремити декларацію змінної від її визначення та ввести стан у свою функцію.

Наприклад, у наступному коді:

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

vs.

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

Перший має дві переваги, крім того, що він коротший:

  1. x є постійною і тому пропонує набагато менше шансів ввести помилки, і потенційно її можна оптимізувати способами, якими другий ніколи не міг бути.
  2. Тип стає зрозумілим за виразом, тому компілятор може без особливих зусиль зробити висновок про те, що xпотрібно мати тип Parity.

Конфузно у функціональних мовах потрійний оператор часто називається if-then-else. У Haskell, можна сказати x = if n mod 3 == 1 then Odd else Even.


Так, це теж пункт @Benjol. Перегляньте мій коментар до його відповіді щодо цікавого способу емуляції операторів перемикання як виразів у 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структура. Ключовим є форматування.
Включення

13
Якби я бачив це в кодовій базі, я б серйозно задумався над пошуком нової роботи.
Нік Ларсен

+1 Не для цього фактичного відступу, але це найкраще рішення для цього прикладу.
Марк Херд

2
Тільки те, що ще одне випадкове автоматичне форматування викреслить все це відступ, і час для чергового раунду реструктуризації - надзвичайно продуктивний :)
nawfal

2

Далеко від зла, потрійний оператор - це знаток.

  • Найбільш корисно, коли ви хочете прийняти рішення вкладеним виразом. Класичний приклад - виклик функції:

    printf("I see %d evil construct%s in this program\n", n, n == 1 ? "" : "s");
    
  • У вашому конкретному прикладі, потрійний майже безплатний, оскільки це вираз верхнього рівня під а 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 притягує мух.

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


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заяви не відповідають.

Якщо ви переживаєте за читабельність, ви можете їх відформатувати в читаному форматі.

Якось "зло" прокралося до словника програмування. Я хотів би дізнатися, хто перший її кинув. (Насправді, у мене є підозрюваний - він є в MIT.) Я вважаю за краще, щоб у нас були об'єктивні причини для ціннісних суджень у цій галузі, а не лише про смак і прізвище людей.


1
Чи можу я підказати, хто такий підозрюваний? Просто для того, щоб трохи покращити свої знання з галузі.
mlvljr

1
@mlvljr: Ну я можу помилитися, тому краще не робити цього.
Майк Данлаве

1

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

Хоча потрійний оператор виглядає нітрофічно і дуже круто, мій досвід полягає в тому, що рядок коду буде майже неможливо підтримувати. У мого нинішнього роботодавця у нас є товари, які перевозяться майже 20 років. Я б ніде не використовував цей приклад.


1

Я не думаю, що потрійний оператор - це зло.

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


1

Все, що робить ваш код гіршим - це зло.

Якщо ви використовуєте тернар, щоб зробити свій код чистішим, тоді обов'язково використовуйте його. Іноді, як у php, це чудово робити вставки заміни, наприклад

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

Це економить кілька рядків, і це досить зрозуміло, але ваш приклад потребує принаймні кращого форматування, щоб бути зрозумілим, а потрійний не дуже хороший для багаторядкових, тоді ви можете також використовувати if / else.


1

Чи можу я це сказати? Я не можу керувати , щоб знайти це конкретне застосування троичной операції зла :

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

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


1

Найбільший виграш: показ того, що існує одна ціль дій.

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

Ви можете слідувати двома кодовими шляхами, і читач повинен уважно прочитати, щоб побачити, які дві змінні встановлюються. У цьому випадку це лише одна змінна, але читачеві належить прочитати більше, щоб зрозуміти це. Зрештою, це могло бути так:

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

З потрійним оператором зрозуміло, що встановлюється лише одна змінна.

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

На самому низькому рівні це принцип СУХОСТІ (не повторюй себе) на самому базовому рівні. Якщо ви можете вказати $fooлише один раз, зробіть це.


0

Якщо ... тоді ... інше прагне підкреслити стан і тому де-акцентувати увагу на операціях, що виконуються умовно.

потрійний оператор - навпаки, він схильний приховувати стан і тому корисний, коли виконувана операція важливіша, ніж сама умова.

Існує незначна технічна хитавка на деяких мовах, що вони не зовсім взаємозамінні через те, що один є заявою, а один виразом, наприклад, умовно ініціалізуючи consts у C ++


0

Коли це доречно, а коли ні?

Я думаю, що при розробці для однорідної групи людей немає жодної проблеми, але коли вам доводиться мати справу з людьми, які працюють на різних рівнях, такий тип oneliners лише вводить у код ще один рівень складності. Отже, моя політика щодо цього: код зрозумілий і не пояснюйте, а не короткий код і пояснюйте 123123 рази.

Чи слід цього навчати або ховати від початківців?

Мені не слід навчати початківців, скоріше віддайте перевагу їм розгадувати це, коли виникає потреба, тому він буде використовуватися лише за необхідності, а не кожного разу, якщо вам це потрібно.


0

IMO, сам оператор не є злим, але синтаксис, який використовується для нього в C (і C ++), надмірно лаконічний. IMO, Algol 60 зробив це краще, тож щось подібне:

A = x == y ? B : C;

виглядав би більше так (але дотримуючись C-подібного синтаксису взагалі):

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

Навіть при цьому надмірно глибоке гніздування може призвести до проблем з читабельністю, але принаймні А) той, хто взагалі займається програмуванням, може зрозуміти просте, а Б) люди, які це розуміють, можуть легко впоратися зі значно глибшим гніздуванням. OTOH, я також зазначу, що в LISP (наприклад) a cond- це майже схоже на потрійне твердження - не набір висловлювань, а єдиний вираз дає значення (тоді знову ж таки, більша частина LISP така.) .)


Чому б просто не зробити цього для читабельності? A = (x==y) ? B : C
Джеремі Хайлер

@Jeremy: в той час як деякі люди вважають , що круглі дужки корисні, навіть в кращому випадку вони не допомагають багато . Гніздиться більше ніж на пару глибоко, і ти ще потребуєш ретельного відступу (як мінімум), щоб не розбирати речі. Безумовно, те саме відбудеться і в Алголі, але я ніколи не кажу, що в ньому виникає проблема, як це часто я роблю в С ...
Джеррі Коффін

Я просто припустив, що всі погоджуються, що гніздування термінального оператора є поганим. Я говорив конкретно про приклади, які ви подали. Зокрема, як перший може бути схожим на другий у більшості мов.
Джеремі Хайлер

0

Магазин, який регулярно пише 600-1200 рядкових методів, не повинен мені говорити, що потрійне "важко зрозуміти". Будь-який магазин, який регулярно дає змогу оцінити галузь коду за п’ятьма умовами, не повинен мені говорити, що конкретно узагальнені умови в потрійному просторі "важко читати".


0

Коли це?: Підходить, а коли ні?

  • Якщо ви не отримаєте підвищення продуктивності, не використовуйте його; це впливає на читабельність вашого коду.
  • Використовуйте його один раз і не гніздіть його.
  • Налагоджувати важче.

Чи слід цього навчати або ховати від початківців?

Не має значення, але це не повинно бути навмисно прихованим, оскільки це не надто складно для того, щоб «новачок» навчався.


-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 - якщо ви не знаєте IsCickick до часу виконання
Martin Beckett

так ось що. Я думаю, що constце так на певних мовах. У C # constзавжди повинен бути складений відомий час. що означає const int nLegs = isChicken ? 2: 4 ;const int nLegs = true ? 2: 4 ;
звичну
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.