问题
Suppose you have a HUGE application "develoopped" ;) by a big team. Here is a simplified model of the potential disaster that may occur when somebody checks too deep in a data structure. If not possible to disable autovification completely or in scope, how to work around this? Thank you very much :) !!!!
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 );
This outputs the following:
$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
}
};
Yes I know there is a warning, but... it may be too late.
Hey guys it will may be help to say that my hashref references a tied HASH.
May be if I implement a good FETCH method which checks for checks deeper in the structure, I will solve easily my problem?
I looked at Tie::StrictHash,Tie::Hash and perltie. Here is simplified version of my solution:
#!/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 '.$/;
}
What I achieved is to touch only one place in the app. Now all Places like if($some_ref->{deep}{doot}) will cause die with stack-trace. So I will easily find them and correct them. And new writings of this kind will NOT be possible. Perl is good for big apps too, you just need to know more ;).
Thank you all! I hope this helps others too.
回答1:
You might want to use an object instead of the hash (see Moose) or use a strict tied hash. Or you can turn warnings into errors, if you really want to:
use warnings NONFATAL => 'all', FATAL => 'uninitialized';
回答2:
Relatively new is the autovivification module, which lets you do this:
no autovivification;
Pretty straightforward.
回答3:
You can lock the hash using one of the functions from Hash::Util (a core module).
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;
Now the last statement will throw an exception:
Attempt to access disallowed key 'deep' in a restricted hash
The downside is, of course, that you'll have to be very careful when checking for keys in the hash to avoid exceptions, i.e. use a lof of "if exists ...
" to check for keys before you access them.
If you need to add keys to the hash again later you can unlock it:
unlock_keys %$some_ref;
$some_ref->{foo} = 'bar'; # no exception
回答4:
I upvoted @zoul, but you should take it one step further.
Write Tests
You should have your code covered with tests, and you should run some of those tests with
use warnings FATAL => 'uninitialized';
declared in the test-case itself. Its the only way to address the concern you have with developers not checking things in advance properly. Make sure their code is tested.
And take it another step further, and make it easy to run your tests under Devel::Cover to get a coverage report.
cover -delete
PERL5OPT='-MDevel::Cover' prove -l
cover -report Html_basic
And then check the lines of code and statements are being executed by the tests, otherwise making those warnings fatal will just make code die at an unexpected time later.
回答5:
Another option is to use Data::Diver to access your data structures.
if( 1 == Dive($some_ref, qw/ deep structures are not autovivified now / )) {
Do_Stuff();
}
来源:https://stackoverflow.com/questions/790516/how-do-i-disable-autovivification-in-perl