Glenn Jackman said:
Would you mind posting a short example?
Wanting to be somewhat realistic, and also to show a few variations of
the basic theme, the example I came up with isn't exactly short.
Suppose you are writing a program that takes user input (which may
come from a batch file) and produces graphics in response to commands --
something like interactive turtle graphics for instance. At some point,
a roundup of your global variables may yield this:
my $x_pos; # graphics cursor x
my $y_pos; # graphics cursor y
my $fg_color # foreground color for graphics
my $bg_color # background color for graphics
my %color_table # map color names to rgb colors
my $use_interactive; # is input from a terminal?
my $last_command; # last input line from user
my $last_reply; # last output to user
I have already sorted them in two blocks, one graphics-related and one
related to the interactive ("shell"-) functions. In a sufficiently
chaotic source they would come up in random order.
Looking over the graphics-related variables, you may decide that the
position variables are always used together, so you'll write combined
get- and set- accessors for x and y. Similarly, when a drawing
routine needs one of the foreground/background colors, it wants the other
too, so these get a combined get-accessor. The user wants to set the
colors independently, so they get individual set-accessors. That suggests
a design for the graphic status like this (untested code):
{ # graphics status
my ( $x_pos, $y_pos); # cursor
sub get_pos { return ( $x_pos, $y_pos) }
sub set_pos {
my ( $x, $y) = @_;
# check validity
( $x_pos, $y_pos) = ( $x, $y);
}
my ( $fg_color, $bg_color) # default foreground/background colors
sub set_fg_color { $fg_color = _encode_color( shift || 'black') }
sub set_bg_color { $bg_color = _encode_color( shift || 'white') }
sub get_colors { return ( $fg_color, $bg_color) }
my %color_table = (
white => 0xFF_FF_FF,
black => 0x00_00_00,
# etc
);
sub _encode_color {
my $name = shift;
return $color_table{ $name} if exists $color_table{ $name};
Carp::croak( "Invalid color name: '$name'");
}
}
That wraps a nice chunk of functionality into an identifiable package (umm,
lexical scope). Code that doesn't need to access the variables directly
goes outside:
sub shove_pos { # move position relative to current
my ( $dx, $dy) = @_;
my ( $x, $y) = get_pos();
set_pos( $x + $dx, $y + $dy);
}
Use similar considerations to corral the shell-related variables into
their lexical scope:
{ # shell status
my $use_interactive = -t STDIN; # no set-accessor, just initialize
sub is_interactive { return $use_interactive }
# more code for $last_command and $last reply
}
What I haven't shown are the necessary changes to the rest of the code
so that it goes through the accessors. The design of the accessor routines
should consider this step and make it easy, it is usually the hardest part.
In any case, the Perl compiler (under strict) will tell you if anything
anywhere still tries to access an ex-global. It's not a matter of test
coverage, you'll know when you're done.
Anno