Во время недавнего процесса собеседования я представил пример кода 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)
В вашем !!
используются две неясные вещи в Perl: конкретные значения, возвращаемые !
, и что одно из возможных значений - doublevar (скаляр, содержащий как строку, так и число). Использование !!
для объединения этих поведений, по общему признанию, является проницательным. Хотя его ценность может быть огромным плюсом, она также может быть довольно большим минусом. Не используйте функции, которые приводят к тому, что проект требует, чтобы "использование Perl было запрещено в этом проекте".
Существует множество альтернатив $count += !! (some_expression)
для подсчета вхождения правных значений этого выражения. Один из них - тернарный, $count += (some_expression) ? 1 : 0
. Это тоже немного неясно. Существует хороший компактный способ делать то, что вы хотите, чтобы использовать post-if:
$x++ if some_expression;
Это точно говорит о том, что вы делаете.
condition ? 0 : 1
(стандартная техника Java для преобразования логического числа в целое) является злодеянием.
Я бы назвал это "плохой формой", потому что это просто не нужно. Вы не нуждаетесь в логическом преобразовании в 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
Если мы разберем вопрос на менее субъективный:
Я прошу причины, чтобы избежать!
if ( $somecondition ) { $result++ }
понятнее, чем $result += !! $somecondition
.!!1
- 1
, !!0
- dualvar('', 0)
. Хотя это довольно гибко, это может удивить, тем более, что большинство людей даже не знают, что такое dualvar, тем более что !
возвращает один. Если вы используете тернар, то явные значения, которые вы получаете: $value ? 1 : 0
true
/ false
/ undef
является, пожалуй, единственным исключением, и там вы defined
и //
.
or
операции. (Как это делает C / C ++). Но у вас уже есть логическое значение or
(и ||
для логического и |
для побитового) (или xor
для логического или ^
для побитового)
Весь вопрос несколько субъективен, поэтому я хотел бы дать мнение, которое отличается от других ответов, опубликованных до сих пор. Я бы не рассматривал плохую форму двойного отрицания вообще, но я бы постарался избежать ее, если есть более читаемая альтернатива. Что касается ваших примеров:
Используйте что-то вроде $count++ if $condition
.
Значения истины как хеш-ключи? Это просто зло. По крайней мере, это удивительно для всех, кто должен поддерживать ваш код.
Здесь использование !!
оправдано.
Здесь я бы использовал тернарный оператор. На самом деле не совсем ясно, как execute
ведет себя при передаче двоичного файла.
Другая ситуация, когда полностью использовать !!
, - это приведение определенных значений в Boolean, например, в оператор return. Что-то вроде:
# Returns true or false.
sub has_item {
my $self = shift;
return !!$self->{blessed_ref};
}
Большинство программистов должны знать идиому !!
из статически типизированных языков, где она часто используется в качестве броска для bool. Если это то, что вы хотите сделать, и нет опасности нежелательных побочных эффектов, идите с двойным отрицанием.
Возможно, это не так плохо для кода, который вы видите только.
Однако, ваш коллега-разработчик может однажды увидеть его, исправляя ошибку в вашем коде, и наткнулся на это. Существует два варианта
!
, недействительный код!!
без второй мысли. После рефакторинга какого-то другого кода он внезапно зависает... несоответствие типа?В любом случае, это будет тратить время на будущих разработчиков, если они не знакомы с ним. Интеллектуальный код никогда не для себя (ну, это может помочь), но для любых других, кто когда-либо увидит ваш код.
В зависимости от работы, это плохая форма для многих деловых и собеседований, а также...
p.s. было бы в хорошей форме спросить интервьюера, почему он считал его "плохим". Удобный совет для интервью, будет послать ему благодарность и спросить его, почему он считает это "плохой формой"