Enforcing Simple Standards with One Module

It's fair to say that at our consulting company, we work with many clients who use Perl heavily. The "preamble" of their Perl code is either an ad-hoc mixture of features, or stock boilerplate like this which gets cut-n-pasted all over the place:

use strict;
use warnings;
use v5.24;
use feature "signatures";
no warnings 'experimental::signatures';
use utf8::all;
use Carp;

Both of those approaches are dead wrong. The "ad hoc" pragma list means it's hard to be sure what features are or are not available. The "standard boilerplate" approach means cutting-n-pasting and then hating yourself when you have to change that standard boilerplate.

Modern::Perl is a nice middle ground for avoiding this, but it may not be the features you want. For example, our free-to-play narrative browser game, Tau Station, doesn't use the C3 MRO because we don't use multiple inheritance (note: we enforce C3 on our DBIx::Class classes, of course).

Instead, we have our custom boilerplate, wrapped up in Veure::Module.

It looks like this:

package Veure::Module;

use 5.24.0;
use strict;
use warnings;
use feature ();
use utf8::all;
use Veure::Carp;

use Import::Into;

sub import {
    my ($class) = @_;

    my $caller = caller;

    warnings->import;
    warnings->unimport('experimental::signatures');
    strict->import;
    feature->import(qw/signatures :5.24/);
    utf8::all->import;
    Veure::Carp->import::into( $caller, qw(carp croak vcarp confess) );
}

sub unimport {
    warnings->unimport;
    strict->unimport;
    feature->unimport;
    utf8::all->unimport;
    Veure::Carp->unimport;
}

1;

__END__

=head1 NAME

Veure::Module - Use modern Perl features

=head1 SYNOPSIS

    use Veure::Module;

=head1 DESCRIPTION

This module is a replacement for the following:

    use strict;
    use warnings;
    use v5.24;
    use feature 'signatures';
    no warnings 'experimental::signatures';
    use utf8::all;
    use Veure::Carp qw(carp croak);

Now, for all of our modules, we can just drop use Veure::Module; at the top and our development work is much easier, and standardized.

The use of strict and warnings is obvious.

utf8::all might be controversial, but we've found it helpful.

Veure::Carp is our internal version of Carp (if called from Template Toolkit, it tells us the exact template name and also dumps environment variables).

A future version of this might include the autodie pragma. We only need to add it in one place and it should be backwards-compatible (we'll see).

The subroutine signatures feature, however, is the killer feature. If you're not using them, you should be, even if they're still experimental. We have a few clients who've made the switch and I've spoken to many others who have, too. This is the one experimental feature that more and more Perl shops are adopting. The reason is simple: your code will be more correct. Pass in the wrong number of arguments and it will die a horrible death. This has saved us so much grief in production that it's worth risking its experimental status.

When you're working on an "enterprise" code base, having standards is critical. I know that's something that many Perl devs don't like to hear (I've seen a dev get let go because they wouldn't adjust their brace style), but if you don't have standards, codebases get messy quickly. It's never too late to start. I know that Veure::Module won't meet your exact needs, but the idea of building standards into a single module is worth the trouble. Combine that with Perl::Critic and Perl::Tidy and you're well on your way to reigning in the headache of large codebases.

And for what it's worth (I know that LOC is a terrible metric), Tau Station is now around 1/3 of a million lines of code, if you also count what we've built on the front end.

As always, drop me a line if you need expert Perl software development.

6 Comments

Most of my code runs under a version of current version of perl I've built and loaded with distributions I use. However I have a couple of scripts for bootstrapping which run under the system perl but use quite a few of my own pure-perl modules. These modules are not-specific to bootstrapping so include my version of a Modern::Perl-inspired boilerplate module. The first thing the import() in that module does is return when running under system perl.

FWIW the boilerplate imports; I have not done anything for features yet. Modern::PBP::Perl has nice %FEATURES & %WARNINGS tables up to 5.24 'utf8::all' ->import::into($callerpackage, 'NO-GLOBAL'); 'English' ->import::into($callerpackage); 'IO::File' ->import::into($callerpackage); 'IO::Handle' ->import::into($callerpackage); 'autodie' ->import::into($callerpackage); if ($CARPALWAYBUTQUIETER) { # don't need the verbosity of java-style full-stacktrace 'Carp::Always::ButQuieter'->import::into($callerpackage); } else { 'Carp::Always'->import::into($callerpackage); } 'Data::Printer'->import::into($callerpackage, 'colored' => 0, 'quotekeys' => 1, 'scalarquotes' => "'", );

FWIW some boilerplate modules in CPAN Modern::PBP::Perl Modern::Perl features strictures pragma Sympatic features & modules utf8::all features & modules & more

Looks like you have a runaway " literal in the use feature statement. Or is that some really obscure Perl-Foo that I'm unfamiliar with?

No it's not :-)

  use feature "signatures';

OK, i have some problems with the feature->unimport.

To keep everything complicated here is the setup:

  • enabled feature 'try' if perl 5.34 via boilerplate
  • old script using Try::Tiny (reason to disable try-feature in scope/file)
  • old script may run under 5.32 or 5.34

I can't get something working for both perl versions.

use strict;
use warnings;
no warnings 'experimental';

# copied from boilerplate module
BEGIN {
    if ($] >= 5.034) {
        require feature;
        feature->import('try');
    }
}

# copied from script
use Try::Tiny;

# no feature 'try';
# # perl 5.34 -> OK
# # perl 5.32 -> Feature "try" is not supported by Perl 5.32.0

feature->unimport('try') if $] >= 5.034;
# perl 5.34 -> syntax error at ..., near "catch {"
# perl 5.32 -> OK

try { ; }
catch { ; };

it looks like that feature->unimport simple can't unimport 'try' or perhaps any feature?!

OK, to add some juice:

BEGIN {
    feature->unimport('try') if $] >= 5.034;
}

do the trick (so disable 'try' for the complete file i guess?!)

Leave a comment

About Ovid

user-pic Freelance Perl/Testing/Agile consultant and trainer. See http://www.allaroundtheworld.fr/ for our services. If you have a problem with Perl, we will solve it for you. And don't forget to buy my book! http://www.amazon.com/Beginning-Perl-Curtis-Poe/dp/1118013840/