Perl::Critic & Perl Best Practices

Chris Dolan

Equilibrious LLC

cdolan@cpan.org

About Me

Perl Best Practices

→ Use code to optimize for you!

What is Perl::Critic?

Static source code analyzer

by Jeffrey Ryan Thalhammer and crew

Policies

PPI

Built on top of PPI, the pure-perl Perl parser.

% ppidump 'print "Hello, World";'
PPI::Document
  PPI::Statement
    PPI::Token::Word    'print'
    PPI::Token::Whitespace      ' '
    PPI::Token::Quote::Double   '"Hello, World"'
    PPI::Token::Structure       ';'

Installation

% cpan install Perl::Critic

Usage: Command line

% perlcritic PerfectCode.pm
# OK

% perlcritic LegacyCode.pm
Code before strictures are enabled at line 3, column 1. See page 429 of PBP. (Severity: 5)
Expression form of 'eval' at line 4, column 4. See page 161 of PBP. (Severity: 5)

%  

Usage: Unit tests

use Test::Perl::Critic;
all_critic_ok();

(for example in t/perlcritic.t)

Usage: Web

Upload code via http://perlcritic.com/

Usage

Lenient (the default)
% perlcritic -5 Foo.pm

Typical
% perlcritic -3 Foo.pm

Pedantic
% perlcritic -1 Foo.pm

Examples:
RequireBarewordIncludes
require "lib/Foo.pm"; vs. require Foo;
ProhibitExplicitISA
@ISA = qw(Foo); vs. use base 'Foo';
ProhibitParensWithBuiltins
print("foo\n"); vs. print "foo\n";

Configuration

Create a ~/.perlcriticrc file

[-CodeLayout::RequireTidyCode]
[-ControlStructures::ProhibitCStyleForLoops]
[-Documentation::RequirePodAtEnd]
[-Documentation::RequirePodSections]
[-Miscellanea::RequireRcsKeywords]
[-NamingConventions::ProhibitMixedCaseSubs]

[ControlStructures::ProhibitCascadingIfElse]
max_elsif = 3

Making Exceptions

Perl::Critic has flags to ignore special cases

Learn more

Policy Creation: Choose Goal

Pick a useful goal: detect calls to undeclared functions.

sub main {
   output_strnig('Hello, World!');
}

sub output_string {
   my ($msg) = @_;
   print $msg, "\n";
}

Policy Creation: Write test

Create a file: t/Subroutines/ProhibitUndeclaredSub.run

## name sub exists
## failures 0
foo();
sub foo { }

## name sub missing
## failures 1
bar();

Policy Creation: Look at PPI DOM

Run: ppidump 'bar();'

PPI::Document
  PPI::Statement
    PPI::Token::Word    'bar'
    PPI::Structure::List        ( ... )
    PPI::Token::Structure       ';'

Policy Creation: Look at PPI DOM

Run: ppidump 'sub foo { print; }'

PPI::Document
  PPI::Statement::Sub
    PPI::Token::Word    'sub'
    PPI::Token::Word    'foo'
    PPI::Structure::Block       { ... }
      PPI::Statement
        PPI::Token::Word        'print'
        PPI::Token::Structure   ';'

Policy Creation: Start the policy

package Perl::Critic::Policy::
  Subroutines::ProhibitUndeclaredSub;

use warnings;
use strict;
use Perl::Critic::Utils;
use base 'Perl::Critic::Policy';

sub applies_to {
   return 'PPI::Token::Word';
}

Policy Creation: Heart of the policy

sub violates {
   my ( $self, $elem, $doc ) = @_;

   return if !is_function_call($elem);

   my $subs = $doc->find('PPI::Statement::Sub');
   for my $sub ( @{ $subs } ) {
      return if $sub->name eq $elem;
   }

   return $self->violation($desc, $expl, $elem);
}

Policy Creation: Run tests

% t/20_policies.t Subroutines::ProhibitUndeclaredSub
1..3
ok 1 - Perl::Critic::Policy::Subroutines::
       ProhibitUndeclaredSub->can('violates')
ok 2 - Subroutines::ProhibitUndeclaredSub - 
       line 1 - sub exists
ok 3 - Subroutines::ProhibitUndeclaredSub - 
       line 7 - sub missing

Success!

Conclusion

Perl::Critic is: