Почему "!!" считается плохой формой в Perl?

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

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

Имеет ли оператор "bang bang" побочные эффекты или другое неожиданное поведение? Почему это или может быть, что-то считается "плохой формой"? Есть ли идиоматическая альтернатива?

Ниже приведены несколько примеров, где я мог бы считать !! приемлемым и/или желательным.

  • Фактический код в упражнении для кодирования, который является примером добавления булевых:

    while (my $line = <$F>) {
        # snip
        exists $counts{lines} and $counts{lines} += !! chomp $line;
    }
    
  • Использование логического значения в качестве хэш-ключа (явно упрощенного примера):

    sub foo {
        my ($input) = @_;
        my %responses = ( '' => "False", 1 => "True" );
        return $responses{ !! $input };
    }
    
  • Использование логического в побитовом режиме или даже pack():

    sub foo {
        my ( $a, $b, $c ) = @_;
        my $result = !!$a + (!! $b)<<1 + (!! $c)<<2;
        return $result;
    }
    
  • Вам нужно сделать typecast для использования внешней библиотекой/процессом, например, с базой данных, которая считает только определенные значения правными:

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

Ответ 1

В вашем !! используются две неясные вещи в Perl: конкретные значения, возвращаемые !, и что одно из возможных значений - doublevar (скаляр, содержащий как строку, так и число). Использование !! для объединения этих поведений, по общему признанию, является проницательным. Хотя его ценность может быть огромным плюсом, она также может быть довольно большим минусом. Не используйте функции, которые приводят к тому, что проект требует, чтобы "использование Perl было запрещено в этом проекте".

Существует множество альтернатив $count += !! (some_expression) для подсчета вхождения правных значений этого выражения. Один из них - тернарный, $count += (some_expression) ? 1 : 0. Это тоже немного неясно. Существует хороший компактный способ делать то, что вы хотите, чтобы использовать post-if:

$x++ if some_expression;

Это точно говорит о том, что вы делаете.

Ответ 2

Я бы назвал это "плохой формой", потому что это просто не нужно. Вы не нуждаетесь в логическом преобразовании в Perl. И поэтому в лучшем случае это избыточно, а в худшем случае это запутывание.

Несмотря на то, что в Perl есть несколько действительно хороших трюков, одна из самых больших проблем с языком (по крайней мере, по восприятию) заключается в том, что он скорее склонен к "писать один раз".

В конце концов - зачем вам когда-либо понадобиться "двойное отрицание" скаляра, чтобы получить логическое значение, когда вы можете просто проверить скаляр?

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

Или:

if ( 'some_string' ) { print "It was true\n" };

И это правда - вы можете написать какой-то ужасно непонятный код Perl благодаря "секретным" операторам, переменным на основе пунктуации и т.д. И это будет хорошо работать - например, я все еще считаю это шедевром: трехмерная стереограмма, источник саморепликации

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

Относительно альтернатив;

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

Это... подсчет, как часто вы успешно chomp редактировали строку, я думаю? Таким образом, в основном он просто подсчитывает количество строк на вашем входе.

Для этого я бы подумал "используйте $.". Разве я не пропустил что-то глубокое о вашем коде?

"Что делать, если он не горит?"

Хорошо, хорошо. Как насчет:

 $counts{lines}++ if foo();

Для этого:

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

Я бы написал это как:

sub foo {
    my ($input) = @_;
    return $input ? "True" : "False";
}

Для последнего условия - упаковка флагов в байт:

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

print flags ( 1, 1, 1 );

Или, может быть, если вы делаете что-то вроде этого - беря лист от того, как Fcntl делает это, определяя значения для добавления на основе позиции, так что вы не беспокоясь о проблеме позиционных аргументов:

Вы можете запросить, чтобы константы flock() (LOCK_SH, LOCK_EX, LOCK_NB и LOCK_UN) были предоставлены с помощью тега :flock.

И затем вы можете:

flock ( $fh, LOCK_EX | LOCK_NB );

Они определены поразрядным образом, поэтому они "добавляют" через оператор or, но это означает, что это идемпотентная операция, где установка LOCK_NB дважды не будет.

Например,

 LOCK_UN = 8
 LOCK_NB = 4
 LOCK_EX = 2
 LOCK_SH = 1

Если мы разберем вопрос на менее субъективный:

Я прошу причины, чтобы избежать!

  • Perl оценивает "правду" переменных, поэтому на самом деле нет необходимости в фактическом логическом логическом выражении.
  • Условные утверждения яснее их эквивалентной булевой алгебры. if ( $somecondition ) { $result++ } понятнее, чем $result += !! $somecondition.
  • Это в секретном списке операторов, потому что это неясно. Ваши будущие программисты по обслуживанию могут не оценить это.
  • !!1 - 1, !!0 - dualvar('', 0). Хотя это довольно гибко, это может удивить, тем более, что большинство людей даже не знают, что такое dualvar, тем более что ! возвращает один. Если вы используете тернар, то явные значения, которые вы получаете: $value ? 1 : 0

Ответ 3

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

  • Используйте что-то вроде $count++ if $condition.

  • Значения истины как хеш-ключи? Это просто зло. По крайней мере, это удивительно для всех, кто должен поддерживать ваш код.

  • Здесь использование !! оправдано.

  • Здесь я бы использовал тернарный оператор. На самом деле не совсем ясно, как execute ведет себя при передаче двоичного файла.

Другая ситуация, когда полностью использовать !!, - это приведение определенных значений в Boolean, например, в оператор return. Что-то вроде:

# Returns true or false.
sub has_item {
    my $self = shift;
    return !!$self->{blessed_ref};
}

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

Ответ 4

Возможно, это не так плохо для кода, который вы видите только.

Однако, ваш коллега-разработчик может однажды увидеть его, исправляя ошибку в вашем коде, и наткнулся на это. Существует два варианта

  • Он думает, что вы допустили ошибку и удалил один !, недействительный код
  • Он думает, что это всего лишь программная гниль, и удаляет обе !! без второй мысли. После рефакторинга какого-то другого кода он внезапно зависает... несоответствие типа?

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

Ответ 5

В зависимости от работы, это плохая форма для многих деловых и собеседований, а также...

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

p.s. было бы в хорошей форме спросить интервьюера, почему он считал его "плохим". Удобный совет для интервью, будет послать ему благодарность и спросить его, почему он считает это "плохой формой"