В тройной или нет в тройной?

Я лично сторонник троичного оператора: ()?:; Я понимаю, что это имеет свое место, но я сталкивался со многими программистами, которые полностью против того, чтобы когда-либо использовать это, и некоторые, которые используют это слишком часто.

Каковы ваши чувства по этому поводу? Какой интересный код вы видели, используя его?

54 ответа

Недавно я увидел вариант троичных операторов (ну, вроде), который делает стандартный вариант "()?:" Кажущимся образцом ясности:

var Result = [CaseIfFalse, CaseIfTrue][(boolean expression)]

или, чтобы дать более ощутимый пример:

var Name = ['Jane', 'John'][Gender == 'm'];

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

Тройной оператор опускает руки. Они не сложны, если вы правильно отформатируете. Возьмите пример високосного года из @paxdiablo:

$isLeapYear = 
   (($year % 400) == 0)
   ? 1
   : ((($year % 100) == 0)
      ? 0
      : ((($year % 4) == 0)  
         ? 1 
         : 0));

Это можно записать более кратко и сделать более читабельным с помощью этого форматирования:

//--------------test expression-----result
$isLeapYear = (($year % 400) == 0) ? 1 : 
              ((($year % 100) == 0)? 0 : 
              ((($year % 4) == 0)  ? 1 : 
                                     0));//default result

Нет, их трудно читать. Если /Else гораздо легче читать.

Это мое мнение. Ваш пробег может отличаться.

Я бы сказал, что количество условий в логическом выражении усложняет чтение. Это верно для оператора if, и это верно для троичного оператора. В идеальном мире должна быть одна сводная причина для того, чтобы взять ветку в отличие от других. Скорее всего, это действительно больше "бизнес-правило", если ваше объяснение "только тогда, когда возникает этот кластер состояний".

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

Мне нравятся троицы, потому что с оператором if можно делать что угодно.

if( object.testSomeCondition()) { 
    System.exec( "format c:" );
}
else {
    a++;
}

С другой стороны:

a += ( object.testSomeCondition() ? 0 : 1 );

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

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

    if (  user.hasRepeatedlyPressedOKWithoutAnswer() 
       && me.gettingTowardMyLunchtime( time )
       ) {
        ...
    }
    
  • Также моя цель - обработка почти в одном потоке. Поэтому я часто стараюсь не делать else и if это просто шаг от общего пути. Когда вы выполняете большую часть однопотоковой обработки, для кода гораздо сложнее скрыть ошибки в вашем коде, ожидая того единственного условия, которое выпадет и сломает вещи.

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

  • С одной оговоркой -> НЕТ КОМПЛЕКСА

    a = b == c ? ( c == d ? ( c == e ? f : g ) : h ) : i;
    

Конечно, это можно разложить на:

a = b != c ? i
  : c != d ? h
  : c == e ? f
  :          g
  ;

И это выглядит как (сжатая) таблица истинности.

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

Мое недавно сформулированное эмпирическое правило для определения того, следует ли вам использовать троичный оператор:

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

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

В небольших дозах они могут уменьшить количество строк и сделать код более читабельным; особенно если в результате получается что-то вроде установки строки символов в "Да" или "Нет" на основе результата вычисления.

Пример:

char* c = NULL;
if(x) {
  c = "true";
}else {
  c = "false";
}

по сравнению с:

char* c = x ? "Yes" : "No";

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

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

Во втором примере строка будет отображаться как выполненная независимо от значения X, поэтому вы не можете быть уверены, что тестировали альтернативный путь (YMMV в зависимости от возможностей ваших инструментов покрытия).

Это имеет большее значение с ростом сложности тестов.

Тернарный оператор чрезвычайно полезен для краткого создания списков, разделенных запятыми. Вот пример Java:

    int[] iArr = {1,2,3};
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < iArr.length; i++) {
        sb.append(i == 0 ? iArr[i] : "," + iArr[i]);
    }
    System.out.println(sb.toString());

производит: "1,2,3"

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

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

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

Это зависит:)

Они полезны при работе с возможными нулевыми ссылками (кстати: Java действительно нужен способ простого сравнения двух возможных нулевых строк).

Проблема начинается, когда вы вкладываете много тернарных операторов в одно выражение.

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

Что-то вроде этого было бы действительно запутанным, хотя и эффективным, способом их использования:

 __CRT_INLINE int __cdecl getchar (void)
{
   return (--stdin->_cnt >= 0)
          ?  (int) (unsigned char) *stdin->_ptr++
          : _filbuf (stdin);
}

Однако это:

c = a > b ? a : b;

вполне разумно.

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

Нет, троичные операторы не увеличивают сложность. К сожалению, некоторые разработчики настолько ориентированы на императивный стиль программирования, что отвергают (или не узнают) что-либо еще. Я не верю этому, например:

int c = a < b ? a : b;

является "более сложным", чем эквивалент (но более многословным):

int c;
if (a < b) {
    c = a;
} else {
    c = b;
}

или еще более неловко (что я видел):

int c = a;
if (!a < b) {
    c = b;
}

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

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

Я часто использую троичный оператор во встроенном коде, чтобы уточнить, что делает мой код. Возьмите следующие (упрощенно упрощенные) примеры кода:

Фрагмент 1:

int direction = read_or_write(io_command);

// Send an I/O
io_command.size = (direction==WRITE) ? (32 * 1024) : (128 * 1024);
io_command.data = &buffer;
dispatch_request(io_command);

Фрагмент 2:

int direction = read_or_write(io_command);

// Send an I/O
if (direction == WRITE) {
    io_command.size = (32 * 1024);
    io_command.data = &buffer;
    dispatch_request(io_command);
} else {
    io_command.size = (128 * 1024);
    io_command.data = &buffer;
    dispatch_request(io_command);
}

Здесь я отправляю запрос ввода или вывода. Процесс одинаков, независимо от того, является ли запрос чтением или записью, изменяется только размер ввода-вывода по умолчанию. В первом примере я использую троичный оператор, чтобы прояснить, что процедура такая же, и что size Поле получает различное значение в зависимости от направления ввода / вывода. Во втором примере не так сразу ясно, что алгоритм для этих двух случаев одинаков (тем более что код растет намного длиннее трех строк). Во втором примере было бы сложнее поддерживать общий код в синхронизации. Здесь троичный оператор делает лучшую работу по выражению в значительной степени параллельной природы кода.

Тернарный оператор имеет еще одно преимущество (хотя обычно это проблема встроенного программного обеспечения). Некоторые компиляторы могут выполнять определенные оптимизации только в том случае, если код не "вложен" за определенную глубину (т. Е. Внутри функции вы увеличиваете глубину вложенности на 1 каждый раз, когда вводите оператор if, loop или switch, и уменьшаете ее на 1, когда ты оставляешь это). Иногда использование тернарного оператора может минимизировать объем кода, который должен быть внутри условного (иногда до такой степени, что компилятор может оптимизировать условное выражение), и может уменьшить глубину вложенности вашего кода. В некоторых случаях мне удалось реструктурировать некоторую логику с помощью тернарного оператора (как в моем примере выше) и уменьшить вложенную глубину функции настолько, чтобы компилятор мог выполнить дополнительные шаги по оптимизации над ней. По общему признанию это - довольно узкий случай использования, но я полагал, что это стоило упомянуть в любом случае.

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

Два случая, когда они мне не нравятся: если они выходят за рамки отметки в 120 столбцов или если они встроены в другие троичные операторы. Если вы не можете быстро, легко и легко выразить то, что вы делаете в троичном операторе. Затем используйте эквивалент if/else.

Интересный анекдот: я видел, что троичный оператор весов оптимизатора менее "тяжелый" для целей встраивания, чем эквивалентный if. Я заметил это с компиляторами Microsoft, но это может быть более распространенным.

В частности, такие функции встроены:

int getSomething()
{
   return m_t ? m_t->v : 0;
}

Но это не будет

int getSomething() 
{
    if( m_t )
        return m_t->v;
    return 0;
}

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

auto myVariable = fun();  
// typeof(myVariable) == Foo!(Bar, Baz, Waldo!(Stuff, OtherStuff)).

// Now I want to declare a variable and assign a value depending on some
// conditional to it.
auto myOtherVariable = (someCondition) ? fun() : gun();

// If I didn't use the ternary I'd have to do:
Foo!(Bar, Baz, Waldo!(Stuff, OtherStuff)) myLastVariable;  // Ugly.
if(someCondition) {
    myLastVariable = fun();
} else {
    myLastVariable = gun():
}

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

int x = something == somethingElse ? 0 : -1;

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

int x = something == (someValue == someOtherVal ? string.Empty : "Blah blah") ? (a == b ? 1 : 2 ): (c == d ? 3 : 4);

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

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

where 
               (active == null ? true : 
               ((bool)active ? p.active : !p.active)) &&... 

Вместо

where ( active == null || p.active == active) &&...

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

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

int i;
if( piVal ) {
    i = *piVal;
} else {
    i = *piDefVal;
}

В приведенном выше случае я бы выбрал троичный, потому что он имеет меньше шума:

int i = ( piVal ) ? *piVal : *piDefVal;

Аналогично, условные возвращаемые значения являются хорошими кандидатами:

return ( piVal ) ? *piVal : *piDefVal;

Я думаю, что компактность может улучшить читабельность, что, в свою очередь, помогает улучшить качество кода.

Но читаемость всегда зависит от аудитории кода.

Читатели должны быть в состоянии понять a ? b : c шаблон без каких-либо умственных усилий. Если вы не можете предположить это, перейдите на длинную версию.

Мне это очень нравится. Когда я использую это, я пишу это как if-then-else: одна строка для условия, истинного действия и ложного действия. Таким образом, я могу легко их вложить.

Пример:

х = (а == б? (SQRT (а) -2): (a * a + b * b));

х = (а == б? (SQRT (а) -2): (a * a + b * b));
х = (а == б? (с> д? (SQRT (а) -2): (c + cos (d))): (a * a + b * b));

Для меня это довольно легко читать. Это также облегчает добавление дополнительных случаев или изменение существующих случаев.

Строка someSay = bCanReadThis? "Нет да";

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

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

Плохой:

($var)?1:0;

Хорошо:

($var) ? 1 : 0;

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

Я большой поклонник этого... когда это уместно.

Вещи, как это здорово, и лично мне не трудно читать / понимать:

$y = ($x == "a" ? "apple"
   : ($x == "b" ? "banana"
   : ($x == "c" ? "carrot"
   : "default")));

Я знаю, что, вероятно, многие люди съеживаются.

При использовании в PHP нужно помнить одну вещь: как она работает с функцией, возвращающей ссылку.

class Foo {
    var $bar;
    function Foo() {
        $this->bar = "original value";
    }
    function &tern() {
        return true ? $this->bar : false;
    }
    function &notTern() {
        if (true) return $this->bar;
        else      return false;
    }
}

$f = new Foo();
$b =& $f->notTern();
$b = "changed";
echo $f->bar;  // "changed"

$f2 = new Foo();
$b2 =& $f->tern();
$b2 = "changed";
echo $f2->bar;  // "original value"

Как кто-нибудь может выиграть состязание по запутанному коду без троичного оператора?!

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

Другие вопросы по тегам