dealing with legacy perl code - peter scott
DESCRIPTION
Peter Scott, author of the O'Reilly School of Technology's Perl Programming Certificate series, talks about how to deal with "legacy" Perl code - written by someone else, or maybe even yourself when you were younger and less wise.TRANSCRIPT
Maintaining Code While Staying SaneMaintaining Code While Staying Sane
Peter ScottO’Reilly School of Technology
February 2011
Dealing With Legacy PerlDealing With Legacy Perl
• Legacy Perl can stink
• Even when you wrote it
• Or especially when you wrote it
• But Why?
Why So Many Ugly Perl Programs?Why So Many Ugly Perl Programs?
• Unfortunately, some of those ways stink
• Or, people use more than one way of doing the same thing in the same program
The “DWIM” MythThe “DWIM” Myth
“Perl programming doesn’t require the same discipline as other languages”
• Indeed; it may require more So many WTDI
• Cure: Adopt best practices
The “Prototyping Only” MythThe “Prototyping Only” Myth
“Perl is too slow and/or unpredictable to be used for serious work”
• Too slow - sometimes, not always when you’d expect it
• Unpredictable - only when programming without discipline or understanding
• Cure: Learn algorithms, profiling, benchmarking
The “$@%*!” MythThe “$@%*!” Myth
“Perl is a write-only language”
• Another product of insufficient discipline
• The dark side of TMTOWTDI
• Cure: Adopt best practices, eschew obfuscation
Find the Author(s)!Find the Author(s)!
• Are they a better programmer than you or worse?
• Especially, better or worse at Perl?
• This helps you evaluate code you don’t understand If you find code you don’t
understand, it may be wrong Or it may be right, and over your head
• What was their background? A Shell programmer uses different idioms from a
C++ programmer
What Are You Dealing With?What Are You Dealing With?
• What was the code optimized for? Maintainability Performance Brevity Job security Something else?
MaintainabilityMaintainability
# Print words with an even number of letters, AND even
# number of each vowel, AND even position in the input
# (input is a dictionary that has one word per line)
OUTER: while (<>)
{
next if $. % 2;
chomp;
next if length() % 2;
for my $vowel (qw/a e i o u y/)
{ my @vowels = /$vowel/g;
next OUTER if @vowels %2;
} print "$_\n";
}
PerformancePerformance
while (<>)
{
next if ($. | length() - 1)) % 2;
next if tr/e// % 2;
next if tr/a// % 2;
next if tr/i// % 2;
next if tr/o// % 2;
next if tr/u// % 2;
next if tr/y// % 2;
print;
}
BrevityBrevity
#!/usr/bin/perl -ln
($x=aeiouy)=~s#.#y/$&//|#g;eval("$x$.|y///c")%2&&next;print
Job SecurityJob Security
@i = map { chop; $x++ %2 ? $_ : () } <>;
while ($i = shift @i)
{
ord(pack "w/a*", $i) & 1 and next;
$_ = "$i\n";
$i =~ s/$_(.*)$_/$1/ for qw/a e i o u y/;
print unless $i =~ /[aeiouy]/;
}
TestingTesting
• You can’t test too early Or too much Use Test::More
• Also useful: Test::Exception Test::Inline Test::NoWarnings
Tests are Real Programs, TooTests are Real Programs, Too
• Don’t abandon good indentation, variable naming, design, etc just because they’re “tests” Follow good development practices use strict and use warnings in them Abstract common code to modules in t/
• Keep tests small• They’ll grow anyway• Refactor as necessary• It’s fine for tests to prompt for
passwords, etc
Testing Web ApplicationsTesting Web Applications
• Good design would mean you wouldn't have to go through a web server, of course
• Start out simple by using WWW::Mechanize Acts like a virtual browser Easy to navigate and
fill in forms
Web Testing ExampleWeb Testing Example
my $ua = WWW::Mechanize->new;
my $res = $ua->get("http://www.example.com/");
ok( $res->is_success, "Got first page")
or die $res->message;
$ua->set_visible($username, $password);
ok( $ua->submit->is_success, "Logged in" )
or die $ua->res->message;
Modern Web TestingModern Web Testing
• Now you can use Test::WWW::Mechanize$mech->get_ok(...)
$mech->title_like(...)
$mech->content_contains(...)
$mech->follow_link_ok(...)
$mech->has_tag_like(...)
etc
LayoutLayout
• Code should be pretty to look at
• Add comments where you had to think a lot
• What’s your role?
• Don’t reformat if it doesn’t belong to you
• Use perltidy to fix up even the worst layout
Before perltidyBefore perltidy
for my $word (keys %{$word{$len}}){
chop(my $prefix = $word);if ($opt{g}){
while( $prefix ){
if(my $words=delete$chain{ $prefix} ){ $chain{$word} = [ @$words, $word ];
$maxcount=max ($maxcount,@$words+1); last;}
chop $prefix; }
}else{ if (my $words = delete
$chain{$prefix}){$chain{$word} = [@$words,
$word]; $changed = 1;} }}
After perltidyAfter perltidyfor my $word (keys %{$word{$len}}) { chop(my $prefix = $word); if ($opt{g}) { while ($prefix) { if (my $words = delete $chain{$prefix}) { $chain{$word} = [@$words, $word]; $maxcount = max($maxcount, @$words + 1); last; } chop $prefix; } } else { if (my $words = delete $chain{$prefix}) {
$chain{$word} = [@$words, $word]; $changed = 1;
} }}
After perltidyAfter perltidyfor my $word (keys %{$word{$len}}) { chop(my $prefix = $word); if ($opt{g}) { while ($prefix) { if (my $words = delete $chain{$prefix}) { $chain{$word} = [@$words, $word]; $maxcount = max($maxcount, @$words + 1); last; } chop $prefix; } } else { if (my $words = delete $chain{$prefix}) {
$chain{$word} = [@$words, $word]; $changed = 1;
} }}
AnalysisAnalysis
• Eliminate superfluous code through coverage analysis: Devel::Coverage Devel::Cover
• Improve speed through profiling: Devel::Dprof Devel::NYTProf
Devel::NYTProfDevel::NYTProf
• Very new
• Incredibly flexible and accurate
• Terrific reporting
Devel::NYTProf
What to Look Out For in Inherited CodeWhat to Look Out For in Inherited Code
• Apparent level of Perl expertise Uses hashes? Regexes? Uses parallel arrays/hashes
instead of LoLs? Calls unnecessary external
programs?
• What version of Perl was it apparently developed for? Uses my? Or local? Uses use?
• Cargo Cult Perl
The Documentation HoundThe Documentation Hound####################################################################################
# Function name: increment_number# Function name: increment_number
# Author: John Q. Lifer# Author: John Q. Lifer
# Date Created: 1996-07-14 13:45:22 PDT# Date Created: 1996-07-14 13:45:22 PDT
# Last modified: 2005-03-21 11:09:32 PST# Last modified: 2005-03-21 11:09:32 PST
# Inputs: Number# Inputs: Number
# Outputs: None# Outputs: None
# Returns: Input number plus one# Returns: Input number plus one
# Exceptions: none# Exceptions: none
# Change history:# Change history:
# 1996-07-21: Fixed off by one bug - jql# 1996-07-21: Fixed off by one bug - jql
# 2002-10-23: Changed obfuscatory ++ operator - jql# 2002-10-23: Changed obfuscatory ++ operator - jql
####################################################################################
sub increment_number {sub increment_number {
### Formal parameter list### Formal parameter list
my ($num) = @_;my ($num) = @_;
### Function body### Function body
# TODO: Throw exception on missing input, NaN, etc... - jql# TODO: Throw exception on missing input, NaN, etc... - jql
$num = $num + 1; # Add one to $num$num = $num + 1; # Add one to $num
return $num;return $num;
}}
The Documentation HoundThe Documentation Hound####################################################################################
# Function name: increment_number# Function name: increment_number
# Author: John Q. Lifer# Author: John Q. Lifer
# Date Created: 1996-07-14 13:45:22 PDT# Date Created: 1996-07-14 13:45:22 PDT
# Last modified: 2005-03-21 11:09:32 PST# Last modified: 2005-03-21 11:09:32 PST
# Inputs: Number# Inputs: Number
# Outputs: None# Outputs: None
# Returns: Input number plus one# Returns: Input number plus one
# Exceptions: none# Exceptions: none
# Change history:# Change history:
# 1996-07-21: Fixed off by one bug - jql# 1996-07-21: Fixed off by one bug - jql
# 2002-10-23: Changed obfuscatory ++ operator - jql# 2002-10-23: Changed obfuscatory ++ operator - jql
####################################################################################
sub increment_number {sub increment_number {
### Formal parameter list### Formal parameter list
my ($num) = @_;my ($num) = @_;
### Function body### Function body
# TODO: Throw exception on missing input, NaN, etc... - jql# TODO: Throw exception on missing input, NaN, etc... - jql
$num = $num + 1;$num = $num + 1; # Add one to $num# Add one to $num
return $num;return $num;
}}
The Documentation Hound CureThe Documentation Hound Cure
• s/^#+\n//mgs/^#+\n//mg
• Move to POD later in this file Or maybe another file• Such as /dev/null
But keep function/method signatures and descriptions
• Make the code tell the story
• Preserve comments that answer 'Why?'
The Documentation Hound CureThe Documentation Hound Cure
• For local programs, author and history information can be taken care of by a source code control system
• For distributions: Move author information to a README or
POD AUTHOR section Move history information to change log
String Manipulation, BASIC-StyleString Manipulation, BASIC-Style
$repl = ' ';$repl = ' ';
for ($off = 0; $off < length($str); $off++) {for ($off = 0; $off < length($str); $off++) {
$c = substr($str, $off, 1);$c = substr($str, $off, 1);
if (index("012345789", $c) < 0) {if (index("012345789", $c) < 0) {
substr($str, $off, 1, $repl);substr($str, $off, 1, $repl);
$off-- unless $repl;$off-- unless $repl;
$repl = '';$repl = '';
}}
else {else {
$repl = ' ';$repl = ' ';
}}
}}
… i.e., Without Regexes… i.e., Without Regexes
$str =~ s/\D+/ /g;$str =~ s/\D+/ /g;
Nice Formatting, But…Nice Formatting, But…
if ($month == "1") { $month = "0" . $month; }if ($month == "1") { $month = "0" . $month; }
if ($month == "2") { $month = "0" . $month; }if ($month == "2") { $month = "0" . $month; }
if ($month == "3") { $month = "0" . $month; }if ($month == "3") { $month = "0" . $month; }
if ($month == "4") { $month = "0" . $month; }if ($month == "4") { $month = "0" . $month; }
if ($month == "5") { $month = "0" . $month; }if ($month == "5") { $month = "0" . $month; }
if ($month == "6") { $month = "0" . $month; }if ($month == "6") { $month = "0" . $month; }
if ($month == "7") { $month = "0" . $month; }if ($month == "7") { $month = "0" . $month; }
if ($month == "8") { $month = "0" . $month; }if ($month == "8") { $month = "0" . $month; }
if ($month == "9") { $month = "0" . $month; }if ($month == "9") { $month = "0" . $month; }
Nice Formatting, But…Nice Formatting, But…
$month = sprintf "%02d", $month;$month = sprintf "%02d", $month;
Too Much Time on Their HandsToo Much Time on Their Hands
$smtp->datasend("To: santa.claus\@north.pole\n");$smtp->datasend("To: santa.claus\@north.pole\n");
$smtp->datasend("From: johnny\@home\n");$smtp->datasend("From: johnny\@home\n");
$smtp->datasend("Subject: I've Been Good\n");$smtp->datasend("Subject: I've Been Good\n");
$smtp->datasend("\n");$smtp->datasend("\n");
$smtp->datasend("Dear Santa\n");$smtp->datasend("Dear Santa\n");
$smtp->datasend("For Christmas I would like:\n");$smtp->datasend("For Christmas I would like:\n");
$smtp->datasend(" Perl 6\n");$smtp->datasend(" Perl 6\n");
$smtp->datasend("Thank you\n");$smtp->datasend("Thank you\n");
Too Much Time on Their HandsToo Much Time on Their Hands
$smtp->datasend(<<'EOTEXT');$smtp->datasend(<<'EOTEXT');
To: [email protected]: [email protected]
From: johnny@homeFrom: johnny@home
Subject: I've Been GoodSubject: I've Been Good
Dear SantaDear Santa
For Christmas I would like:For Christmas I would like:
Perl 6Perl 6
Thank youThank you
EOTEXTEOTEXT
Too Much Time on Their HandsToo Much Time on Their Hands
$smtp->datasend(<<'EOTEXT' =~ /[^\S\n]*(.*?\n)/g);$smtp->datasend(<<'EOTEXT' =~ /[^\S\n]*(.*?\n)/g);
To: [email protected]: [email protected]
From: johnny@homeFrom: johnny@home
Subject: I've Been GoodSubject: I've Been Good
Dear SantaDear Santa
For Christmas I would like:For Christmas I would like:
Perl 6Perl 6
Thank youThank you
EOTEXTEOTEXT
To: [email protected]: [email protected]
From: johnny@homeFrom: johnny@home
Subject: I've Been GoodSubject: I've Been Good
Dear SantaDear Santa
For Christmas I would like:For Christmas I would like:
Perl 6Perl 6
Thank youThank you
$smtp->datasend(<<'EOTEXT' =~ /[^\S\n]*(.*?\n)/g);$smtp->datasend(<<'EOTEXT' =~ /[^\S\n]*(.*?\n)/g);
EOTEXTEOTEXT
Too Much Time on Their HandsToo Much Time on Their Hands
• Or use, say, Text::Outdent or similar
Way Too Much Time On Their handsWay Too Much Time On Their hands
print "<HTML><HEAD>\n";print "<HTML><HEAD>\n";print "<TITLE>My Home Page</TITLE>\n";print "<TITLE>My Home Page</TITLE>\n";print "</HEAD><BODY>\n";print "</HEAD><BODY>\n";print "<H1>My Home Page</H1>\n";print "<H1>My Home Page</H1>\n";print "<H2>What I Did Last Summer</H2>\n";print "<H2>What I Did Last Summer</H2>\n";print "<H3>by Cuthbert J. Bigglesworth</H3>\n";print "<H3>by Cuthbert J. Bigglesworth</H3>\n";print "<P>Me and my dog <I>Fang</I> went down ";print "<P>Me and my dog <I>Fang</I> went down ";print "to the river and caught toads.</P>\n";print "to the river and caught toads.</P>\n";print "<P>P.S. I also learned Perl.</P>\n";print "<P>P.S. I also learned Perl.</P>\n";print "<P>Here is a scalar: <KBD>$x</KBD>.</P>\n";print "<P>Here is a scalar: <KBD>$x</KBD>.</P>\n";print "</BODY></HTML>\n";print "</BODY></HTML>\n";
• Use HTML::Template, Text::Template, the Template Toolkit, or Inline::Files
my ($sec, $min, $hour,my ($sec, $min, $hour,
$mday, $mon, $year,$mday, $mon, $year,
$wday, $yday, $isdst)$wday, $yday, $isdst)
= localtime(time);= localtime(time);
The Perils of Cut and PasteThe Perils of Cut and Paste
# But now use only $mon # But now use only $mon and $mday...and $mday...
my ($mday, $mon)my ($mday, $mon)
=(localtime)[4,3];=(localtime)[4,3];
The Perils of Cut and PasteThe Perils of Cut and Paste
my ($mday, $mon)my ($mday, $mon)
= (localtime)[4,3];= (localtime)[4,3];
The Perils of Cut and PasteThe Perils of Cut and Paste
use Time::localtime;use Time::localtime;
my ($mday, $mon)my ($mday, $mon)
= (localtime->mday, localtime->mon);= (localtime->mday, localtime->mon);
The Perils of Cut and PasteThe Perils of Cut and Paste
Scope? What is This Thing You Call Scope?Scope? What is This Thing You Call Scope?
my ($count, $ncount, @recs, @nrecs, %ccname, %ccphone, my ($count, $ncount, @recs, @nrecs, %ccname, %ccphone, %ccaddr, %cccity)%ccaddr, %cccity)
my ($count2, $temp, @vbinfo, @pscan, $is_true);my ($count2, $temp, @vbinfo, @pscan, $is_true);my $temp2;my $temp2;my $tempcount;my $tempcount;my $fudgeFactor;my $fudgeFactor;my ($fname, $lname, $mi, $address1, $address2, $city, my ($fname, $lname, $mi, $address1, $address2, $city,
$state, $country, $c_code, $phone, $email, $state, $country, $c_code, $phone, $email, $email_valid);$email_valid);
my ($form1, $form2, $form3, $form4, $form4a, $form4b, my ($form1, $form2, $form3, $form4, $form4a, $form4b, $form4b_valid, @subtotals, $preTaxTotal, $postTaxTotal, $form4b_valid, @subtotals, $preTaxTotal, $postTaxTotal, $shipping, $TotalTotal);$shipping, $TotalTotal);
my ($is_valid, $discount, $mealpref, $likes_pie, my ($is_valid, $discount, $mealpref, $likes_pie, $whatisthisfor);$whatisthisfor);
my ($PI, $PIE) = (3.14159265358979, "cherry");my ($PI, $PIE) = (3.14159265358979, "cherry");$count2 = 3;$count2 = 3;[...][...]
Scope Ignorance CureScope Ignorance Cure
• Move variable declaration to latest possible point Exception: configuration settings
• Put those in a separate file if appropriate
• Use in-line declarations for loop variables:foreach my $dog (@schnauzers)foreach my $dog (@schnauzers)
while (my $imp = shift @demons)while (my $imp = shift @demons)
• You can carry this even further:getopts('dq:v', \my %Opt);getopts('dq:v', \my %Opt);
• Scope Ignorance is frequently combined with Monolithic Madness
Monolithic MadnessMonolithic Madness
• (Visualize 2500 lines of code without the word 'sub')
• Sufferers’ favorite language: JCL
• “It started out at 30 lines… it just grew”
• “I know where everything is” Of course, no one else does
Monolithic Madness CureMonolithic Madness Cure
• Look for variables with short scopes and evaluate the area for subroutine-ness
• If you like Eclipse, try Devel::Refactor and the extract_subroutine method for the EPIC plug-in
• use strictuse strict and turn it off over an ever-narrowing scope: use strict;use strict;
[...][...]
{{
no strict;no strict;
[...][...]
}}
[...][...]
Perl from ???Perl from ???
$#abspaths = $num;$#abspaths = $num;
for ($i=0; $i<$num; $i++) {for ($i=0; $i<$num; $i++) {
my $newlen =my $newlen =
$ROOTLEN+1+length($paths[$i]);$ROOTLEN+1+length($paths[$i]);
$abspaths[$i] = ' ' x $newlen;$abspaths[$i] = ' ' x $newlen;
$abspaths[$i] = sprintf("%s/%s", $ROOT, $abspaths[$i] = sprintf("%s/%s", $ROOT,
$dirs[$i]);$dirs[$i]);
}}
Perl from CPerl from C
abspaths = realloc(abspaths, abspaths = realloc(abspaths,
num * sizeof(char*));num * sizeof(char*));
for ( i=0; i< num; i++) {for ( i=0; i< num; i++) {
int newlen =int newlen =
ROOTLEN+1+ strlen(paths[ i]);ROOTLEN+1+ strlen(paths[ i]);
char temp[newlen];char temp[newlen];
abspaths[ i] = malloc(newlen);abspaths[ i] = malloc(newlen);
sprintf(abspaths[i], "%s/%s", ROOT,sprintf(abspaths[i], "%s/%s", ROOT,
dirs[ i]);dirs[ i]);
}}
Perl from CPerl from C
@abspaths = map { "$ROOT/$_" } @paths;@abspaths = map { "$ROOT/$_" } @paths;
Perl from ???Perl from ???
$file = "matrix.dat";$file = "matrix.dat";
open (FH, ">$file");open (FH, ">$file");
for ($I = 1, $I <= 4; $I++) {for ($I = 1, $I <= 4; $I++) {
$value = $X[$I][$_],$value = $X[$I][$_],
write (FH) for 1..10;write (FH) for 1..10;
}}
format FH =format FH =
<<<<<<<<<<<<<<<<<<<<<<<<<<
$value$value
..
close (FH);close (FH);
exit "Done";exit "Done";
Perl from FORTRANPerl from FORTRAN
ofile = "matrix.dat"ofile = "matrix.dat"
OPEN (42, FILE=ofile)OPEN (42, FILE=ofile)
DO 10 I = 1, 4DO 10 I = 1, 4
10 WRITE (42,100) (X(I,J),J=1,10)10 WRITE (42,100) (X(I,J),J=1,10)
100 FORMAT "(10F10.3)"100 FORMAT "(10F10.3)"
CLOSE (42)CLOSE (42)
STOP "Done"STOP "Done"
Perl from FORTRANPerl from FORTRAN
open my $fh, '>', $file or die $!;open my $fh, '>', $file or die $!;
for my $i (1 .. 4) { for my $i (1 .. 4) {
printf {$fh} "%10.3f" $X[$i][$_]printf {$fh} "%10.3f" $X[$i][$_]
for 1..10;for 1..10;
print {$fh} "\n";print {$fh} "\n";
}}
Perl from ???Perl from ???
############################################
#Program name: Report.#Program name: Report.
############################################
sub decipher {sub decipher {
unpack $fmt, shift;unpack $fmt, shift;
}}
$fmt = "A7" . # base$fmt = "A7" . # base
"x4" . # filler"x4" . # filler
"A7"; # bonus"A7"; # bonus
$rec = <>;$rec = <>;
($base, $bonus) = decipher($rec);($base, $bonus) = decipher($rec);
$salary = $base + $bonus;$salary = $base + $bonus;
printf "%7.2f\n", $salary;printf "%7.2f\n", $salary;
exit;exit;
Perl from COBOLPerl from COBOL
IDENTIFICATION DIVISION.IDENTIFICATION DIVISION.
PROGRAM-ID. Report.PROGRAM-ID. Report.
DATA DIVISION.DATA DIVISION.
WORKING-STORAGE SECTION.WORKING-STORAGE SECTION.
01 salary PICTURE 99999V9901 salary PICTURE 99999V99
01 rec01 rec
02 base PICTURE 99999V9902 base PICTURE 99999V99
02 FILLER PICTURE X(4)02 FILLER PICTURE X(4)
02 bonus PICTURE 99999V9902 bonus PICTURE 99999V99
PROCEDURE DIVISION.PROCEDURE DIVISION.
READ recREAD rec
ADD bonus TO base GIVING salaryADD bonus TO base GIVING salary
DISPLAY salaryDISPLAY salary
STOP RUN.STOP RUN.
Perl from COBOLPerl from COBOL
my $rec = <>;my $rec = <>;
my ($bonus, $base) = unpack "A7x4A7", $rec;my ($bonus, $base) = unpack "A7x4A7", $rec;
my $salary = $bonus + $base;my $salary = $bonus + $base;
printf "%7.2f\n", $salary;printf "%7.2f\n", $salary;
Perl from ???Perl from ???
#!/usr/bin/perl -l#!/usr/bin/perl -l
print "Think of a number: "; $dummy = <>;print "Think of a number: "; $dummy = <>;
$I = 1;$I = 1;
AGAIN: print "Is it ",$I, "?";AGAIN: print "Is it ",$I, "?";
$A = <>;$A = <>;
$X = substr($A,0,1);$X = substr($A,0,1);
if($X eq"Y" or $X eq"y") { goto DONE }if($X eq"Y" or $X eq"y") { goto DONE }
$I =$I + 1$I =$I + 1
goto AGAIN;goto AGAIN;
DONE: exit;DONE: exit;
# END# END
Perl from BASICPerl from BASIC
100 INPUT "Think of a number: ";D$100 INPUT "Think of a number: ";D$
110 LET I = 1110 LET I = 1
120 PRINT "Is it ", I;120 PRINT "Is it ", I;
130 INPUT "?"; A$130 INPUT "?"; A$
140 LET X$ = LEFT$(A$,1)140 LET X$ = LEFT$(A$,1)
145 IF X$ = "Y" OR X$ = "y" THEN GOTO 149145 IF X$ = "Y" OR X$ = "y" THEN GOTO 149
147 LET I = I + 1147 LET I = I + 1
148 GOTO 120148 GOTO 120
149 STOP149 STOP
150 END150 END
Perl from BASICPerl from BASIC
print "Think of a number\n";print "Think of a number\n";
my $ans = '';my $ans = '';
for (my $guess = 1; $ans !~ /^y/i; $guess++) {for (my $guess = 1; $ans !~ /^y/i; $guess++) {
print "Is it $guess? ";print "Is it $guess? ";
chomp($ans = <>);chomp($ans = <>);
}}
read(STDIN, $buffer, $ENV{CONTENT_LENGTH});read(STDIN, $buffer, $ENV{CONTENT_LENGTH});
my @pairs = split(/&/, $buffer); my @pairs = split(/&/, $buffer);
push(@pairs, map { split(/&/, $_) } $ENV{QUERY_STRING});push(@pairs, map { split(/&/, $_) } $ENV{QUERY_STRING});
push(@pairs, map { split(/&/, $_) } @ARGV);push(@pairs, map { split(/&/, $_) } @ARGV);
foreach my $pair (@pairs) {foreach my $pair (@pairs) {
my ($name, $value) = split(/=/, $pair);my ($name, $value) = split(/=/, $pair);
$name =~ tr/+/ /;$name =~ tr/+/ /;
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;hex($1))/eg;
$value =~ tr/+/ /;$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;hex($1))/eg;
[... You know the rest... ][... You know the rest... ]
• Stop the insanity!
Cargo Cult PerlCargo Cult Perl
Cargo Cult PerlCargo Cult Perl
use CGI;use CGI;
Comment Code SmellsComment Code Smells
• Non-O-O: Wannabee Objects and Data Clumps* Cut And Paste
• O-O: Mixed Abstraction Levels Time Dependencies*
• * Courtesy of “The Art of Agile Development”, Shore & Warden
Line EditingLine Editing
• Reduce bloat Mothball code that coverage analysis
indicates is not called Shorten subroutines and main program
to one screen’s length at most Don’t exceed screen
width
Consolidate VariablesConsolidate Variables
my (%hits_by_client, %hits_by_method, %hits_by_ext,
%hits_by_protocol, %hits_by_uri);
$hits_by_client{$client}++;
$hits_by_method{$method}++;
$hits_by_ext{$extension}++;
$hits_by_protocol{$protocol}++;
$hits_by_uri{$uri}++;
Consolidate VariablesConsolidate Variables
my %hits;
$hits{CLIENT}{$client}++;
$hits{METHOD}{$method}++;
$hits{EXTENSION}{$extension}++;
$hits{PROTOCOL}{$protocol}++;
$hits{URI}{$uri}++;
Consolidate VariablesConsolidate Variables
my ($client, $method, $extension, $protocol, $uri) =
($line =~ /^(\S+) - .../);
my %hits;
$hits{CLIENT}{$client}++;
$hits{METHOD}{$method}++;
$hits{EXTENSION}{$extension}++;
$hits{PROTOCOL}{$protocol}++;
$hits{URI}{$uri}++;
Consolidate VariablesConsolidate Variables
my @KEYS = qw(CLIENT METHOD EXTENSION PROTOCOL URI);
my %access;
@access{@KEYS} = ($line =~ /^(\S+) - .../);
my %hits;
for my $key (@KEYS) {
$hits{$key}{ $access{$key} }++;
}
Line EditingLine Editing
• Remove rote stuff that the computer can figure out for you Example:$dbh->do("INSERT INTO perf (s, s1a, s1b, x1, x3, y1, y2, y3, zx, zx4, zx4a)VALUES ($s, $s1a, '$s1b', '$x1', $x2, $y1, $y2, $y3, $zx, '$zx4, '$zx4a')");
Arrgh
Line EditingLine Editing
• Try:my $sth = $dbh->prepare("INSERT INTO perf (s, s1a, s1b, x1, x3, y1, y2, y3, zx, zx4, zx4a) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)");
$sth->execute($s, $s1a, $s1b, $x1, $x3, $y1, $y3, $y2, $zx, $zx4, $zx4a);
Line EditingLine Editing
• Still too much work, too error-prone. Try:$dbh->do( make_insert(perf => keys %data), undef, values %data);
sub make_insert{ my ($table, @cols) = @_; "INSERT INTO $table ("
. join(',' => @cols) . ") VALUES (" . join(',' => ('?') x @cols) . ")";}
Line EditingLine Editing
• This wheel has been invented several times, e.g.:
use DBIx::Recordset;# ...DBIx::Recordset->insert( { '!DataSource' => $dbh, '!Table' => 'perf', %data } );
Line EditingLine Editing
• Get rid of massive strings
• Especially for HTML, use a templating system instead HTML::Template works great, even for
non-HTML So does Text::Template and the
Template Toolkit
use strictuse strict
• Use it or get sand kicked in your face
• Eliminate all errors in order to get the code to run
• Eliminate unnecessary package variables
• Declare lexical variables explicitly• Eliminate symbolic references
They’re hard to maintain anyway and just plain ugly
• Turn strictness off with no strict I’ve only ever needed no strict 'refs'
use warningsuse warnings
• Use it or get sand kicked in your face Can use -w instead on older perls On newer perls, use -W in testing to
force warnings on across all modules
• Leave warnings enabled in production But if users might see the warnings,
have them sent to you instead Trap via $SIG{__WARN__} handler
Commonly Neglected ModulesCommonly Neglected Modules
• Date::* - look for unnecessary calls to date or cal
• DBI, DBD::* - look for unnecessary calls to database programs
• LWP::* - look for unnecessary calls to lynx, wget or GET