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.
You can lock the hash using one of the functions from Hash::Util (a core module).
Now the last statement will throw an exception:
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:
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
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.
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.
Relatively new is the
autovivification
module, which lets you do this:Pretty straightforward.
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:
Another option is to use Data::Diver to access your data structures.