Perl Programming Best Practices 2011 (part 1 - Modern Perl features)

[ Perl tips index ]
[ Subscribe to Perl tips ]

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:

	use v5.10.0;

        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.

	use v5.10.0;

        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:

	use v5.10.0;

        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.

	use v5.10.0;

        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:

	use v5.10.0;

        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:

	use v5.10.0;

        $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?

Up 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.

[ Perl tips index ]
[ Subscribe to Perl tips ]


This Perl tip and associated text is copyright Perl Training Australia. You may freely distribute this text so long as it is distributed in full with this Copyright noticed attached.

If you have any questions please don't hesitate to contact us:

Email: contact@perltraining.com.au
Phone: 03 9354 6001 (Australia)
International: +61 3 9354 6001

Valid XHTML 1.0 Valid CSS