Question

What is the difference between writing sub functions and putting it all in one file vs. writing packages? Is object oriented better than procedural when it comes to Perl?

Basically looking for examples of scenarios where OO is better than procedural.

Thanks!

Was it helpful?

Solution

First just to clarify, the difference between procedural and OO is not the same as the difference between putting all of your code in one file versus putting it into separate modules. You can have separate modules that are full of functions that you call procedurally.

Where using modules, OO or procedural, is advantageous is if the code is going to be reused or if it is just a large code base. If you have a CMS with 10 different CGI scripts that all do several of the same things, such as verifying a user session perhaps, then putting that code into a separate module rather than rewriting it in every CGI makes sense. If it's a 20 line function specific to that script, then leave it in the same file.

Whether to go with OO or procedural depends on what you are doing. Most people are going to favor OO the majority of the time these days. I agree with them as I feel it helps you think about your code logically and group things together in a sane way that will be easy to manage and update later on.

OTHER TIPS

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;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top