I like to put as much of my code as possible into modules. The nice thing about modules is that you can use Perl's testing tools (prove and Test::More) to easily write and manage unit tests. So if almost all your code is in modules, almost all of it is testable.
When I write a script, I like to have a thin wrapper that parses configuration and command line options in my script (probably using modules like Config::Any or Getopt::Long). The script also contains a usage
subroutine. I then add a main
subroutine. main
is very simple:
sub main {
my $cfg = shift;
my @collected_data;
for my $file ( @{ $cfg->{files} ) {
eval {
my $fh = get_handle_from_file($file);
my $xyz_data = parse_xyz_data( $fh );
push @collected_data, extract_data( $xyz_data, $cfg->{filter} );
1;
} or do {
my $e = $@;
$e = "an unknown error occurred" unless defined $e;
warn "Error parsing '$file': $e\n";
};
}
my %summary_data = summarize_data( @collected_data );
write_summary( $cfg->{summary_target} );
return;
}
Pretty much all of the supporting subroutines would live in one or more modules.
OOP is a nice way to associate data and behavior. This can make code more readable and reduce clutter.
$foo->eat_something( $sandwich )
is easier to understand than:
eat_something( $sandwich, $likes_salty, $likes_sour, $likes_sweet, $likes_spicy );
All the extra crap is bundled up in handy $foo
object attributes and travels along without cluttering up the sub call.
Of course you could do:
eat_something( $foo, $sandwich )
Where $foo is just an ordinary hash of taste preferences. This is essentially what Perl OO does, anyway. The invocant (object or class name) is passed as the first argument to each method. You do lose convenient namespacing, inheritance and dynamic method calls. Of the three costs, the convenient name spaces will be most sorely missed. IMO, inheritance is overrated and should be used only rarely. Dynamic method calls can be handy for the same reasons dispatch tables are handy.
There's nothing you can't do in OO Perl that you can't do in procedural Perl. But, OO makes certain things very convenient.
Let me close by rewriting my mythical script in OO style (I'll go a bit overboard on the OO, just for illustration):
sub main {
my $cfg = shift;
my $cd = Data::Collection->new();
for my $file ( $cfg->files ) {
eval {
# skip the step of creating a handle first. The parsing object
# can take a file and get a handle or could be passed a handle.
my $xyz_parser = File::XYZ::Parse->new( file => $file );
# The parser returns an XYZ::Data object
my $xyz_data = $xyz_parser->parse;
$cd->add_data( $xyz_data->extract_data( $cfg->filter );
1;
} or do {
my $e = $@;
$e = "an unknown error occurred" unless defined $e;
warn "Error parsing '$file': $e\n";
};
}
# Skip the step of separate summarization, since the cd object can check if
# the summary has been done, and if not generate and cache it as needed.
$cd->write_summary( $cfg->summary_target );
return;
}