Это разумное использование троичного оператора?
Существуют ли какие-либо проблемы с пониманием / обслуживаемостью, возникающие в результате
inVar1 == 0 ? NULL : v.push_back(inVar1);
inVar2 == 0 ? NULL : v.push_back(inVar2);
и так далее.
Возможно, сбивает с толку идея использовать троичный оператор для выполнения программы, а не присваивать переменные, что является обычным объяснением.
Я не видел стандартов кодирования на работе, которые касаются этого использования, поэтому, хотя мне удобно делать это, я хотел бы выяснить, есть ли веская причина не делать этого.
16 ответов
Я думаю, что это сбивает с толку и намного сложнее читать, чем просто печатать;
if (inVar != 0)
v.push_back(inVar);
Мне пришлось несколько раз просканировать ваш пример, чтобы выяснить, какой будет результат с какой-то определенностью. Я бы даже предпочел однострочный оператор if() {}, чем ваш пример - и я ненавижу однострочный оператор if:)
Тернарный оператор предназначен для возврата значения.
ИМО, это не должно изменять состояние, и возвращаемое значение должно использоваться.
В другом случае используйте операторы if. Если операторы предназначены для выполнения блоков кода.
Тройка - хорошая вещь, и я обычно продвигаю ее использование.
Однако то, что вы делаете здесь, бросает тень на доверие. Это короче, да, но это излишне сложно.
Я думаю, что этого следует избегать. Вы можете использовать 1-строчный оператор if на его месте.
if(inVar1 != 0) v.push_back(inVar1);
Использование вами троичного оператора ничего не дает, и вы ухудшаете читаемость кодов.
Поскольку троичный оператор возвращает значение, которое вы не используете, это нечетный код. Использование if
гораздо понятнее в таком случае, как ваш.
Компиляторы в эти дни сделают так же быстро, как троичный оператор.
Ваша цель должна заключаться в том, насколько легко читать другому разработчику программного обеспечения.
Я голосую за
if ( inVar != 0 )
{
v.push_back( inVar );
}
почему скобки... потому что однажды вы захотите добавить что-то еще, и скобки уже готовы для вас. Большинство редакторов в эти дни поместят их в любом случае.
Как сказано в комментариях, это не относится к C++. GCC, например, выдаст ошибку в этом коде:
error: `(&v)->std::vector<_Tp, _Alloc>::push_back [with _Tp = int, _Alloc =
std::allocator<int>](((const int&)((const int*)(&inVar1))))' has type `void'
and is not a throw-expression
Тем не менее, это можно обойти путем приведения:
inVar1 == 0 ? (void)0 : v.push_back(inVar1);
inVar2 == 0 ? (void)0 : v.push_back(inVar2);
Но какой ценой? И с какой целью?
Это не похоже на использование тернарного оператора здесь более кратким, чем оператор if в этой ситуации:
inVar1 == 0 ? NULL : v.push_back(inVar1);
if(inVar1 != 0) v.push_back(inVar1);
Хотя на практике я согласен с мнением тех, кто не одобряет этот тип письма (при чтении вам необходимо проделать дополнительную работу для сканирования выражения на предмет его побочных эффектов), я бы хотел предложить
!inVar1 ?: v.push_back(inVar1);
!inVar2 ?: v.push_back(inVar2);
... если ты идешь в неизвестность, то это так. GCC позволяет x ?: y
на месте x ? x : y
,:-)
Я использую троичный оператор, когда мне нужно вызвать некоторую функцию с условными аргументами - в этом случае это лучше, чем if
,
Для сравнения:
printf("%s while executing SQL: %s",
is_sql_err() ? "Error" : "Warning", sql_msg());
с
if (is_sql_err())
printf("Error while executing SQL: %s", sql_msg());
else
printf("Warning while executing SQL: %s", sql_msg());
Я считаю, что первое более привлекательно. И это соответствует принципу СУХОЙ, в отличие от последнего - вам не нужно писать две почти одинаковые строки.
Я думаю, что иногда троичные являются необходимым злом в списках инициализаторов для конструкторов. Я использую их в основном для конструкторов, где я хочу выделить память и установить некоторый указатель, чтобы указывать на нее перед телом конструктора.
Например, предположим, что у вас есть целочисленный класс хранения, который вы хотите, чтобы в качестве входных данных использовался вектор, но внутренним представлением является массив:
class foo
{
public:
foo(std::vector<int> input);
private:
int* array;
unsigned int size;
};
foo:foo(std::vector<int> input):size(input.size()), array( (input.size()==0)?
NULL : new int[input.size])
{
//code to copy elements and do other start up goes here
}
Вот как я использую троичный оператор. Я не думаю, что это так запутанно, как некоторые люди, но я думаю, что нужно ограничить, насколько они используют это.
Я вижу, что большинство измученных троичностей (как это для аллитерации?) - это просто попытки поместить логику, которая действительно принадлежит утверждению if, в место, где утверждение if не принадлежит или не может идти.
Например:
if (inVar1 != 0)
v.push_back(inVar1);
if (inVar2 != 0)
v.push_back(inVar2);
работает, предполагая, что v.push_back является пустым, но что, если он возвращает значение, которое необходимо передать другой функции? В этом случае это должно выглядеть примерно так:
SomeType st;
if (inVar1 != 0)
st = v.push_back(inVar1);
else if (inVar2 != 0)
st = v.push_back(inVar2);
SomeFunc(st);
Но это больше, чтобы переварить такой простой кусок кода. Мое решение: определить другую функцию.
SomeType GetST(V v, int inVar1, int inVar2){
if (inVar1 != 0)
return v.push_back(inVar1);
if (inVar2 != 0)
return v.push_back(inVar2);
}
//elsewhere
SomeFunc(GetST(V v, inVar1, inVar2));
В любом случае, суть в следующем: если у вас есть какая-то логика, которая слишком замучена для троичного типа, но загромождает ваш код, если он помещен в оператор if, поместите его где-нибудь еще!
Я думаю, что вам лучше обслужить правильную структуру. Я даже предпочитаю всегда иметь фигурные скобки со своими структурами if, если мне придется добавить строки позже к условному выполнению.
if (inVar != 0) {
v.push_back(inVar);
}
inVar1 != 0 || v.push_back(inVar1);
inVar2 != 0 || v.push_back(inVar2);
Распространенный паттерн встречается в таких языках, как Perl.
Если у вас есть несколько вызовов методов в одном или обоих аргументах tenary, то это неправильно. Все строки кода, независимо от того, какое утверждение должно быть коротким и простым, в идеале не должны составляться.
Как уже упоминалось, он не короче или не более понятен, чем 1-й строчный оператор if. Тем не менее, это также больше не - и на самом деле это не так сложно уловить. Если вы знаете троичного оператора, то совершенно очевидно, что происходит.
В конце концов, я не думаю, что у кого-то возникнет проблема, если бы она была назначена переменной (даже если бы она была изменяющимся состоянием):
var2 = inVar1 == 0 ? NULL : v.push_back(inVar1);
Тот факт, что троичный оператор всегда возвращает значение - IMO - не имеет значения. Конечно, нет необходимости использовать все возвращаемые значения... в конце концов, присваивание возвращает значение.
При этом я бы заменил его оператором if, если бы натолкнулся на него с ветвью NULL.
Но, если он заменил 3 строки if:
if (inVar == 0) {
v.doThingOne(1);
} else {
v.doThingTwo(2);
}
с:
invar1 == 0 ? v.doThingOne(1) : v.doThingTwo(2);
Я мог бы оставить это... в зависимости от моего настроения.;)
Правильное выражение if более читабельно, как уже упоминали другие. Кроме того, когда вы просматриваете свой код с помощью отладчика, вы не сможете легко увидеть, какая ветвь оператора if берется, когда все в одной строке или вы используете троичное выражение:
if (cond) doIt();
cond ? noop() : doIt();
Принимая во внимание, что следующее намного лучше, чтобы пройти через (есть ли у вас скобки или нет):
if (cond) {
doIt();
}