Writing Maintainable Perl

682 views
552 views

Published on

One criticism of opponents of Perl is that it is a "write-only" language meaning that once the code is written, it is extremely difficult to maintain because it is difficult to understand upon re-examination. As with many criticisms, this should be aimed at those undisciplined developers who are writing the code, and not their tool of choice.

Having said that, I think it is also fair to say that Perl makes it very easy to write difficult-to-decipher code. This is the double­edged sword which is the shorthand Perl gives us to be very expressive in a small amount of space. A negative application of this is obfuscated Perl (where the author intentionally makes his code difficult to read), while a more positive application is the craft of creating Perl "one­liners" (trying to include a great deal of functionality in a single line of code). A one­liner can be a powerful weapon in the arsenal of a system administrator.

In this talk:
* We'll look at a line of code in a subroutine that is in desperate need of readability changes
* We'll make the code more readable by introducing:
* appropriate whitespace
* different ways of writing the same thing, for example: $array[$#array] vs. $array[-1]
* useful names for variables, versus $index, $j $k $l, etc
* breaking up one line of code into multiple lines
* exploring further improvements through Perl::Critic and by extension Perl::Tidy

This talk will be beginner-friendly.

0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
682
On SlideShare
0
From Embeds
0
Number of Embeds
39
Actions
Shares
0
Downloads
6
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide
  • C-style "for" loop used at line 11, column 7. See page 100 of PBP. (Severity: 2)
    “What?” Damian recommends against using C-style “for” loops”
    “Why?” His main argument is that C-style “for” loops are more difficult to read and therefore more difficult to maintain
    “I disagree.” That's OK and in fact Perl::Critic allows for a lot of customizations to alter or remove those policies you don't agree with
    BUT, I'm going to fix all the default suggestions for the purpose of this presentation
  • C-style "for" loop used at line 11, column 7. See page 100 of PBP. (Severity: 2)
    “What?” Damian recommends against using C-style “for” loops”
    “Why?” His main argument is that C-style “for” loops are more difficult to read and therefore more difficult to maintain
    “I disagree.” That's OK and in fact Perl::Critic allows for a lot of customizations to alter or remove those policies you don't agree with
    BUT, I'm going to fix all the default suggestions for the purpose of this presentation
  • C-style "for" loop used at line 11, column 7. See page 100 of PBP. (Severity: 2)
    “What?” Damian recommends against using C-style “for” loops”
    “Why?” His main argument is that C-style “for” loops are more difficult to read and therefore more difficult to maintain
    “I disagree.” That's OK and in fact Perl::Critic allows for a lot of customizations to alter or remove those policies you don't agree with
    BUT, I'm going to fix all the default suggestions for the purpose of this presentation
  • C-style "for" loop used at line 11, column 7. See page 100 of PBP. (Severity: 2)
    “What?” Damian recommends against using C-style “for” loops”
    “Why?” His main argument is that C-style “for” loops are more difficult to read and therefore more difficult to maintain
    “I disagree.” That's OK and in fact Perl::Critic allows for a lot of customizations to alter or remove those policies you don't agree with
    BUT, I'm going to fix all the default suggestions for the purpose of this presentation
  • C-style "for" loop used at line 11, column 7. See page 100 of PBP. (Severity: 2)
    “What?” Damian recommends against using C-style “for” loops”
    “Why?” His main argument is that C-style “for” loops are more difficult to read and therefore more difficult to maintain
    “I disagree.” That's OK and in fact Perl::Critic allows for a lot of customizations to alter or remove those policies you don't agree with
    BUT, I'm going to fix all the default suggestions for the purpose of this presentation
  • $", $;, $ " (ignored space - crazy! Whitespace thus used to my advantage) even $_, @_
    use English;
    My obfuscated code is 74% non-alpha/space, and that isn't considering the abuse of alpha conventions like q qq s tr etc
    The higher percentage of non-alpha characters, the more difficult to read
  • $", $;, $ " (ignored space - crazy! Whitespace thus used to my advantage) even $_, @_
    use English;
    My obfuscated code is 74% non-alpha/space, and that isn't considering the abuse of alpha conventions like q qq s tr etc
    The higher percentage of non-alpha characters, the more difficult to read
  • $", $;, $ " (ignored space - crazy! Whitespace thus used to my advantage) even $_, @_
    use English;
    My obfuscated code is 74% non-alpha/space, and that isn't considering the abuse of alpha conventions like q qq s tr etc
    The higher percentage of non-alpha characters, the more difficult to read
  • $", $;, $ " (ignored space - crazy! Whitespace thus used to my advantage) even $_, @_
    use English;
    My obfuscated code is 74% non-alpha/space, and that isn't considering the abuse of alpha conventions like q qq s tr etc
    The higher percentage of non-alpha characters, the more difficult to read
  • $", $;, $ " (ignored space - crazy! Whitespace thus used to my advantage) even $_, @_
    use English;
    My obfuscated code is 74% non-alpha/space, and that isn't considering the abuse of alpha conventions like q qq s tr etc
    The higher percentage of non-alpha characters, the more difficult to read
  • Conformity can be good: code like everyone else has in the apps you inherit or join. Unless it's horrible, then start a consistent style.
    Then how to implement good change? One idea: implement across the entire code base, a little at a time. For example, get "use strict" working everywhere first.
    Make it easy on the "next guy". The next guy might be YOU.
    Comment to pre-empt disputes or misunderstandings that will break production.
  • Conformity can be good: code like everyone else has in the apps you inherit or join. Unless it's horrible, then start a consistent style.
    Then how to implement good change? One idea: implement across the entire code base, a little at a time. For example, get "use strict" working everywhere first.
    Make it easy on the "next guy". The next guy might be YOU.
    Comment to pre-empt disputes or misunderstandings that will break production.
  • Conformity can be good: code like everyone else has in the apps you inherit or join. Unless it's horrible, then start a consistent style.
    Then how to implement good change? One idea: implement across the entire code base, a little at a time. For example, get "use strict" working everywhere first.
    Make it easy on the "next guy". The next guy might be YOU.
    Comment to pre-empt disputes or misunderstandings that will break production.
  • Conformity can be good: code like everyone else has in the apps you inherit or join. Unless it's horrible, then start a consistent style.
    Then how to implement good change? One idea: implement across the entire code base, a little at a time. For example, get "use strict" working everywhere first.
    Make it easy on the "next guy". The next guy might be YOU.
    Comment to pre-empt disputes or misunderstandings that will break production.
  • Writing Maintainable Perl

    1. 1. Writing Maintainable Perl David M. Bradford
    2. 2. Who am I? David Bradford @tinypig tinypig.com
    3. 3. Who am I? ● Web Engineer at OmniTI – Full stack tech consulting – Remote database management and consulting – Large Scale / Mission Critical – We're Hiring! – omniti.com
    4. 4. Why This Talk? ● Is Perl a "write-only" language?
    5. 5. Why This Talk? ● Is Perl a "write-only" language? No!
    6. 6. Why This Talk? ● Is Perl a "write-only" language? No! ● ...but it can be.
    7. 7. Why This Talk? ● Is Perl a "write-only" language? No! ● ...but it can be. ● It's our responsibility to keep code:
    8. 8. Why This Talk? ● Is Perl a "write-only" language? No! ● ...but it can be. ● It's our responsibility to keep code: – Easy to read
    9. 9. Why This Talk? ● Is Perl a "write-only" language? No! ● ...but it can be. ● It's our responsibility to keep code: – Easy to read, so it will be:
    10. 10. Why This Talk? ● Is Perl a "write-only" language? No! ● ...but it can be. ● It's our responsibility to keep code: – Easy to read, so it will be: – Easy to maintain
    11. 11. My Function, “listify” sub listify { my ($aref,$cc) = @_; if( ref $aref eq 'ARRAY' && $cc > 0 ) { my $j; for(my $i=0; $i<=$#$aref; $i+=$cc) { push @$j, [@$aref[$i..$i+$cc-1]]; } $#{$j->[$#{$j}]}=$#$aref%$cc; @$aref = @$j; return 1; } return; }
    12. 12. Purpose of listify Take a list, for example: List # 1, with 10 items one two three four five six seven eight nine ten Give me several lists back, each containing no more than N items, for example, 4: List #1 with 4 items: one two three four List #2 with 4 items: five six seven eight List #3 with 2 items: nine ten
    13. 13. Prepare to call listify listify( @list, 4 ); Result: @array = ( [ 'one', 'two', 'three', 'four' ], [ 'five', 'six', 'seven', 'eight' ], [ 'nine', 'ten' ], ); my @list = qw( one two three four five six seven eight nine ten );
    14. 14. Call to listify my @list = qw( one two three four five six seven eight nine ten ); Result: @array = ( [ 'one', 'two', 'three', 'four' ], [ 'five', 'six', 'seven', 'eight' ], [ 'nine', 'ten' ], ); listify( @list, 4 );
    15. 15. Result of listify my @list = qw( one two three four five six seven eight nine ten ); listify( @list, 4 ); Result: @array = ( [ 'one', 'two', 'three', 'four' ], [ 'five', 'six', 'seven', 'eight' ], [ 'nine', 'ten' ], );
    16. 16. Declare function listify sub listify { my ($aref,$cc) = @_; if( ref $aref eq 'ARRAY' && $cc > 0 ) { my $j; for(my $i=0; $i<=$#$aref; $i+=$cc) { push @$j, [@$aref[$i..$i+$cc-1]]; } $#{$j->[$#{$j}]}=$#$aref%$cc; @$aref = @$j; return 1; } return; } sub listify { }
    17. 17. Get Parameters sub listify { my ($aref,$cc) = @_; if( ref $aref eq 'ARRAY' && $cc > 0 ) { my $j; for(my $i=0; $i<=$#$aref; $i+=$cc) { push @$j, [@$aref[$i..$i+$cc-1]]; } $#{$j->[$#{$j}]}=$#$aref%$cc; @$aref = @$j; return 1; } return; } my ($aref,$cc) = @_;
    18. 18. Validate Parameters sub listify { my ($aref,$cc) = @_; if( ref $aref eq 'ARRAY' && $cc > 0 ) { my $j; for(my $i=0; $i<=$#$aref; $i+=$cc) { push @$j, [@$aref[$i..$i+$cc-1]]; } $#{$j->[$#{$j}]}=$#$aref%$cc; @$aref = @$j; return 1; } return; } if( ref $aref eq 'ARRAY' && $cc > 0 ) { } return;
    19. 19. Declare Return Variable sub listify { my ($aref,$cc) = @_; if( ref $aref eq 'ARRAY' && $cc > 0 ) { my $j; for(my $i=0; $i<=$#$aref; $i+=$cc) { push @$j, [@$aref[$i..$i+$cc-1]]; } $#{$j->[$#{$j}]}=$#$aref%$cc; @$aref = @$j; return 1; } return; } my $j;
    20. 20. Loop Through Input Array sub listify { my ($aref,$cc) = @_; if( ref $aref eq 'ARRAY' && $cc > 0 ) { my $j; for(my $i=0; $i<=$#$aref; $i+=$cc) { push @$j, [@$aref[$i..$i+$cc-1]]; } $#{$j->[$#{$j}]}=$#$aref%$cc; @$aref = @$j; return 1; } return; } for(my $i=0; $i<=$#$aref; $i+=$cc) { }
    21. 21. Add List to Output Variable sub listify { my ($aref,$cc) = @_; if( ref $aref eq 'ARRAY' && $cc > 0 ) { my $j; for(my $i=0; $i<=$#$aref; $i+=$cc) { push @$j, [@$aref[$i..$i+$cc-1]]; } $#{$j->[$#{$j}]}=$#$aref%$cc; @$aref = @$j; return 1; } return; } push @$j, [@$aref[$i..$i+$cc-1]];
    22. 22. ??? sub listify { my ($aref,$cc) = @_; if( ref $aref eq 'ARRAY' && $cc > 0 ) { my $j; for(my $i=0; $i<=$#$aref; $i+=$cc) { push @$j, [@$aref[$i..$i+$cc-1]]; } $#{$j->[$#{$j}]}=$#$aref%$cc; @$aref = @$j; return 1; } return; } $#{$j->[$#{$j}]}=$#$aref%$cc;
    23. 23. Update Input Array In Place sub listify { my ($aref,$cc) = @_; if( ref $aref eq 'ARRAY' && $cc > 0 ) { my $j; for(my $i=0; $i<=$#$aref; $i+=$cc) { push @$j, [@$aref[$i..$i+$cc-1]]; } $#{$j->[$#{$j}]}=$#$aref%$cc; @$aref = @$j; return 1; } return; } @$aref = @$j;
    24. 24. Return 1 to Indicate Success sub listify { my ($aref,$cc) = @_; if( ref $aref eq 'ARRAY' && $cc > 0 ) { my $j; for(my $i=0; $i<=$#$aref; $i+=$cc) { push @$j, [@$aref[$i..$i+$cc-1]]; } $#{$j->[$#{$j}]}=$#$aref%$cc; @$aref = @$j; return 1; } return; } return 1;
    25. 25. Clever (but not easy to read) Line $#{$j->[$#{$j}]}=$#$aref%$cc;
    26. 26. Purpose of Clever Line The purpose of the line is to truncate the final array to the number of remaining elements so we don't end up with this: @array = ( [ 'one', 'two', 'three', 'four' ], [ 'five', 'six', 'seven', 'eight' ], [ 'nine', 'ten', undef, undef ], );
    27. 27. Improvement #1: Whitespace
    28. 28. Improvement #2: Refactor $#{ $j->[ -1 ] } = $#$aref % $cc; $#{ $j->[ $#{$j} ] } = $#$aref % $cc;
    29. 29. Improvement #3: Naming $#{ $result_array[ -1 ] } = $#$in_aref % $elements_per_array;
    30. 30. Improvement #4: Multiple Lines $final_aref = $result_array[ -1 ]; $#$final_aref = $#$in_aref % $elements_per_array;
    31. 31. Improvement #5: Split Another Line my $final_aref = $result_array[ -1 ]; my $elements_in_final = $#$in_aref % $elements_per_array; $#$final_aref = $elements_in_final;
    32. 32. Improvement #6: More Whitespace my $final_aref = $result_array[ -1 ]; my $elements_in_final = $#$in_aref % $elements_per_array; $#$final_aref = $elements_in_final;
    33. 33. Improvement #7: A comment! my $final_aref = $result_array[ -1 ]; my $elements_in_final = $#$in_aref % $elements_per_array; # Truncate final array $#$final_aref = $elements_in_final;
    34. 34. Easier to Read Now? Original: $#{$j->[$#{$j}]}=$#$aref%$cc; Revised: my $final_aref = $result_array[ -1 ]; my $elements_in_final = $#$in_aref % $elements_per_array; # Truncate final array $#$final_aref = $elements_in_final;
    35. 35. Apply Principles to Entire Function sub listify { my ( $in_aref, $elements_per_array ) = @_; return if ( ref $in_aref ne 'ARRAY' or $elements_per_array <= 0 ); my @result_array; for( my $i = 0; $i <= $#$in_aref; $i += $elements_per_array ) { push @result_array, [ @$in_aref[ $i..$i + $elements_per_array - 1 ] ]; } # Continued on next slide
    36. 36. Apply Principles to Entire Function # Continued from previous slide my $final_aref = $result_array[ -1 ]; my $elements_in_final = $#$in_aref % $elements_per_array; # Truncate final array $#$final_aref = $elements_in_final; @$in_aref = @result_array; }
    37. 37. Helpful Modules Perl::Critic based on the book “Perl Best Practices” by Damian Conway Perl::Tidy increase readability of code
    38. 38. perlcritic Result $ perlcritic -1 listify.pl Code not contained in explicit package at line 1, column 1. Violates encapsulation. (Severity: 4) Code before strictures are enabled at line 1, column 1. See page 429 of PBP. (Severity: 5) Subroutine "listify" does not end with "return" at line 1, column 1. See page 197 of PBP. (Severity: 4) Code before warnings are enabled at line 1, column 1. See page 431 of PBP. (Severity: 4) No package-scoped "$VERSION" variable found at line 1, column 1. See page 404 of PBP. (Severity: 2) C-style "for" loop used at line 10, column 7. See page 100 of PBP. (Severity: 2) Double-sigil dereference at line 10, column 26. See page 228 of PBP. (Severity: 2) (more double-sigil warnings follow)
    39. 39. Critical Fixes Code not contained in explicit package at line 1, column 1. Violates encapsulation. (Severity: 4) Code before strictures are enabled at line 1, column 1. See page 429 of PBP. (Severity: 5) Code before warnings are enabled at line 1, column 1. See page 431 of PBP. (Severity: 4) sub listify { my ( $in_aref, $elements_per_array ) = @_; # --- cut --- #!/usr/bin/perl use strict; use warnings;
    40. 40. Explicit Return Subroutine "listify" does not end with "return" at line 2, column 1. See page 197 of PBP. (Severity: 4) # Truncate final array $#$final_aref = $elements_in_final; @$in_aref = @result_array; } return;
    41. 41. $VERSION No package-scoped "$VERSION" variable found at line 1, column 1. See page 404 of PBP. (Severity: 2) #!/usr/bin/perl use strict; use warnings; sub listify { my ( $in_aref, $elements_per_array ) = @_; # --- cut --- our $VERSION = '1.19';
    42. 42. C-style "for" Loop C-style "for" loop used at line 11, column 7. See page 100 of PBP. (Severity: 2)
    43. 43. C-style "for" Loop C-style "for" loop used at line 11, column 7. See page 100 of PBP. (Severity: 2) ● “What?” No C-style “for” loops
    44. 44. C-style "for" Loop C-style "for" loop used at line 11, column 7. See page 100 of PBP. (Severity: 2) ● “What?” No C-style “for” loops ● “Why?” More difficult to read and maintain
    45. 45. C-style "for" Loop C-style "for" loop used at line 11, column 7. See page 100 of PBP. (Severity: 2) ● “What?” No C-style “for” loops ● “Why?” More difficult to read and maintain ● “I disagree.” That's OK. Modify Perl::Critic settings!
    46. 46. C-style "for" Loop C-style "for" loop used at line 11, column 7. See page 100 of PBP. (Severity: 2) ● “What?” No C-style “for” loops ● “Why?” More difficult to read and maintain ● “I disagree.” That's OK. Modify Perl::Critic settings! But today, I'm going to fix all the defaults
    47. 47. Change to “while” loop C-style "for" loop used at line 11, column 7. See page 100 of PBP. (Severity: 2) Old: for( my $i = 0; $i <= $#$in_aref; $i += $elements_per_array ) { # (loop contents) } New: my $i = 0; while($i <= $#$in_aref) { # (loop contents) $i += $elements_per_array; }
    48. 48. Double-sigil dereference ● Caused by – @$my_array_reference ● Fixed with – @{ $my_array_reference }
    49. 49. JAPH: The Do-Not Example sub q{ord($_[0])}sub qq{chr($_[0])}(@q=(j,q,q,,q,s,,u,o,[$;=@q,$"=sub{for(@q){ s/(.)/&qq(&q($1)-1)/e}},$_=q,*534`!./4(%2`0%2{`(!#+%2,,tr;{;,;,s/(.)/&q($1)>95? &qq(&q($1)-64):&qq(&q($ 1)+64)/eg,% q=(q,qq,,sub{eval$ _[0]},q,q,,1)])),&{$ "}, $q{qq}->(qq.$;->[$q=$q{q}]${$;}[++$q]$;->[$[]${$;}[-1+$)-$ )+$#q]$;->[$#q-$q].)
    50. 50. JAPH: The Do-Not Example sub q{ord($_[0])}sub qq{chr($_[0])}(@q=(j,q,q,,q,s,,u,o,[$;=@q,$"=sub{for(@q){ s/(.)/&qq(&q($1)-1)/e}},$_=q,*534`!./4(%2`0%2{`(!#+%2,,tr;{;,;,s/(.)/&q($1)>95? &qq(&q($1)-64):&qq(&q($ 1)+64)/eg,% q=(q,qq,,sub{eval$ _[0]},q,q,,1)])),&{$ "}, $q{qq}->(qq.$;->[$q=$q{q}]${$;}[++$q]$;->[$[]${$;}[-1+$)-$ )+$#q]$;->[$#q-$q].) ● Ugly variables $" $; $ " and even $_, @_
    51. 51. JAPH: The Do-Not Example sub q{ord($_[0])}sub qq{chr($_[0])}(@q=(j,q,q,,q,s,,u,o,[$;=@q,$"=sub{for(@q){ s/(.)/&qq(&q($1)-1)/e}},$_=q,*534`!./4(%2`0%2{`(!#+%2,,tr;{;,;,s/(.)/&q($1)>95? &qq(&q($1)-64):&qq(&q($ 1)+64)/eg,% q=(q,qq,,sub{eval$ _[0]},q,q,,1)])),&{$ "}, $q{qq}->(qq.$;->[$q=$q{q}]${$;}[++$q]$;->[$[]${$;}[-1+$)-$ )+$#q]$;->[$#q-$q].) ● Ugly variables $" $; $ " and even $_, @_ – use English;
    52. 52. JAPH: The Do-Not Example sub q{ord($_[0])}sub qq{chr($_[0])}(@q=(j,q,q,,q,s,,u,o,[$;=@q,$"=sub{for(@q){ s/(.)/&qq(&q($1)-1)/e}},$_=q,*534`!./4(%2`0%2{`(!#+%2,,tr;{;,;,s/(.)/&q($1)>95? &qq(&q($1)-64):&qq(&q($ 1)+64)/eg,% q=(q,qq,,sub{eval$ _[0]},q,q,,1)])),&{$ "}, $q{qq}->(qq.$;->[$q=$q{q}]${$;}[++$q]$;->[$[]${$;}[-1+$)-$ )+$#q]$;->[$#q-$q].) ● Ugly variables $" $; $ " and even $_, @_ – use English; ● 74% non-alpha/space
    53. 53. JAPH: The Do-Not Example sub q{ord($_[0])}sub qq{chr($_[0])}(@q=(j,q,q,,q,s,,u,o,[$;=@q,$"=sub{for(@q){ s/(.)/&qq(&q($1)-1)/e}},$_=q,*534`!./4(%2`0%2{`(!#+%2,,tr;{;,;,s/(.)/&q($1)>95? &qq(&q($1)-64):&qq(&q($ 1)+64)/eg,% q=(q,qq,,sub{eval$ _[0]},q,q,,1)])),&{$ "}, $q{qq}->(qq.$;->[$q=$q{q}]${$;}[++$q]$;->[$[]${$;}[-1+$)-$ )+$#q]$;->[$#q-$q].) ● Ugly variables $" $; $ " and even $_, @_ – use English; ● 74% non-alpha/space – More non-alpha characters, more difficult to read
    54. 54. Other Considerations ● Conformity can be good
    55. 55. Other Considerations ● Conformity can be good ● How to implement good change?
    56. 56. Other Considerations ● Conformity can be good ● How to implement good change? ● The “next guy” might be YOU.
    57. 57. Other Considerations ● Conformity can be good ● How to implement good change? ● The “next guy” might be YOU. ● Comment to preempt disputes or misunderstandings
    58. 58. Best Book on this Topic Perl Best Practices by Damian Conway
    59. 59. Questions?
    60. 60. Thank You! David Bradford @tinypig tinypig.com Web Engineer at OmniTI omniti.com/presents

    ×