perlvariablesglobal-variablescode-organizationscoping

Avoiding global variables in Perl (What is the "right" way?)


When writing Perl code I frequently run into the situation where I have a large cluster of variables at the top of the file that act as "global" variables within the script. I have been writing these "global" variables in all capital letters to differentiate them from others, but I have recently been working on some very large scripts with many modules and would really like to learn a good way to minimize the use of "global" variables and make my code as safe as possible.

The situation I run into frequently is having two subroutines that share a large number of variables:

my $var1, $var2, $var3, $var4, $var5 . . .
sub sub1 {
    # uses $var1, $var2, $var3 . . .
    # may even make changes to some of these variables
}

sub sub2 {
    # also uses $var1, $var2, $var3 . . .
    # may change $var1, $var2, $var3
}

sub sub3 {
    # doesn't use $var1, $var2, $var3
    # still has access to change them
}

passing all of these variables into each subroutine and returning 4 or 5 variables can look very ugly and can quickly get difficult to keep track of all of the variables, but if I keep these variables global I run into the issue of sub3 potentially editing them when it shouldn't.

I know I can scope variables using "{}" to do this, but in my personal opinion i think this looks quite ugly:

{
    my $var1, $var2, $var3, $var4, $var5 . . .
    sub sub1 {
        # uses $var1, $var2, $var3 . . .
        # may even make changes to some of these variables
    }

    sub sub2 {
        # also uses $var1, $var2, $var3 . . .
        # may change $var1, $var2, $var3
    }
}

sub sub3 {
    # doesn't use $var1, $var2, $var3
    # no longer has access to change them
}

Also if I use "{}" to scope variables what happens if sub1 and sub2 share variables that they don't want sub3 to have access to, and then I also want sub1 and sub3 to share variables that sub2 won't have access to? Using "{}" to scope the variables that doesn't seem to be possible. An attempt to do that would look something like this:

{
    my $varx, $vary, $varz . . .
    {
        my $var1, $var2, $var3, $var4, $var5 . . .
        sub sub1 {
            # uses $var1, $var2, $var3 . . .
            # may change $var1, $var2, $var3

            # uses $varx, $vary, $varz . . .
            # may change $varx, $vary, $varz
        }
    
        sub sub2 {
            # also uses $var1, $var2, $var3 . . .
            # may change $var1, $var2, $var3

            # does not use $varx, $vary, $varz . . .
            ############################################
            # still able to change $varx, $vary, $varz #
            ############################################
        }
    }
    
    sub sub3 {
        # doesn't use $var1, $var2, $var3
        # no longer has access to change them

        # uses $varx, $vary, $varz . . .
        # may change $varx, $vary, $varz
    }
}

Is there a better way to restrict access other than using "{}"? Even if the only solution is to get into object oriented Perl feel free to throw out some ideas (If you do go into object oriented Perl please be very descriptive, I am still somewhat new to using object oriented programming).

Thanks!


Solution

  • Why you shouldn't use globals is covered pretty well in the comments and in Sobrique's answer. I'd like to give a simple (contrived) example of how to avoid globals by using objects:

    Knight.pm

    package Knight;
    
    use Moose;
    use namespace::autoclean;
    
    has 'name' => (
        is       => 'rw',
        isa      => 'Str',
        required => 1
    );
    
    has 'quest' => (
        is       => 'rw',
        isa      => 'Str',
        required => 1
    );
    
    has 'favorite_color' => (
        is       => 'rw',
        isa      => 'Str',
        required => 1
    );
    
    __PACKAGE__->meta->make_immutable;
    

    cross_bridge_of_death

    use strict;
    use warnings FATAL => 'all';
    use 5.010;
    
    use Knight;
    use List::Util qw(none);
    
    sub cross_bridge {
        my ($crosser) = @_;
    
        my @answers = ($crosser->name, $crosser->quest, $crosser->favorite_color);
        my $success = none { /\. No / } @answers; # Can't change your answer
    
        return $success;
    }
    
    my $lancelot = Knight->new(
        name           => 'Sir Lancelot of Camelot',
        quest          => 'To seek the Holy Grail',
        favorite_color => 'Blue'
    );
    
    my $galahad = Knight->new(
        name           => 'Sir Galahad of Camelot',
        quest          => 'To seek the Holy Grail',
        favorite_color => 'Blue. No yel...'
    );
    
    foreach my $knight ($lancelot, $galahad) {
        say $knight->name, ': "Auuuuuuuugh!"' unless cross_bridge $knight;
    }
    

    Output:

    Sir Galahad of Camelot: "Auuuuuuuugh!"
    

    This example uses Moose, which is just one of several modules that improve on Perl's native OO syntax.

    Now, this may seem like a lot of code just to check a few strings. After all, we could get rid of the Knight class altogether and change our cross_bridge function so that we could call it something like this:

    cross_bridge($name, $quest, $favorite_color);
    

    Or even:

    cross_bridge({
        name           => $name,
        quest          => $quest,
        favorite_color => $favorite_color
    });
    

    But then we would have to keep track of three variables instead of one. Using an object allows us to access multiple attributes through a single variable, so you can reduce the number of arguments you're passing to subroutines.

    OO is a big topic (I haven't even touched on methods, which can simplify your code even more). I would recommend reading perlootut and then browsing through the Moose manual, starting with the section Moose concepts. There are also several other popular alternatives to Moose listed in perlootut.