views:

715

answers:

5

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}{shit} == 1){
    print 'too deep '.$/;
}

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

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 shit 
$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}{shit} == 1){
    print 'too deep '.$/;
}

What I achieved is to touch only one place in the app. Now all Places like if($some_ref->{deep}{shit}) 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.

+11  A: 

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';
zoul
+1, FATAL on uninitialized for the win.
Kent Fredric
May be, as Kent says, use warnings NONFATAL => 'all', FATAL => 'uninitialized'; is the most acceptable way to go.I may have this set up in development.I will leave the question for a little more, just to see if somebody will suggest something even smarter :).Thank you.
Berov
I just realized that I need to write just some check in my FETCH method since my reference points to a Tied HASH actually.Thanks really very much.
Berov
The only way to do this without refactoring your application is to tie the hash (which needs to be done at each level). There's a substantial performance penalty for doing this, but you're already paying the penalty anyway.
Michael Carman
+6  A: 

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
8jean
Well yes, but this is a big app. and imagine the hash is a tied Session. Thank you.
Berov
+3  A: 

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.

Kent Fredric
You don't need FATAL => 'uninitialized' for this. The Test::Warn module will allow you to test that you received the correct warnings (or absence of warnings) from your test cases.
Dave Sherohman
"Write Tests" is quite easy to say :). I started doing it, but as you know the tasks are waitingMeanwhile I need something easier to do.Thank you for the vote.I am considering going with FATAL => 'uninitialized' in development environment.
Berov
use warnings is lexically scoped, so having it on in the test does not enable it in the code being tested.
ysth
A: 

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();
}
daotoad
Thanks, I just wanted a quick way to solve my problem and discontinue further misbehaviour. I will add to my question a simplified model of the solution.
Berov
+4  A: 

Relatively new is the autovivification module, which lets you do this:

no autovivification;

Pretty straightforward.

oeuftete
I will try it. Does it work ok with tied hashes/arrays?
Berov
the current non version dependent link:http://search.cpan.org/~vpit/autovivification/
Pablo Marin-Garcia
Thanks... updated the link in the response.
oeuftete