By Damian Conway
Book Price: $39.95 USD
£28.50 GBP
PDF Price: $31.99
Cover | Table of Contents
We do not all have to write like Faulkner, or program
like Dijkstra. I will gladly tell people what my
programming style is, and I will even tell them where I
think their own style is unclear or makes me jump
through mental hoops.
But I do this as a fellow programmer, not as the Perl
god ... stylistic limits should be self-imposed, or at most
policed by consensus among your buddies.
—Larry Wall
Natural Language Principles in Perl
_ref to the name of every variable that stores a reference (see Chapter 3) makes it harder to accidentally write $array_ref[$n] instead of $array_ref->[$n], because anything except an arrow after _ref will soon come to look wrong.if-elsif-elsif-elsif-... in favour of table look-ups (see Chapter 6) can ensure that the cost of any selection statement stays nearly constant, rather than growing linearly with the number of alternatives.Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
constant-width regular). These code fragments aim to demonstrate the advantages of following the suggested practice, and the problems that can occur if you don't. All of these examples are also available for you to download and reuse from http://www.oreilly.com/catalog/perlbp.use strict and use warnings aren't mentioned on page 1. But if you've already seen the light on those two, they don't need to be on page 1. And if you haven't seen the light yet, Chapter 18 is soon enough. By then you'll have discovered several hundred ways in which code can go horribly wrong, and will be better able to appreciate these two ways in which Perl can help your code go right.
@tcmd= grep /^.*;$/ => @cmd;
@terminated_commands
= grep { m/ \A [^\n]* ; \n? \z /xms } @raw_commands;
Most people's [...] programs should be indented six feet downward and covered with dirt.
—Blair P. Houghton
Brace and parenthesize in K&R style.
my @names = (
'Damian',
# Primary key
'Matthew',
# Disambiguator
'Conway',
# General class or category
);
for my $name (@names) {
for my $word ( anagrams_of(lc $name) ) {
print "$word\n";
}
}
# Don't use BSD style...
my @names =
(
'Damian', # Primary key
'Matthew', # Disambiguator
'Conway', # General class or category
);
for my $name (@names)
{
for my $word (anagrams_of(lc $name))
{
print "$word\n";
}
}
# And don't use GNU style either...
for my $name (@names)
{
for my $word (anagrams_of(lc $name))
{
print "$word\n";
}
}
Separate your control keywords from the following opening bracket.
for my $result (@results) {
print_sep();
print $result;
}
while ($min < $max) {
my $try = ($max - $min) / 2;
if ($value[$try] < $target) {
$max = $try;
}
else {
$min = $try;
}
}
for(@results) {
print_sep();
print;
}
while($min < $max) {
my $try = ($max - $min) / 2;
if($value[$try] < $target) {
$max = $try;
}
else{
$min = $try;
}
}
Don't separate subroutine or variable names from the following opening bracket.
my @candidates = get_candidates($marker);
CANDIDATE:
for my $i (0..$#candidates) {
next CANDIDATE if open_region($i);
$candidates[$i]
= $incumbent{ $candidates[$i]{region} };
}
my @candidates = get_candidates ($marker);
CANDIDATE:
for my $i (0..$#candidates) {
next CANDIDATE if open_region ($i);
$candidates [$i]
= $incumbent {$candidates [$i] {region}};
}
Don't use unnecessary parentheses for builtins and "honorary" builtins.
while (my $record = <$results_file>) {
chomp $record;
my ($name, $votes) = split "\t", $record;
print 'Votes for ',
substr($name, 0, 10),
# Parens needed for precedence
": $votes (verified)\n";
}
carp and croak (from the standard Carp module—see Chapter 13), first and max (from the standard List::Util module—see Chapter 8), and prompt (from the IO::Prompt CPAN module—see Chapter 10).
while (my $record = <$results_file>) {
chomp( $record );
my ($name, $votes) = split("\t", $record);
print(
'Votes for ',
substr($name, 0, 10),
": $votes (verified)\n"
);
}Separate complex keys or indices from their surrounding brackets.
$candidates[$i] = $incumbent{$candidates[$i]{get_region()}};
$candidates[$i] = $incumbent{ $candidates[$i]{ get_region() } };
print $incumbent{ $largest_gerrymandered_constituency };
print $incumbent{$largest_gerrymandered_constituency};
Use whitespace to help binary operators stand out from their operands.
my $displacement=$initial_velocity*$time+0.5*$acceleration*$time**2;
my $price=$coupon_paid*$exp_rate+(($face_val+$coupon_val)*$exp_rate**2);
my $displacement
= $initial_velocity * $time + 0.5 * $acceleration * $time**2;
my $price
= $coupon_paid * $exp_rate + ($face_val + $coupon_paid) * $exp_rate**2;
+ to visually reinforce the higher precedence of the two multiplicative subexpressions surrounding it. On the other hand, it's quite appropriate to sandwich the ** operator tightly between its operands, given its very high precedence and its longer, more easily identified symbol.
my $velocity
= $initial_velocity + ($acceleration * ($time + $delta_time));
my $future_price
= $current_price * exp($rate - $dividend_rate_on_index) * ($delivery - $now);
my $spring_force = !$hyperextended ? -$spring_constant * $extension : 0;
my $payoff = max(0, -$asset_price_at_maturity + $strike_price);
my $tan_theta = sin $theta / cos $theta;
my $forward_differential_1_year = $delivery_price * exp -$interest_rate;Place a semicolon after every statement.
while (my $line = <>) {
chomp $line;
if ( $line =~ s{\A (\s*) -- (.*)}{$1#$2}xms ) {
push @comments, $2;
}
print $line;
}
while (my $line = <>) {
chomp $line;
if ( $line =~ s{\A (\s*) -- (.*)}{$1#$2}xms ) {
push @comments, $2
}
print $line
}
while (my $line = <>) {
chomp $line;
if ( $line =~ s{\A (\s*) -- (.*)}{$1#$2}xms ) {
push @comments, $2
/shift/mix
}
print $line
$src_len += length;
}
while (my $line = <>) {
chomp $line;
if ( $line =~ s{\A (\s*) -- (.*)}{$1#$2}xms ) {
push @comments, $2 / shift() / mix()
}
print $line ($src_len += length);
}Place a comma after every value in a multiline list.
my @dwarves = (
'Happy',
'Sleepy',
'Dopey',
'Sneezy',
'Grumpy',
'Bashful',
'Doc',
);
my @dwarves = (
'Bashful',
'Doc',
'Dopey',
'Grumpy',
'Happy',
'Sleepy',
'Sneezy',
);
'Doc', reordering the list would introduce a bug:
my @dwarves = (
'Bashful',
'Doc'
'Dopey',
'Grumpy',
'Happy',
'Sleepy',
'Sneezy',
);
Use 78-column lines.
set textwidth=78
(setq fill-column 78)
(setq auto-fill-mode t)
From: boss@headquarters
To: you@saltmines
Subject: Please explain
I came across this chunk of code in your latest module.
Is this your idea of a joke???
> $;=$/;seek+DATA,undef$/,!$s;$_=<DATA>;$s&&print||(*{q;::\;
> ;}=sub{$d=$d-1?$d:$0;s;';\t#$d#;,$_})&&$g&&do{$y=($x||=20)*($y||8);sub
> i{sleep&f}sub'p{print$;x$=,join$;,$b=~/.{$x}/g,$;}sub'f{pop||1}sub'n{substr($b
> ,&f%$y,3)=~tr,O,O,}sub'g{@_[@_]=@_;--($f=&f);$m=substr($b,&f,1);($w,$w,$m,O)
> [n($f-$x)+n($x+$f)-(${m}eq+O=>)+n$f]||$w}$w="\40";$b=join'',@ARGV?<>:$_,$w
> x$y;$b=~s).)$&=~/\w/?O:$w)gse;substr($b,$y)=q++;$g='$i=0;$i?$b:$c=$b;
> substr+$c,$i,1,g$i;$g=~s?\d+?($&+1)%$y?e;$i-$y+1?eval$g:do{$b=$c;p;i}';
> sub'e{eval$g;&e};e}||eval||die+No.$;Use four-column indentation levels.
while (my $line = <>) {
chomp $line;
if ( $line =~ s{\A (\s*) -- ([^\n]*) }{$1#$2}xms ) {
push @comments, $2;
}
print $line;
}
while (my $line = <>) {
chomp $line;
if ( $line =~ s{\A (\s*) -- ([^\n]*) }{$1#$2}xms ) {
push @comments, $2;
}
print $line;
}
Indent with spaces, not tabs .
sub addarray_internal {
» my ($var_name, $need_quotemeta) = @_;
» $raw .= $var_name;
» my $quotemeta = $need_quotemeta ? q{ map {quotemeta $_} }
» » » » » : $EMPTY_STR
» ··············;
····my $perl5pat
····» = qq{(??{join q{|}, $quotemeta \@{$var_name}})};
» push @perl5pats, $perl5pat;
» return;
}
sub addarray_internal {
····my ($var_name, $need_quotemeta) = @_;
····$raw .= $var_name;
····my $quotemeta = $need_quotemeta ? q{ map {quotemeta $_} }
··················:···················$EMPTY_STR
··················;
····
my $perl5pat
········
= qq{(??{join q{|}, $quotemeta \@{$var_name}})};
····
push @perl5pats, $perl5pat;
····
return;
}
Never place two statements on the same line.
RECORD:
while (my $record = <$inventory_file>) {
chomp $record; next RECORD if $record eq $EMPTY_STR;
my @fields = split $FIELD_SEPARATOR, $record; update_sales(\@fields);$count++;
}
RECORD:
while (my $record = <$inventory_file>) {
chomp $record;
next RECORD if $record eq $EMPTY_STR;
my @fields = split $FIELD_SEPARATOR, $record;
update_sales(\@fields);
$count++;
}
map and grep blocks that contain more than one statement. You should write:
my @clean_words
= map {
my $word = $_;
$word =~ s/$EXPLETIVE/[DELETED]/gxms;
$word;
} @raw_words;
my @clean_words
= map { my $word = $_; $word =~ s/$EXPLETIVE/[DELETED]/gxms; $word } @raw_words;
Code in paragraphs.
# Process an array that has been recognized...
sub addarray_internal {
my ($var_name, $needs_quotemeta) = @_;
# Cache the original...
$raw .= $var_name;
# Build meta-quoting code, if requested...
my $quotemeta = $needs_quotemeta ? q{map {quotemeta $_} } : $EMPTY_STR;
# Expand elements of variable, conjoin with ORs...
my $perl5pat = qq{(??{join q{|}, $quotemeta \@{$var_name}})};
# Insert debugging code if requested...
my $type = $quotemeta ? 'literal' : 'pattern';
debug_now("Adding $var_name (as $type)");
add_debug_mesg("Trying $var_name (as $type)");
return $perl5pat;
}
Don't cuddle anelse.
else looks like this:
} else {
else looks like this:
}
else {
else keyword is no longer in vertical alignment with its controlling if, nor with its own closing bracket. This misalignment makes it harder to visually match up the various components of an if-else construct.else is to distinguish an alternate course of action. But cuddling the else makes that distinction less distinct. For a start, it removes the near-empty line provided by the closing brace of the preceding if, which reduces the visual gap between the if and else blocks. Squashing the two blocks together in that way undermines the paragraphing inside the two blocks (see the previous guideline, "Chunking"), especially if the contents of the blocks are themselves properly paragraphed with empty lines between chunks.else from the leftmost position on its line, which means that the keyword is harder to locate when you are scanning down the code. On the other hand, an uncuddled else improves both the vertical separation of your code and the identifiability of the keyword:
if ($sigil eq '$') {
if ($subsigil eq '?') {
$sym_table{ substr($var_name,2) } = delete $sym_table{$var_name};
$internal_count++;
$has_internal{$var_name}++;
}
else {
${$var_ref} = q{$sym_table{$var_name}};
$external_count++;
$has_external{$var_name}++;
}
}
elsif ($sigil eq '@' && $subsigil eq '?') {
@{ $sym_table{$var_name} }
= grep {defined $_} @{$sym_table{$var_name}};
}
elsif ($sigil eq '%' && $subsigil eq '?') {
delete $sym_table{$var_name}{$EMPTY_STR};
}
else {
${$var_ref} = q{$sym_table{$var_name}};
}Align corresponding items vertically.
my @months = qw(
January February March
April May June
July August September
October November December
);
my %expansion_of = (
q{it's} => q{it is},
q{we're} => q{we are},
q{didn't} => q{did not},
q{must've} => q{must have},
q{I'll} => q{I will},
);
my @months = qw(
January February March April May June July August September
October November December
);
my %expansion_of = (
q{it's} => q{it is}, q{we're} => q{we are}, q{didn't} => q{did not},
q{must've} => q{must have}, q{I'll} => q{I will},
);
$name = standardize_name($name);
$age = time - $birth_date;
$status = 'active';
$name = standardize_name($name);
$age = time - $birth_date;
$status = 'active';
Break long expressions before an operator.
push @steps, $steps[-1] +
$radial_velocity * $elapsed_time +
$orbital_velocity * ($phase + $phase_shift) -
$DRAG_COEFF * $altitude;
Factor out long expressions in the middle of statements.
my $next_step = $steps[-1]
+ $radial_velocity * $elapsed_time
+ $orbital_velocity * ($phase + $phase_shift)
- $DRAG_COEFF * $altitude
;
add_step( \@steps, $next_step, $elapsed_time);
add_step( \@steps, $steps[-1]
+ $radial_velocity * $elapsed_time
+ $orbital_velocity * ($phase + $phase_shift)
- $DRAG_COEFF * $altitude
, $elapsed_time);
Always break a long expression at the operator of the lowest possible precedence.
push @steps, $steps[-1] + $radial_velocity
* $elapsed_time + $orbital_velocity
* ($phase + $phase_shift) - $DRAG_COEFF
* $altitude
;
push @steps, $steps[-1]
+ $radial_velocity * $elapsed_time
+ $orbital_velocity
* ($phase + $phase_shift)
- $DRAG_COEFF * $altitude
;
Break long assignments before the assignment operator.
$predicted_val = $average
+ $predicted_change * $fudge_factor
;
$predicted_val
= $average + $predicted_change * $fudge_factor;
$predicted_val
= ($minimum + $maximum) / 2
+ $predicted_change * max($fudge_factor, $local_epsilon);
$predicted_val =
$average + $predicted_change * $fudge_factor;
$predicted_val{$current_data_set}[$next_iteration] =
$average + $predicted_change * $fudge_factor;
Format cascaded ternary operators in columns.
? and : of a ternary have very low precedence, a straightforward interpretation of the expression-breaking rule doesn't work well in this particular case, since it produces something like:
my $salute = $name eq $EMPTY_STR ? 'Customer'
: $name =~ m/\A((?:Sir|Dame) \s+ \S+)/xms ? $1
: $name =~ m/(.*), \s+ Ph[.]?D \z/xms ? "Dr $1" : $name;
# When their name is... Address them as...
my $salute = $name eq $EMPTY_STR ? 'Customer'
: $name =~ m/\A((?:Sir|Dame) \s+ \S+) /xms ? $1
: $name =~ m/(.*), \s+ Ph[.]?D \z /xms ? "Dr $1"
: $name
;
my $name = defined $customer{name} ? $customer{name}
: 'Sir or Madam'
;Parenthesize long lists .
, and a ;.