为什么是 "!!" 在Perl中被认为是不好的形式?

Fli*_*mzy 58 perl boolean type-conversion

在最近的求职面试过程中,我提交了一些使用所谓的"秘密" !! 操作符的 Perl代码示例.后来,在讨论代码时,其中一位采访者问我为什么选择使用它,并表示它被认为是不好的形式.他没有详细说明原因.

我的团队和我一直使用这个运营商多年,没有意识到它被认为是"糟糕的形式".

"砰砰"操作员是否有副作用或其他意外行为?为什么它或者可能被某些人认为是"糟糕的形式"?有没有惯用的选择?

以下是我认为!!可以接受和/或需要的几个例子.

  1. 编码练习中的实际代码,这是添加布尔值的一个例子:

    while (my $line = <$F>) {
        # snip
        exists $counts{lines} and $counts{lines} += !! chomp $line;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用布尔值作为散列键(显然是简化示例):

    sub foo {
        my ($input) = @_;
        my %responses = ( '' => "False", 1 => "True" );
        return $responses{ !! $input };
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在按位运算中使用布尔值,甚至pack():

    sub foo {
        my ( $a, $b, $c ) = @_;
        my $result = !!$a + (!! $b)<<1 + (!! $c)<<2;
        return $result;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 您需要进行类型转换以供外部库/进程使用,例如数据库,它只考虑某些值是真实的:

    my $sth = $dbh->prepare("INSERT INTO table (flag,value) VALUES (?,?)")
    $sth->execute("i_haz_cheeseburger", !! $cheeseburger_string)
    
    Run Code Online (Sandbox Code Playgroud)

Sob*_*que 65

我称之为"糟糕的形式",因为它根本就没有必要.你不需要在Perl中进行布尔转换.所以充其量是多余的,最糟糕的是它是一种混淆.

虽然你可以在Perl中使用一些非常好的技巧,但语言的最大问题之一(至少在感知上)是它很容易"编写一次"代码.

毕竟 - 当你可以测试标量时,为什么你需要'双否定'一个标量来得到一个布尔值?

my $state = !! 'some_string';
if ( $state ) { print "It was true\n" };
Run Code Online (Sandbox Code Playgroud)

要么:

if ( 'some_string' ) { print "It was true\n" };
Run Code Online (Sandbox Code Playgroud)

这是真的 - 由于"秘密"操作符,基于标点符号的变量等,你可以编写一些可怕的无法理解的Perl代码.它会工作得很好 - 例如 - 我仍然认为这是一个杰作:3-D Stereogram,Self replicating资源

但它不是"好代码".对于"真实世界"的使用,您的代码需要清晰,易懂且易于排除故障.

关于替代品;

while (my $line = <$F>) {
    # snip
    exists $counts{lines} and $counts{lines} += !! chomp $line;
}
Run Code Online (Sandbox Code Playgroud)

这是......计算你chomp认为你成功编辑一条线的频率?所以基本上它只计算输入中的行数.

为此,我会考虑"使用$.".除非我错过了你的代码的深刻内容?

"如果它没有窒息怎么办?"

好吧,好吧.怎么样:

 $counts{lines}++ if foo();
Run Code Online (Sandbox Code Playgroud)

为了这:

sub foo {
    my ($input) = @_;
    my %responses = ( "" => "False", 1 => "True" );
    return $responses{ !! $input };
}
Run Code Online (Sandbox Code Playgroud)

我会把它写成:

sub foo {
    my ($input) = @_;
    return $input ? "True" : "False";
}
Run Code Online (Sandbox Code Playgroud)

对于最后一个条件 - 将标志打包成一个字节:

sub flags {
    my @boolean_flags = @_;
    my $flags = 0;
    for ( @boolean_flags ) {
        $flags<<=1;
        $flags++ if $_;
    }
    return $flags;
}

print flags ( 1, 1, 1 );
Run Code Online (Sandbox Code Playgroud)

或者,如果您正在做类似的事情 - Fcntl通过定义基于位置添加的值来了解它是如何做的,因此您不必担心位置参数问题:

您可以通过使用标记请求提供flock()常量(LOCK_SH,LOCK_EX,LOCK_NB和LOCK_UN):flock.

然后你可以:

flock ( $fh, LOCK_EX | LOCK_NB );
Run Code Online (Sandbox Code Playgroud)

它们按位定义,因此它们通过or运算符"添加" - 但这意味着它是幂等操作,其中设置LOCK_NB两次不会.

例如,

 LOCK_UN = 8
 LOCK_NB = 4
 LOCK_EX = 2
 LOCK_SH = 1
Run Code Online (Sandbox Code Playgroud)

如果我们将问题扩展到不那么主观:

我问的原因是要避免!!

  • Perl评估变量的"真实性",因此实际上并不需要实际的逻辑布尔值.
  • 条件语句比它们的等效布尔代数更清晰.if ( $somecondition ) { $result++ }比...更清楚$result += !! $somecondition.
  • 它在一个秘密的运营商名单上,因为它模糊不清.您未来的维护程序员可能不会那么欣赏.
  • !!11,!!0dualvar('', 0).虽然这很灵活,但它可能会让人感到惊讶,特别是因为大多数人甚至不知道双变量是什么,更不用说!返回双变量了.如果您使用三元组,则明确您获得的值:$value ? 1 : 0

  • 我不得不说 - 我想不出任何我需要"实际布尔"的场景.评估标量的真实性总是成功的.三态`true` /`false` /`undef`的奇怪边缘情况可能是唯一的例外,你有`定义`和`//`. (9认同)
  • @Flimzy - 如果你想将某个值转换为0或1,那么你的`!!'技巧就不行了.`!!`的作用是将truthy值转换为1,将非真值转换为空字符串.虽然1是perl中的最小真值,但0不是perl中的最小非真值.这将是空字符串.你的`!!'技巧不会做你想象的那样. (4认同)
  • 最后一段可能是最重要的段落. (3认同)
  • "你不需要在Perl中进行布尔转换." - 返回正确布尔值的一个明显原因是您可能不希望泄漏公共API中的实现细节. (2认同)

Dav*_*men 38

!!利用了Perl中的两个模糊的东西:!返回的特定值,以及可能的值之一是dualvar(包含字符串和数字的标量).利用!!到复合这些行为固然精辟.虽然它的简洁性可以是一个巨大的优势,它也可以是一个相当大的减号.不要使用导致项目强制要求"在此项目中禁止使用Perl"的功能.

有很多替代方法$count += !! (some_expression)可以计算该表达式的真实值的出现次数.一个是三元组$count += (some_expression) ? 1 : 0.这也有点模糊.有一个很好的紧凑方式来做你想要的,这是使用post-if:

$x++ if some_expression;
Run Code Online (Sandbox Code Playgroud)

这恰恰说明了你在做什么.

  • 同意使用post-if,但三元运算符不是"模糊"也不是钝 (2认同)

nwe*_*hof 19

整个问题有些主观,所以我想提出一个与迄今为止发布的其他答案不同的意见.我不会考虑一般的双重否定形式,但如果有更可读的替代方案,我会尽量避免它.关于你的例子:

  1. 使用类似的东西$count++ if $condition.

  2. 真值是否为哈希键?那简直就是邪恶.至少,对于必须维护代码的人来说,这是令人惊讶的.

  3. 在这里使用!!是合理的.

  4. 在这里,我将使用三元运算符.execute传递双变量时的行为并不十分清楚.

另一种可以完全正常使用的情况!!是将某些值转换为布尔值,例如在return语句中.就像是:

# Returns true or false.
sub has_item {
    my $self = shift;
    return !!$self->{blessed_ref};
}
Run Code Online (Sandbox Code Playgroud)

大多数程序员应该知道!!静态类型语言中的习惯用语,它通常用作强制转换为强制转换.如果这就是你想要做的事情并且没有不必要的副作用的危险,那就去做双重否定.

  • @Sobrique:通常认为返回调用者期望的数据类型以外的数据类型是不好的形式.如果函数测试真实性,它应该返回一个布尔值.特别是如果你考虑上面其他评论中提出的"公共API"论点.我不认为"不转换为布尔值"是这个问题的有效答案. (3认同)

San*_*ses 7

也许只有你看到的代码并没有那么糟糕.

但是,您的开发人员可能会在某天修复代码中的错误,并偶然发现错误.有两种选择

  1. 他认为你犯了一个错误并删除了一个!,使你的代码无效
  2. 他认为这只是软件腐烂,并且不加思索地删除它们!!.重构一些其他代码后,它突然挂起......类型不匹配?

无论哪种方式,它都会浪费未来开发人员的时间,除非他们碰巧熟悉它.可理解的代码永远不会为你自己(好吧,它可以帮助),但对于任何其他人将看到你的代码.

  • 在大学时代,我给了我的朋友一个困难的时间,在一行中嵌套2级深三元运算符并将其称为"紧凑和酷".我说"这是愚蠢的,不可能维持,因为我必须花30分钟手动追踪一些东西." (2认同)
  • 正如智者曾经说过的那样.调试是编码的两倍.如果您在编写时尽可能聪明,那么您将如何调试它? (2认同)