Programming and programming well are related, but distinct skills. If we only wrote programs once and never had to modify or maintain them, if our programs never had bugs, if we never had to choose between using more memory or taking more time, and if we never had to work with other people, we wouldn't have to worry about how well we program. To program well, you must understand the differences between potential solutions based on specific priorities of time, resources, and future plans.
Writing Perl well means understanding how Perl works. It also means developing a sense of good taste. To develop that skill, you must practice writing and maintaining code and reading good code. There are no shortcuts--but you can improve the effectiveness of your practice by following a few guidelines.
The easier your program is to understand and to modify, the better. This is maintainability. Set aside your current program for six months, then try to fix a bug or add a feature. The more maintainable the code, the less artificial complexity you will encounter making changes.
To write maintainable Perl you must:
The more duplication in your system, the more work it is to make a necessary change, and the more likely you will forget to make a change in every place necessary. The less duplication in your system, the more likely you've found an effective design for your problem. The best designs allow you to add features while removing code overall.
You can't always avoid the dark corners of Perl, and some problems require cleverness to solve effectively. Only good taste and experience will help you evaluate the appropriate level of cleverness. As a rule of thumb, if you're prouder of explaining your solution to your coworkers than you are of solving a problem, your code may have unnecessary complexity.
If you do need clever code, encapsulate it behind a simple interface and document your cleverness very well.
Simplicity means that you've solved the problem at hand effectively without adding anything you don't need. This is no excuse to avoid error checking or verification or validation or security. Instead it's a reminder to think about what's really important. Sometimes you don't need frameworks, or objects, or complex data structures. Sometimes you do. Simplicity means knowing the difference.
Perl steals ideas from other languages as well as from the wild world outside of programming. Perl tends to claim these ideas by making them Perlish. To write Perl well, you must know how experienced Perl programmers write it.
Similarly, CPAN distributions such as Perl::Critic
and Perl::Tidy
and CPAN::Mini
can make your work simpler and easier.
Writing a few lines of code to solve a problem someone else posted is a great way to learn.
Knowing Perl's syntax and semantics is only the beginning. You can only achieve good design if you follow habits to encourage good design.
Modularity also forces you to manage different levels of abstraction; you must consider how the entities of your system work together. There's no better way to learn the value of abstraction than having to revise systems into effective abstractions.
Whenever possible, search the CPAN first--and ask your fellow community members--for advice on solving your problems. You may even report a bug, or submit a patch, or produce your own distribution on the CPAN. Nothing demonstrates you're an effective Perl programmer more than helping other people solve their problems.
Programming would be simpler if everything always worked as intended. Unfortunately, files you expect to exist don't. Sometimes you run out of disk space. Your network connection vanishes. The database stops accepting new data.
Exceptional cases happen, and robust software must handle those exceptional conditions. If you can recover, great! If you can't, sometimes the best you can do is retry or at least log all of the relevant information for further debugging. Perl 5 handles exceptional conditions through the use of exceptions: a dynamically-scoped form of control flow that lets you handle errors in the most appropriate place.
Consider the case where you need to open a file for logging. If you cannot open the file, something has gone wrong. Use die
to throw an exception:
sub open_log_file
{
my $name = shift;
open my $fh, '>>', $name
or die "Can't open logging file '$name': $!";
return $fh;
}
die()
sets the global variable $@
to its argument and immediately exits the current function without returning anything. If the calling function does not explicitly handle this exception, the exception will propagate upwards to every caller until something handles the exception or the program exits with an error message.
Uncaught exceptions eventually terminate the program. Sometimes this is useful; a system administration program run from cron (a Unix jobs scheduler) might throw an exception when the error logs have filled; this could page an administrator that something has gone wrong. Yet many other exceptions should not be fatal; good programs can recover from them, or at least save their state and exit more cleanly.
To catch an exception, use the block form of the eval
operator:
# log file may not open
my $fh = eval { open_log_file( 'monkeytown.log' ) };
As with all blocks, the block argument to eval
introduces a new scope. If the file open succeeds, $fh
will contain the filehandle. If it fails, $fh
will remain undefined, and Perl will move on to the next statement in the program.
If open_log_file()
called other functions which called other functions, and if one of those functions threw its own exception, this eval
could catch it, if nothing else did. There is no requirement that your exception handlers catch only those exceptions you expect.
To check which exception you've caught (or if you've caught an exception at all), check the value of $@
:
# log file may not open
my $fh = eval { open_log_file( 'monkeytown.log' ) };
# caught exception
if ($@) { ... }
Of course, $@
is a global variable. For optimal safety, local
ize its value before you attempt to catch an exception:
local $@;
# log file may not open
my $fh = eval { open_log_file( 'monkeytown.log' ) };
# caught exception
if ($@) { ... }
You may check the string value of $@
against expected exceptions to see if you can handle the exception or if you should throw it again:
if (my $exception = $@)
{
die $exception unless $exception =~ /^Can't open logging file/;
$fh = log_to_syslog();
}
Rethrow an exception by calling die()
again, passing $@
.
You may find the idea of using regular expressions against the value of $@
distasteful; you can also use an object with die
. Admittedly, this is rare. $@
can contain any arbitrary reference, but in practice it seems to be 95% strings and 5% objects.
As an alternative to writing your own exception system, see the CPAN distribution Exception::Class
.
Using $@
correctly can be tricky; the global nature of the variable leaves it open to several subtle flaws:
local
ized uses further down the dynamic scope may reset its valueeval
and change its valueDIE
signal handler) may change its value when you do not expect it
Writing a perfectly safe and sane exception handler is difficult. The Try::Tiny
distribution from the CPAN is short, easy to install, easy to understand, and very easy to use:
use Try::Tiny;
my $fh = try { open_log_file( 'monkeytown.log' ) }
catch { ... };
Not only is the syntax somewhat nicer than the Perl 5 default, but the module handles all of those edge cases for you without your knowledge.
Perl 5 has several exceptional conditions you can catch with an eval
block. perldoc perldiag
lists them as "trappable fatal errors". Most are syntax errors thrown during compilation. Others are runtime errors. Some of these may be worth catching; syntax errors rarely are. The most interesting or likely exceptions occur for:
If you have enabled fatal lexical warnings (Registering Your Own Warnings), you can catch the exceptions they throw. The same goes for exceptions from autodie
(The autodie Pragma).
Perl 5's extension mechanism is modules (Modules). Most modules provide functions to call or they define classes (Moose), but some modules instead influence the behavior of the language itself.
A module which influences the behavior of the compiler is a pragma. By convention, pragmas have lower-case names to differentiate them from other modules. You've heard of some before: strict
and warnings
, for example.
A pragma works by exporting specific behavior or information into the enclosing static scope. The scope of a pragma is the same as the scope of a lexical variable. In a way, you can think of lexical variable declaration as a sort of pragma with funny syntax. Pragma scope is clearer with an example:
{
# $lexical is not visible; strict is not in effect
{
use strict;
my $lexical = 'available here';
# $lexical is visible; strict is in effect
...
}
# $lexical is again not visible; strict is not in effect
}
Just as lexical declarations affect inner scopes, so do pragmas maintain their effects on inner scopes:
# file scope
use strict;
{
# inner scope, but strict still in effect
my $inner = 'another lexical';
...
}
Pragmas have the same usage mechanism as modules. As with modules, you may specify the desired version number of the pragma and you may pass a list of arguments to the pragma to control its behavior at a finer level:
# require variable declarations; prohibit bareword function names
use strict qw( subs vars );
Within a scope you may disable all or part of a pragma with the no
builtin:
use strict;
{
# get ready to manipulate the symbol table
no strict 'refs';
...
}
Perl 5 includes several useful core pragmas:
strict
pragma enables compiler checking of symbolic references, the use of barewords, and the declaration of variableswarnings
pragma enables optional warnings for deprecated, unintended, and awkward behaviors that are not necessarily errors but may produce unwanted behaviorsutf8
pragma enables the use of the UTF-8 encoding of source codeautodie
pragma (new in 5.10.1) enables automatic error checking of system calls and builtins, reducing the need for manual error checkingconstant
pragma allows you to create compile-time constant values (though see Readonly
from the CPAN for an alternative)vars
pragma allows you to declare package global variables, such as $VERSION
or those for exporting (Exporting) and manual OO (Blessed References)
Several useful pragmas exist on the CPAN as well. Two worth exploring in detail are autobox
, which enables object-like behavior for Perl 5's core types (scalars, references, arrays, and hashes) and perl5i
, which combines and enables many experimental language extensions into a coherent whole. These two pragmas may not belong yet in your production code without extensive testing and thoughtful consideration, but they demonstrate the power and utility of pragmas.
Perl 5.10.0 added the ability to write your own lexical pragmas in pure Perl code. perldoc perlpragma
explains how to do so, while the explanation of $^H
in perldoc perlvar
explains how the feature works.