Как отключить автовивификацию в Perl? - PullRequest
8 голосов
/ 26 апреля 2009

Предположим, у вас есть ОГРОМНОЕ приложение, "разработанное";) большой командой. Вот упрощенная модель потенциальной катастрофы, которая может произойти, когда кто-то слишком глубоко проверяет структуру данных. Если невозможно полностью или автоматически отключить автовификацию, как обойти это? Большое спасибо :) !!!!

use strict; use warnings;use Data::Dumper;

my $some_ref = {akey=>{deeper=>1}};
print Dumper($some_ref );
if($some_ref->{deep}{doot} == 1){
    print 'too deep '.$/;
}

if($some_ref->{deep}){
    print 'Already in a deep doot'.$/;
}

print Dumper($some_ref );

Это выводит следующее:

$VAR1 = {
          'akey' => {
                      'deeper' => 1
                    }
        };
Use of uninitialized value in numeric eq (==) at autovivify_test.pl line 5.
Already in a deep doot
$VAR1 = {
          'deep' => {},
          'akey' => {
                      'deeper' => 1
                    }
        };

Да, я знаю, что есть предупреждение, но ... может быть уже слишком поздно.

Привет, ребята, может быть, будет полезно сказать, что мой хэш-референц ссылается на связанный HASH.

Может быть, если я реализую хороший метод FETCH, который проверяет проверки глубже в структуре, я легко решу свою проблему?


Я посмотрел на Tie :: StrictHash , Tie :: Hash и perltie . Вот упрощенная версия моего решения:

#!/usr/bin/env perl;
#test_tie.pl

package StrictHash;
use strict; use warnings;
use Tie::Hash;
our @ISA = qw(Tie::StdHash);
use Carp;

sub TIEHASH {
    my $class = shift;
    my $hash = bless {@_}, $class;
    return $hash;
}
##========================================================================
## FETCH fails if applied to a member that doesn't exist.
##========================================================================
sub FETCH {
    my ($hash, $key) = @_;
    Carp::confess "key '$key' does not exist" unless exists $hash->{$key};
    return $hash->{$key};
}
##========================================================================
package main;
use strict;use warnings;use Data::Dumper;
#Imagine StrictHash is in ./StrictHash.pm
#use StrictHash;
my %hash;
tie %hash, 'StrictHash', akey => {deeper=>1} ;  

my $some_ref =\%hash;
print Dumper($some_ref );
if($some_ref->{deep}{doot} == 1){
    print 'too deep '.$/;
}

Чего я достиг, так это прикоснулся только к одному месту в приложении. Теперь все места, такие как if ($ some_ref -> {deep} {doot}) будут вызывать die с помощью трассировки стека. Поэтому я легко их найду и исправлю. И новые сочинения такого рода НЕ будут возможны. Perl хорош и для больших приложений, вам просто нужно знать больше;).

Спасибо всем! Надеюсь, это поможет и другим.

Ответы [ 5 ]

21 голосов
/ 10 июля 2009

Относительно новым является модуль autovivification, который позволяет вам сделать это:

no autovivification;

Довольно просто.

15 голосов
/ 26 апреля 2009

Возможно, вы захотите использовать объект вместо хеша (см. Moose ) или использовать строго привязанный хеш . Или вы можете превратить предупреждения в ошибки, если вы действительно хотите:

use warnings NONFATAL => 'all', FATAL => 'uninitialized';
9 голосов
/ 26 апреля 2009

Вы можете заблокировать хеш, используя одну из функций из Hash :: Util (основной модуль).

use Hash::Util qw( lock_keys unlock_keys );

my $some_ref = { akey => { deeper => 1 } };
lock_keys %$some_ref;

print "too deep" if $some_ref->{deep}{shit} == 1;

Теперь последний оператор выдаст исключение:

Attempt to access disallowed key 'deep' in a restricted hash

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

Если вам понадобится снова добавить ключи к хешу, вы можете разблокировать его:

unlock_keys %$some_ref;
$some_ref->{foo} = 'bar'; # no exception
3 голосов
/ 26 апреля 2009

Я проголосовал @ zoul , но вы должны сделать еще один шаг вперед.

Запись тестов

Ваш код должен быть покрыт тестами, и вы должны запустить некоторые из этих тестов с

use warnings FATAL => 'uninitialized';

объявлено в самом тестовом случае. Это единственный способ решить проблему, связанную с тем, что разработчики не проверяют все заранее. Убедитесь, что их код проверен.

И сделайте еще один шаг вперед и упростите запуск ваших тестов под Devel :: Cover , чтобы получить отчет о покрытии.

cover -delete
PERL5OPT='-MDevel::Cover' prove -l 
cover -report Html_basic 

И затем проверьте, что строки кода и операторы выполняются тестами, в противном случае, если эти предупреждения будут фатальными, код просто внезапно умрет.

2 голосов
/ 27 апреля 2009

Другой вариант - использовать Data :: Diver для доступа к вашим структурам данных.

if( 1 == Dive($some_ref, qw/ deep structures are not autovivified now / )) {
    Do_Stuff();
}
...