6 Feb 2011

Perl Programming Best Practices 2011

==== Perl Programming Best Practices 2011 by ====

Over the next few tips, we'll be covering current best practices for
programming Perl.


== Introduction ==

In 2005, Dr Damian Conway published the very popular "Perl Best
Practices" book which details 256 "best practices" for Perl programming.
While some of these "best practices" have been further improved on in
the ensuing 5 years, many of them remain excellent practices for your
coding. If you have not yet read this book, we strongly recommend that
you do so! In this sequence of Perl tips we will cover a wide selection
of current best practices, taking into account the ideas and
modernisations that Perl has undergone..


== Some best practices are obvious ==

use strict;
use warnings;

If you're not already using ``strict'' in all of your scripts, and using
``warnings'' in almost all of your scripts, you have a lot to learn, and
should probably start with Conway's book.


= 10 year old features =

Perl has had scalar filehandles and three-argument open for more than 10
years now:

# Opening a file for reading
open(my $in_fh, "<", $input_file) or die "Failed to open: $!";

# Reading from that filehandle
while(<$in_fh>) {
# do something with $_
}

close $in_fh;

# Opening a file for writing
open(my $out_fh, ">", $output_file) or die "Failed to open: $!";

# Printing to that filehandle
print {$fh} "This text will go into my file.";

close $out_fh;

Three-argument open treats the filename literally, which protects you
from a number of potential security issues that are possible with
two-argument open. Scalar filehandles - because they are just scalars -
can be passed to subroutines, put in arrays, and used as values in
hashes. Additionally, when the scalar goes out of scope before you close
the file, Perl will automatically close the file for you. None of this
is easy with package filehandles.


= Coding standards =

Ideally every organisation you work for, or project you work on, has a
pre-established, formalised set of coding standards. While these may not
be as current as you'd like, consistency is a good thing. If you don't
have a coding standard, then now is a good time to develop one. Conway's
book is a great starting place.


== Upgrade your version ==

Perl 5.6.1 is almost 10 years old now (2001-Apr-08). Perl 5.8.2 is 7
years old (2003-Nov-05). Perl 5.10.0 is 3 years old (2007-Dec-18). Perl
5.8.9 is 2 years old (2008-Dec-14). Perl 5.10.1 is 1.5 years old
(2009-Aug-22) Perl 5.12.2 is 4 months old (2010-Sep-06).

Upgrade! At the very, very least, use one of the Perl 5.8.x versions,
but if you can, upgrade to either 5.10.1 or 5.12.2. Why? Because a whole
lot of wonderful, new, awesome and *modern* features have been
incorporated into Perl in the last few years.

To use the features of your modern version of Perl, just specify your
minimum version:

use v5.10.1; # get all the things from Perl 5.10.1


= Perl 5.12.x =

By specifying any of the Perl 5.12 versions, ``strict'' is automatically
turned on by default. Thus this:

use v5.12.2;

is the same as saying:

use v5.12.2;
use strict;

This is a great improvement.


== New features ==

With Perl 5.10.0 we received a host of new features. Some of these are
mentioned here:


= say =

``say'' gives us ``print'' with an attached newline. Instead of:

print "Hello World!\n";

print "$_\n" foreach @list;

we can write:

say "Hello World!";

say foreach @list;

it's shorter, neater, and gets rid of that ugly $_.


= // (defined-or) =

Consider this subroutine:

sub item_sale {
my ($product, $price) = @_;

$price ||= $product->cost;

...
}

This example is short, neat, and almost correct. But how do we signal
that this item is being provided for free without changing the $product
object? If we pass in zero, then that's a false value, so we're going to
get the full product cost.

We could rewrite this to be:

sub item_sale {
my ($product, $price) = @_;

unless( defined($price) ) {
$price = $product->cost;
}

...
}

Or, in Perl 5.10.0 and above we can use defined-or. Defined-or works
just like regular OR, except that it tests for whether the value is
defined, rather than true.

sub item_sale {
my ($product, $price) = @_;

$price //= $product->cost;

...
}


= state =

``state'' allows us to create variables which remember their state.
These are like ``static'' variables in C. Imagine the following code to
ensure that we only show 100 images:

my $images_allowed = 100;

sub view_image {
die "Too many images" unless $images_allowed-- > 0;

...
}

As it is, other subroutines here could be used to change
$images_allowed. To prevent that, we could wrap it into a closure:

{
my $images_allowed = 100;

sub view_image {
die "Too many images" unless $images_allowed-- > 0;

...
}
}

Or, we can use a state variable:

sub view_image {
state $images_allowed = 100;
die "Too many images" unless $images_allowed-- > 0;

...
}


= given-when =

There's now a native switch for Perl, except that it's very, very cool.

given ($has) {
when (/ninja/) { $ninjas++; }
when (/pirate/) { $pirates++; }
when (/robot/) { $robots++; }
when (@other) { $other_cool_things++; }
when (%interesting) { $interesting++; }
default { $boring++; }
}

This says smart match the value in $has. If it matches against any of
the first three regular expressions, increment that variable. Otherwise
if it is an element of @other increment ``$other_cool_things++'',
otherwise if it is a key in %interesting increment $interesting. If it
matches none of these comparisons, increment $boring.

The given-when can also be used with ``foreach'':

foreach (@input) {
when (/ninja/) { $ninjas++; }
when (/pirate/) { $pirates++; }
when (/robot/) { $robots++; }
when (@other) { $other_cool_things++; }
when (%interesting) { $interesting++; }
default { $boring++; }
}


= New features: smart matching =

given-when uses smart matching to work. Smart match allows us to compare
things to determine if they match, in the most useful way possible:

$foo ~~ undef; # is $foo undefined?

$foo ~~ @array; # is $foo in @array?

$foo ~~ %hash; # is $foo a key in %hash?

$foo ~~ $coderef; # does $coderef->($foo) return true?

$foo ~~ qr{$re}; # does $foo pattern match $re?

$foo ~~ $bar; # does $foo equal $bar?


== Next ==

There are many other best practices still to be covered. In our next tip
we'll discuss exception handling, and how you can test a wide variety of
Perl versions from your home directory.

No comments:

Post a Comment