dealing with legacy perl code - peter scott

74
Maintaining Code While Staying Sane Peter Scott O’Reilly School of Technology February 2011

Upload: oreilly-media

Post on 09-May-2015

11.903 views

Category:

Technology


0 download

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

Page 1: Dealing with Legacy Perl Code - Peter Scott

Maintaining Code While Staying SaneMaintaining Code While Staying Sane

Peter ScottO’Reilly School of Technology

February 2011

Page 2: Dealing with Legacy Perl Code - Peter Scott

Dealing With Legacy PerlDealing With Legacy Perl

• Legacy Perl can stink

• Even when you wrote it

• Or especially when you wrote it

• But Why?

Page 3: Dealing with Legacy Perl Code - Peter Scott

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

Page 4: Dealing with Legacy Perl Code - Peter Scott

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

Page 5: Dealing with Legacy Perl Code - Peter Scott

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

Page 6: Dealing with Legacy Perl Code - Peter Scott

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

Page 7: Dealing with Legacy Perl Code - Peter Scott

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

Page 8: Dealing with Legacy Perl Code - Peter Scott

What Are You Dealing With?What Are You Dealing With?

• What was the code optimized for? Maintainability Performance Brevity Job security Something else?

Page 9: Dealing with Legacy Perl Code - Peter Scott

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";

}

Page 10: Dealing with Legacy Perl Code - Peter Scott

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;

}

Page 11: Dealing with Legacy Perl Code - Peter Scott

BrevityBrevity

#!/usr/bin/perl -ln

($x=aeiouy)=~s#.#y/$&//|#g;eval("$x$.|y///c")%2&&next;print

Page 12: Dealing with Legacy Perl Code - Peter Scott

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]/;

}

Page 13: Dealing with Legacy Perl Code - Peter Scott

TestingTesting

• You can’t test too early Or too much Use Test::More

• Also useful: Test::Exception Test::Inline Test::NoWarnings

Page 14: Dealing with Legacy Perl Code - Peter Scott

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

Page 15: Dealing with Legacy Perl Code - Peter Scott

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

Page 16: Dealing with Legacy Perl Code - Peter Scott

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;

Page 17: Dealing with Legacy Perl Code - Peter Scott

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

Page 18: Dealing with Legacy Perl Code - Peter Scott

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

Page 19: Dealing with Legacy Perl Code - Peter Scott

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;} }}

Page 20: Dealing with Legacy Perl Code - Peter Scott

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;

} }}

Page 21: Dealing with Legacy Perl Code - Peter Scott

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;

} }}

Page 22: Dealing with Legacy Perl Code - Peter Scott

AnalysisAnalysis

• Eliminate superfluous code through coverage analysis: Devel::Coverage Devel::Cover

• Improve speed through profiling: Devel::Dprof Devel::NYTProf

Page 23: Dealing with Legacy Perl Code - Peter Scott

Devel::NYTProfDevel::NYTProf

• Very new

• Incredibly flexible and accurate

• Terrific reporting

Page 24: Dealing with Legacy Perl Code - Peter Scott

Devel::NYTProf

Page 25: Dealing with Legacy Perl Code - Peter Scott

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

Page 26: Dealing with Legacy Perl Code - Peter Scott

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;

}}

Page 27: Dealing with Legacy Perl Code - Peter Scott

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;

}}

Page 28: Dealing with Legacy Perl Code - Peter Scott

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

Page 29: Dealing with Legacy Perl Code - Peter Scott

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

Page 30: Dealing with Legacy Perl Code - Peter Scott

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 = ' ';

}}

}}

Page 31: Dealing with Legacy Perl Code - Peter Scott

… i.e., Without Regexes… i.e., Without Regexes

$str =~ s/\D+/ /g;$str =~ s/\D+/ /g;

Page 32: Dealing with Legacy Perl Code - Peter Scott

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; }

Page 33: Dealing with Legacy Perl Code - Peter Scott

Nice Formatting, But…Nice Formatting, But…

$month = sprintf "%02d", $month;$month = sprintf "%02d", $month;

Page 34: Dealing with Legacy Perl Code - Peter Scott

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");

Page 35: Dealing with Legacy Perl Code - Peter Scott

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

Page 36: Dealing with Legacy Perl Code - Peter Scott

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

Page 37: Dealing with Legacy Perl Code - Peter Scott

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

Page 38: Dealing with Legacy Perl Code - Peter Scott

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

Page 39: Dealing with Legacy Perl Code - Peter Scott

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

Page 40: Dealing with Legacy Perl Code - Peter Scott

my ($mday, $mon)my ($mday, $mon)

=(localtime)[4,3];=(localtime)[4,3];

The Perils of Cut and PasteThe Perils of Cut and Paste

Page 41: Dealing with Legacy Perl Code - Peter Scott

my ($mday, $mon)my ($mday, $mon)

= (localtime)[4,3];= (localtime)[4,3];

The Perils of Cut and PasteThe Perils of Cut and Paste

Page 42: Dealing with Legacy Perl Code - Peter Scott

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

Page 43: Dealing with Legacy Perl Code - Peter Scott

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;[...][...]

Page 44: Dealing with Legacy Perl Code - Peter Scott

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

Page 45: Dealing with Legacy Perl Code - Peter Scott

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

Page 46: Dealing with Legacy Perl Code - Peter Scott

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;

[...][...]

}}

[...][...]

Page 47: Dealing with Legacy Perl Code - Peter Scott

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]);

}}

Page 48: Dealing with Legacy Perl Code - Peter Scott

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]);

}}

Page 49: Dealing with Legacy Perl Code - Peter Scott

Perl from CPerl from C

@abspaths = map { "$ROOT/$_" } @paths;@abspaths = map { "$ROOT/$_" } @paths;

Page 50: Dealing with Legacy Perl Code - Peter Scott

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";

Page 51: Dealing with Legacy Perl Code - Peter Scott

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"

Page 52: Dealing with Legacy Perl Code - Peter Scott

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";

}}

Page 53: Dealing with Legacy Perl Code - Peter Scott

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;

Page 54: Dealing with Legacy Perl Code - Peter Scott

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.

Page 55: Dealing with Legacy Perl Code - Peter Scott

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;

Page 56: Dealing with Legacy Perl Code - Peter Scott

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

Page 57: Dealing with Legacy Perl Code - Peter Scott

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

Page 58: Dealing with Legacy Perl Code - Peter Scott

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 = <>);

}}

Page 59: Dealing with Legacy Perl Code - Peter Scott

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

Page 60: Dealing with Legacy Perl Code - Peter Scott

Cargo Cult PerlCargo Cult Perl

use CGI;use CGI;

Page 61: Dealing with Legacy Perl Code - Peter Scott

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

Page 62: Dealing with Legacy Perl Code - Peter Scott

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

Page 63: Dealing with Legacy Perl Code - Peter Scott

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}++;

Page 64: Dealing with Legacy Perl Code - Peter Scott

Consolidate VariablesConsolidate Variables

my %hits;

$hits{CLIENT}{$client}++;

$hits{METHOD}{$method}++;

$hits{EXTENSION}{$extension}++;

$hits{PROTOCOL}{$protocol}++;

$hits{URI}{$uri}++;

Page 65: Dealing with Legacy Perl Code - Peter Scott

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}++;

Page 66: Dealing with Legacy Perl Code - Peter Scott

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} }++;

}

Page 67: Dealing with Legacy Perl Code - Peter Scott

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

Page 68: Dealing with Legacy Perl Code - Peter Scott

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);

Page 69: Dealing with Legacy Perl Code - Peter Scott

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) . ")";}

Page 70: Dealing with Legacy Perl Code - Peter Scott

Line EditingLine Editing

• This wheel has been invented several times, e.g.:

use DBIx::Recordset;# ...DBIx::Recordset->insert( { '!DataSource' => $dbh, '!Table' => 'perf', %data } );

Page 71: Dealing with Legacy Perl Code - Peter Scott

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

Page 72: Dealing with Legacy Perl Code - Peter Scott

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'

Page 73: Dealing with Legacy Perl Code - Peter Scott

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

Page 74: Dealing with Legacy Perl Code - Peter Scott

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