要三元还是不三元?

时间:2020-03-06 15:00:11  来源:igfitidea点击:

我本人是三元运算符的拥护者:()? :;我的确意识到它的位置,但是我遇到了许多完全反对使用它的程序员,有些人经常使用它。

我们对此有何感想?我们看到过使用它有趣的代码吗?

解决方案

在我看来,仅在需要表达式的情况下使用三元运算符才有意义。

在其他情况下,似乎三元运算符会降低清晰度。

好吧,它的语法很糟糕。我发现函数ifs非常有用,并且通常使代码更具可读性。

我建议创建一个宏以使其更具可读性,但是我敢肯定有人会提出一个可怕的极端案例(就像CPP一样)。

我经常在必须使用构造函数的地方使用它,例如,新的.NET 3.5 LINQ to XML构造用于在可选参数为null时定义默认值。

人为的例子:

var e = new XElement("Something",
    param == null ? new XElement("Value", "Default")
                  : new XElement("Value", param.ToString())
);

或者(感谢星号)

var e = new XElement("Something",
    new XElement("Value",
        param == null ? "Default"
                      : param.ToString()
    )
);

无论是否使用三元运算符,确保代码可读性都是重要的事情。可以使任何构造都不可读。

我像GOTO一样对待三元运算符。它们有自己的位置,但是通常应避免使它们易于理解。

由于我们无法在每个子表达式上放置断点,因此调试起来会稍微困难一些。我很少使用它。

我同意jmulder:不应将其代替if,但应将其用于返回表达式或者表达式内部:

echo "Result: " + n + " meter" + (n != 1 ? "s" : "");
return a == null ? "null" : a;

前者只是一个例子,应该使用更好的i18n复数支持!

我会在所有可能的地方使用三元运算符,除非它使代码极难阅读,但这通常只是表明我的代码可以使用一些重构。

这总是让我感到困惑,有些人认为三元运算符是"隐藏"功能还是有些神秘。这是我开始使用C进行编程时学到的第一件事,而且我认为它根本不会降低可读性。这是语言的自然组成部分。

我认为应在需要时使用三元运算符。这显然是一个非常主观的选择,但是我发现一个简单的表达式(特别是作为返回表达式)比完整的测试要清晰得多。在C / C ++中的示例:

return (a>0)?a:0;

相比:

if(a>0) return a;
else return 0;

我们还会遇到解决方案介于三元运算符和创建函数之间的情况。例如在Python中:

l = [ i if i > 0 else 0 for i in lst ]

替代方法是:

def cap(value):
    if value > 0:
        return value
    return 0
l = [ cap(i) for i in lst ]

足够有必要在Python中(例如),可以经常看到这样的习惯用法:

l = [ ((i>0 and [i]) or [0])[0] for i in lst ]

此行使用Python中逻辑运算符的属性:它们是惰性的,如果最后一个值等于最终状态,则返回最后一个计算得出的值。

确实,这是一个风格问题;我倾向于遵循的潜意识规则是:

  • 只计算1个表达式-所以foo =(bar> baz)吗? true:false,但不是foo =(bar> baz && lotto && someArray.Contains(someValue))吗?真:假
  • 如果我将其用于显示逻辑,例如<%=(foo)吗? "是":"否"%>
  • 仅将其真正用于分配;从不流动逻辑(因此从不((foo)?FooIsTrue(foo):FooIsALie(foo)`))三元流逻辑本身就是一个谎言,请忽略最后一点。

我喜欢它,因为它简洁,优雅,适合简单的赋值操作。

我几乎从不使用三元运算符,因为每当我使用它时,它总是让我比以后尝试维护它时想的要多得多。

我喜欢避免冗长,但是当它使代码易于阅读时,我会选择冗长。

考虑:

String name = firstName;

if (middleName != null) {
    name += " " + middleName;
}

name += " " + lastName;

现在,这有点冗长,但是我发现它比:

String name = firstName + (middleName == null ? "" : " " + middleName)
    + " " + lastName;

或者:

String name = firstName;
name += (middleName == null ? "" : " " + middleName);
name += " " + lastName;

它似乎只是将太多的信息压缩到了很小的空间中,而没有弄清楚到底发生了什么事情。每当我看到使用三元运算符时,我总会发现一种替代方法似乎更容易阅读……然后再说一次,这是一种非常主观的观点,因此,如果我们和同事发现三元运算符非常易读,那就去做吧。

我喜欢在调试代码中使用运算符来打印错误值,因此不必一直查找它们。通常,我这样做是为了调试打印,一旦完成开发,这些打印就不会保留下来。

int result = do_something();
if( result != 0 )
{
  debug_printf("Error while doing something, code %x (%s)\n", result,
                result == 7 ? "ERROR_YES" :
                result == 8 ? "ERROR_NO" :
                result == 9 ? "ERROR_FILE_NOT_FOUND" :
                "Unknown");
}

我是它的忠实拥护者...适当的时候。

这样的东西很棒,就我个人而言,我觉得阅读/理解并不难:

$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"

仅将其用于简单表达式:

int a = (b > 10) ? c : d;

不要链接或者嵌套三元运算符,因为它很难阅读和混淆:

int a = b > 10 ? c < 20 ? 50 : 80 : e == 2 ? 4 : 8;

此外,在使用三元运算符时,请考虑以提高可读性的方式格式化代码:

int a = (b > 10) ? some_value                 
                 : another_value;

我最近看到了三元运算符的一种变体(很好),它使标准的"()?:"变体看起来像是一个清晰的典范:

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

或者,给出一个更具体的例子:

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

请注意,这是Javascript,因此(谢天谢地)其他语言可能无法实现这种功能。

只有当:

$ var =(简单>测试?simple_result_1:simple_result_2);

吻。

没有三元运算符,谁能赢得混淆代码竞赛?

我个人是在适当的时候使用它,但是我认为我永远不会嵌套它。它非常有用,但是它有两个缺点,因为它使代码更难阅读,并在其他操作中的某些其他语言中使用(例如Groovy的null-check)。

三元运算符"?:"只是程序上'if'构造的功能等效项。因此,只要我们不使用嵌套的"?:"表达式,任何操作的功能表示形式/针对任何操作的功能表示形式的参数都在此处适用。但是嵌套三元运算会导致代码彻底混乱(读者练习:尝试编写一个解析器来处理嵌套三元条件,我们会体会到它们的复杂性)。

但是在很多情况下,保守地使用?:运算符会导致代码实际上比其他方式更易于阅读。例如:

int compareTo(Object object) {
    if((isLessThan(object) && reverseOrder) || (isGreaterThan(object) && !reverseOrder)) {
       return 1;
    if((isLessThan(object) && !reverseOrder) || (isGreaterThan(object) && reverseOrder)) {
       return -1;
    else
      return 0;              
}

现在将其与此:

int compareTo(Object object) {
    if(isLessThan(object))
        return reverseOrder ? 1 : -1;         
    else(isGreaterThan(object))
        return reverseOrder ? -1 : 1;
    else        
       return 0;              
}

由于代码更加紧凑,因此语法上的噪音也较小,并且通过明智地使用三元运算符(仅与reverseOrder属性有关),最终结果并不是特别简洁。

对于简单的情况,我喜欢使用它。实际上,将其作为功能或者类似参数的参数进行读取/编码实际上要容易得多。另外,为了避免换行,我希望保留所有if / else内容。

在我的书中,否定它是一个很大的禁忌。

因此,继续,对于单个if / else,我将使用三元运算符。在其他情况下,常规if / else if / else(或者切换)

有趣的轶事:我已经看到,对于内联而言,优化程序的三元运算符称量"三重"运算符要比等效的if少。我在Microsoft编译器中注意到了这一点,但它可能更广泛地出现。

特别是这样的函数将内联:

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

但这不会:

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

我通常在这样的事情中使用:

before:

if(isheader)
    drawtext(x,y,WHITE,string);
else
    drawtext(x,y,BLUE,string);

after:

    drawtext(x,y,isheader==true?WHITE:BLUE,string);

我很喜欢。当我使用它时,我将其写为if-then-else:条件,正确动作和错误动作各占一行。这样,我可以轻松地嵌套它们。

例子:

x = (a == b 
     ? (sqrt(a)-2)
     : (a*a+b*b)
     );

x = (a == b 
     ? (sqrt(a)-2)
     : (a*a+b*b)
     );
x = (a == b 
     ? (c > d
        ? (sqrt(a)-2)
        : (c + cos(d))
       )
     : (a*a+b*b)
     );

对我来说,这很容易阅读。这也使添加子案例或者更改现有案例变得容易。

链式的,我可以嵌套,没那么多。

我倾向于在C中更多地使用它们,因为b / c是具有价值的if语句,因此可以减少不必要的重复或者变量:

x = (y < 100) ? "dog" :
    (y < 150) ? "cat" :
    (y < 300) ? "bar" : "baz";

而不是

if (y < 100) { x = "dog"; } 
else if (y < 150) { x = "cat"; }
else if (y < 300) { x = "bar"; } 
else              { x = "baz"; }

在这样的作业中,我发现重构和清晰的工作更少了。

另一方面,当我在红宝石中工作时,我更可能使用if ... else ... end,因为它也是一种表达方式。

x =   if (y < 100) then "dog"
    elif (y < 150) then "cat"
    elif (y < 300) then "bar"
    else                "baz"
    end

(尽管,坦白地说,对于这种简单的事情,我仍然可以使用三元运算符)。

在逻辑比较琐碎的情况下,我使用并推荐三元数来避免代码行。

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

在上述情况下,我会选择一个三进制,因为它具有较少的噪声:

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

同样,有条件的返回值也是不错的选择:

return ( piVal ) ? *piVal : *piDefVal;

我认为紧凑性可以提高可读性,进而有助于提高代码质量。

但是可读性始终取决于代码的受众。

读者必须能够理解a? b:c模式,无需任何精神努力。
如果我们不能对此进行假定,请使用长版。

我喜欢Groovy的三元运算符的特殊情况,称为Elvis运算符:?:

expr ?: default

如果该代码不为null,则该代码的计算结果为expr;如果为null,则该代码的评估结果为默认值。从技术上讲,它并不是真正的三元运算符,但它确实与此相关,并节省了大量时间/类型。

如果三元运算符最终占据了整个屏幕宽度,那么我就不会使用它。我将其保留为仅检查一个简单条件并返回单个值:

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

我们实际上在生产中有一些像这样的讨厌的代码...不好:

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

三元运算符对于简洁地生成逗号分隔的列表非常有用。这是一个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毫秒来理解该表达式,则无论如何都要这样做。

我喜欢它们,尤其是使用类型安全的语言。

我不知道这是怎么回事:

int count = (condition) ? 1 : 0;

比这更难:

int count;

if (condition)
{
  count = 1;
} 
else
{
  count = 0;
}

编辑 -

我认为三元运算符使所有内容都比其他运算符更简单,更整洁。

对于简单的任务,例如根据条件分配不同的值,它们很棒。当有更长的表达式(取决于条件)时,我不会使用它们。

如果我们和同事了解他们的工作,并且不是成批地创建的,那么我认为他们会使代码的复杂度降低并且更易于阅读,因为代码很少。

我认为三元运算符使代码更难理解的唯一情况是一行中的行数超过3或者4. 大多数人不记得它们是基于正确的优先级,当我们拥有一堆堆栈时,这会使阅读代码成为一场噩梦。

不,三元运算符不会增加复杂性。不幸的是,一些开发人员过于以命令式编程风格为导向,以至于他们拒绝(或者不会学习)其他任何东西。我不相信,例如:

int c = a < b ? a : b;

比同等"更复杂"(但更冗长):

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

甚至更别扭(我见过):

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

也就是说,请逐案仔细查看替代方案。假设一个受过良好教育的开发人员,请问哪个最简洁地表达了代码的意图,然后选择与之对应的代码。

像许多意见问题一样,答案不可避免:它取决于

对于类似的东西:

return x ? "Yes" : "No";

我认为这比以下方法更简洁(对我来说解析起来更快):

if (x) {
    return "Yes";
} else {
    return "No";
}

现在,如果条件表达式很复杂,那么三元运算不是一个好的选择。就像是:

x && y && z >= 10 && s.Length == 0 || !foo

对于三元运算符不是一个很好的候选者。

顺便说一句,如果我们是C程序员,那么GCC实际上具有扩展名,使我们可以排除三元组的if-true部分,如下所示:

/* 'y' is a char * */
const char *x = y ? : "Not set";

假设y不是NULL,它将x设置为y。好东西。

我曾经在三元运算符中建立了行不可读的阵营,但是在最近几年中,我逐渐喜欢使用它们。如果我们团队中的每个人都了解发生了什么,则单行三元运算符可以提高可读性。它是一种简洁的方法,无需花括号就可以容纳很多花括号。

我不喜欢它们的两种情况:它们是否超出了120列标记或者嵌入在其他三元运算符中。如果我们不能快速,轻松且可读地表达我们在三元运算符中所做的工作。然后使用if / else等效项。

这取决于 :)

当处理可能为空的引用时,它们很有用(顺便说一句:Java确实需要一种方法来轻松比较两个可能为空的字符串)。

当我们在一个表达式中嵌套许多三元运算符时,问题就开始了。

不(除非被滥用)。当表达式是较大表达式的一部分时,使用三元运算符通常会更清楚。

我喜欢他们。我不知道为什么,但是当我使用三元表达式时,我感觉很酷。

我见过这样的野兽((实际上是更糟糕的,因为它是isValidDate,并且也逐月检查,但我不愿意为记住整个事情而烦恼):

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

显然,一系列if语句会更好(尽管它仍然比我曾经见过的宏版本更好)。

我不介意小事情:

reportedAge = (isFemale && (Age >= 21)) ? 21 + (Age - 21) / 3 : Age;

甚至有些棘手的事情,例如:

printf ("Deleted %d file%s\n", n, (n == 1) ? "" : "s");

通过测量循环复杂度,等效使用if语句或者三元运算符。因此,按照这种方法,答案是否定的,复杂度将与以前完全相同。

通过其他措施,例如可读性,可维护性和DRY(请勿重复),任何一种选择都可能比另一种更好。

我认为这确实取决于它们所使用的上下文。

像这样的事情虽然使用起来却非常有效,但却是一种令人困惑的方式:

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

但是,这:

c = a > b ? a : b;

是完全合理的。

我个人认为,在减少过于冗长的IF语句时应使用它们。问题是人们或者被他们吓呆了,或者就被人们如此喜欢,因此他们几乎只被使用而不是IF语句。

如果我们使用三元运算符进行简单的条件赋值,我认为这很好。我已经看到它(甚至)用于控制程序流,甚至不进行分配,我认为应该避免这种情况。在这些情况下,请使用if语句。

正如其他人指出的那样,它们对于简短的简单条件非常有用。我特别喜欢它们的默认设置(例如||和或者javascript和python中的用法),例如

int repCount = pRepCountIn ? *pRepCountIn : defaultRepCount;

另一个常见用途是在C ++中初始化引用。由于引用必须在同一条语句中声明和初始化,因此不能使用if语句。

SomeType& ref = pInput ? *pInput : somethingElse;

不。它们很难阅读。 If / Else更容易阅读。

这是我的意见。你的旅费可能会改变。

有这么多答案说,这取决于。我发现,如果在快速浏览代码后看不到三元比较,则不应使用它。

作为附带的问题,我可能还要指出,由于在C语言中比较测试是一个陈述,因此它的存在实际上有点奇怪。在Icon中," if"构造(与Icon的大部分内容一样)实际上是一个表达式。因此,我们可以执行以下操作:

x[if y > 5 then 5 else y] := "Y"

...我发现它比terntern比较运算符更具可读性。 :-)

最近有人讨论了在Icon上添加?:运算符的可能性,但是一些人正确地指出,由于if的工作方式,绝对没有必要。

这意味着,如果我们可以使用C语言(或者具有ternery运算符的任何其他语言)来执行此操作,那么实际上我们根本不需要ternery运算符。

字符串someSay = bCanReadThis吗? "否":"是";

小剂量可以减少行数,并使代码更易读。尤其是在结果类似于根据计算结果将char字符串设置为"是"或者"否"的情况下。

例子:

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

和....相比:

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

像这样的简单测试中唯一可能发生的错误是分配了错误的值,但是由于条件通常很简单,因此程序员将它弄错的可能性较小。让程序输出错误的输出并不是世界末日,应该在所有代码审查,基准测试和生产测试阶段中使用。

现在,我将用我自己的观点来反驳,使用代码覆盖率指标来帮助了解测试用例的质量更加困难。在第一个示例中,我们可以测试两个分配线上的覆盖范围;如果未涵盖其中一项,则测试不会使用所有可能的代码流。

在第二个示例中,无论X的值如何,该行都将显示为已执行,因此无法确定已测试了替代路径(YMMV取决于覆盖工具的能力)。

随着测试复杂性的增加,这一点变得尤为重要。

我最近制定的确定是否应使用三元运算符的经验法则是:

  • 如果代码是在两个不同的值之间进行选择,请继续使用三元运算符。
  • 如果代码在两个不同的代码路径之间进行选择,则坚持使用if语句。

并且对代码的读者要好一些。如果要嵌套三元运算符,请格式化代码以使嵌套显而易见。

似乎没有人提到使用三元运算符的原因,至少在像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():
}

在某些情况下,我喜欢运算符,但我认为有些人倾向于过度使用它,并且它会使代码更难阅读。

我最近偶然发现了我正在修改的一些开放源代码中的这一行。

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

代替

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

我想知道在这种情况下,三元使用是否会给LINQ语句增加额外的开销。

我同意这里许多海报的观点。只要正确使用三元运算符,它就完全有效,并且不会引起歧义(公平地说,关于任何运算符/构造,我们都可以说)。

我经常在嵌入式代码中使用三元运算符来阐明我的代码在做什么。采取以下(为简化起见,过分简化)的代码示例:

片段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);
}

在这里,我正在调度一个输入或者输出请求。无论请求是读取还是写入,该过程都是相同的,只是默认的I / O大小发生了变化。在第一个示例中,我使用三元运算符来明确说明过程是相同的,并且根据I / O方向," size"字段将获得不同的值。在第二个示例中,还不是很清楚两种情况下的算法是否相同(尤其是当代码增长到比三行长得多时)。第二个示例将很难使公共代码保持同步。在这里,三元运算符在表达代码的大部分并行特性方面做得更好。

三元运算符还有另一个优势(尽管通常只是嵌入式软件的问题)。某些编译器仅在代码未"嵌套"超过一定深度时才可以执行某些优化(这意味着在函数内部,每次输入if,循环或者switch语句时,嵌套深度都会增加1,而在每次输入if,循环或者switch语句时,嵌套深度会减少1我们离开它)。有时,使用三元运算符可以最大程度地减少条件条件内的代码量(有时甚至使编译器可以优化条件条件),并可以减少代码的嵌套深度。在某些情况下,我能够使用三元运算符重新构建某些逻辑(如上面的示例中所示),并充分减少了函数的嵌套深度,以使编译器可以对其执行其他优化步骤。诚然,这是一个相当狭窄的用例,但我认为无论如何都值得一提。

三元运算符递减。如果格式正确,它们并不复杂。以@paxdiablo中的the年为例:

$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

(一天中的时间)

#define IF(x) x ?
#define ELSE :

然后,我们可以将if-then-else用作表达式:

int b = IF(condition1)    res1
        ELSE IF(condition2)  res2
        ELSE IF(conditions3) res3
        ELSE res4;