Tuesday, February 06, 2007

Perl Best Practices

I always wanted to tidy up my perl code. But most of the time, because of the nature of work and tasks, I don't need to worry about this because my programs are mostly throw-away scripts, write and use once. But through the years that I'm programming in perl, I have accumulated a bunch of snippets here and there that are really worthy of reuse. Problem is when I look at them again, I need to re-read and re-understand my code again. Something I left out before is naming and layout convention. So with the purpose of rewriting and improving readability, consistency and maintainability of my codes, I set to follow some standards. I found this wonderful book called Perl Best Practices. It outlines coding layouts, naming conventions and well, best practices, perl style. Below are my notes for Naming Conventions taken from thisbook by Damian Conway.

#Identifiers

# Packages and Classes
# Noun::Adjective::Adjective
package Disk;
package Disk::DVD;
use base qw( Disk );
package Disk::DVD::Rewritable;
use base qw( Disk::DVD );

# Variables
# noun, preceded by zero or more adjectives
my $next_client;
my $prev_appointment;
my $estimated_net_worth;
my $final_total;
my $cumulative_total;

# Hashes and Arrays for look-up tables
# noun, preceded by zero or more adjectives, then preposition
my %title_of;
my %ISBN_for;
my @sales_from;
# and later...
while (my $month = prompt -menu => $MONTH_NAMES) {
    for my $book ( @catalog ) {
        print "$ISBN_for{$book} $title_of{$book}: $sales_from[$month]\n";
    }
}

# Subroutines and Methods

# imperative_verb noun
sub get_record;                      

# imperative_verb noun preposition
sub get_record_for;                  

# imperative_verb noun
sub eat_cookie;                      

# imperative_verb adjective noun
sub eat_previous_cookie;              

# imperative_verb noun
sub build_profile;

# imperative_verb adjective noun
sub build_execution_profile;

# imperative_verb adjective noun participle
sub build_execution_profile_using;

# Notice how clear this is:
@config_options = get_record_for($next_client);
for my $option (@config_options) {
    build_execution_profile_using($next_client, $option);
}


# Booleans
# begin with is_ or has_, but not always

sub is_valid;
sub metadata_available_for;
sub has_end_tag;

my $loading_finished;
my $has_found_bad_record;

# and later...

if (is_valid($next_record) && !$loading_finished) {
    METADATA:
    while (metadata_available_for($next_record)) {
        push @metadata, get_metadata_for($next_record);
        last METADATA if has_end_tag($next_record);
    }
}
else {
    $has_found_bad_record = 1;
}

# Reference Variables
# suffix _ref to any variable that is supposed to store a reference
sub pad_str {
    my ($text, $opts_ref) = @_;

    my $gap   = $opts_ref{cols} - length $text;
    my $left  = $opts_ref{centred} ? int($gap/2) : 0;
    my $right = $gap - $left;

    return $SPACE x $left . $text . $SPACE x $right;
}

# Arrays and Hashes
# Name arrays in the plural and hashes in the singular

my %option;
my %title_of;
my %count_for;
my %is_available;

# and later...
if ($option{'count_all'} && $title_of{$next_book} =~ m/$target/xms) {
    $count_for{$next_book}++;
    $is_available{$next_book} = 1;
}

my @events;
my @handlers;
my @unknowns;

# and later...

for my $event (@events) {
    push @unknowns, grep { ! $_->handle($event) } @handlers;
}

print map { $_->err_msg } @unknowns;

# but arrays as random-access look-up table, should be singular

my @factorial = (1);
for my $n (1..$MAX_FACT) {
    $factorial[$n] = $n * $factorial[$n-1];
}

sub factorial {
    my ($n) = @_;

    croak "Can't compute factorial($n)"
        if $n < 0 || $n > $MAX_FACT;

    return $factorial[$n];
}

# Underscores
# Use underscores to separate words in multiword identifiers

FORM:
for my $tax_form (@tax_form_sequence) {
    my $notional_tax_paid
        = $tax_form->{reported_income} * $tax_form->{effective_tax_rate};

    next FORM if $notional_tax_paid  < $MIN_ASSESSABLE;

    $total_paid
        += $notional_tax_paid - $tax_form->{allowed_deductions};
}

# Capitalization
# Distinguish different program components by case
# Use lowercase only for the names of subroutines,
# methods, variables, and labeled arguments
# $controller, new( ), src=>$fh

# Use mixed-case for package and class names
# IO::Controller

# Use uppercase for constants
# $SRC, $NODE

my $controller
        = IO::Controller->new(src=>$fh,  mode=>$SRC|$NODE);

# Abbreviations
# retain start of each word
# This example is easily comprehended:

use List::Util qw( max );

DESC:
for my $desc (@orig_strs) {
    my $len = length $desc;
    next DESC if $len > $UPPER_LIM;
    $max_len = max($max_len, $len);
}

# Utility Subroutines
# Prefix "for internal use only" subroutines with an underscore
sub _find_fib {
    my ($n) = @_;

# Walk up cache from last known value, applying Fn = Fn-1 + Fn-2...

    for my $i (@fib_for..$n) {
        $fib_for[$i] = $fib_for[$i-1] + $fib_for[$i-2];
    }

    return;
}

0 comments: