Home / Alt manpages / perlstyle(1)

  • perlstyle(1)
  • User command
  • linux

Make Perl Code Easier to Review with perlstyle

A script that runs is not the same as a script a reviewer can trust in thirty seconds, and perlstyle(1) is Perl's own list of what closes that gap. This guide turns its main advice into a small working script: strict checking and warnings on, readable layout, explicit error handling, and a syntax check you can repeat before review. The examples target Perl 5.38.2 and the perl-doc package installed on this machine.

Allow about 20 minutes for the example and a first pass over an existing script. You need a shell, Perl, and a text editor. Everything below runs as your normal user and writes only a file in a temporary working directory. No sudo, service restart, module installation, or global configuration is required.

1. Confirm the local Perl guidance

Read the installed manual and record the interpreter version before adopting its version-specific opening pattern:

$ man perlstyle
$ perl -v | sed -n '1,4p'
This is perl 5, version 38, subversion 2 (v5.38.2)

The installed manual recommends use v5.36; or newer as a concise way to enable both strict and warnings, along with other named features. That is a version requirement for the script. If your deployment still supports an older Perl, do not copy it blindly: use that deployment's compatibility policy and enable the pragmata explicitly if needed.

Checkpoint

You know which interpreter will run the code and whether the requested Perl version is acceptable for your target machines.

2. Start with checks that fail early

Create a temporary example: a program that reads a directory path from its first argument, counts ordinary directory entries, and reports any system-call failure with the path and Perl's operating-system error.

$ workdir=$(mktemp -d)
$ cd "$workdir"
$ mkdir sample
$ touch sample/one.txt sample/two.txt
$ editor count_entries.pl

Put this in count_entries.pl:

use v5.36;

my $directory = $ARGV[0] // die "usage: $0 DIRECTORY\n";

opendir(my $handle, $directory)
    or die "can't opendir $directory: $!\n";

my @entries = grep { $_ ne '.' && $_ ne '..' } readdir($handle);
closedir($handle)
    or die "can't closedir $directory: $!\n";

say "$directory: " . scalar(@entries) . " entries";

The explicit use v5.36; line enables strict and warnings for this example. The manual warns against relying on the older -w switch or the $^W variable, because those settings can affect code you did not write, modules included. The or die checks matter just as much: a failed system call should name the operation and include $!, so the operating system's reason is not thrown away.

Run the script with a known directory:

$ perl count_entries.pl sample
sample: 2 entries

Then try a missing path. This is safe and shows the useful failure boundary:

$ perl count_entries.pl missing
can't opendir missing: No such file or directory

The wording after the colon depends on the operating system. The part a caller should actually rely on is the non-zero exit status.

3. Make control flow visible

Prefer the form that keeps the main action obvious. A trailing conditional works when the action is still the sentence's subject:

print "starting analysis\n" if $verbose;

Do not hide that action behind a punctuation expression just to save a line:

$verbose && print "starting analysis\n";

For error handling, the manual likewise favours open(...) or die ... over burying the important operation inside an unless modifier. These are style choices, not a claim that Perl cannot run the shorter alternatives: pick the form that lets a reviewer find the operation and its failure path fast.

When a loop needs to stop partway through, label it and use last or next instead of rearranging the body into something harder to read:

LINE:
for my $line (<STDIN>) {
    chomp $line;
    last LINE if $line eq 'STOP';
    next LINE if $line =~ /^#/;
    print "$line\n";
}

Labels are ordinary Perl syntax. Use them when they say which loop a transfer belongs to, especially with nested loops, and keep the label and its loop close together so the relationship stays visible.

4. Use names and layout that carry meaning

Four-column indentation, spaces around most operators, a space after commas, and blank lines between separate jobs give the reader a predictable shape. Put an opening brace beside the construct where possible, and line the closing brace up with the keyword that opened the block:

sub readable_name {
    my ($input_path, $verbose) = @_;

    my $result = process_file($input_path);
    print "processed $input_path\n" if $verbose;
    return $result;
}
  • Use mnemonic names. Short names can be fine in a small scope, but longer identifiers separated with underscores, such as $input_path, are easier to scan.
  • Let case carry a signal. Lower case for function-scope variables, mixed case for package-wide values, capitals for constants, as a convention rather than a rule that replaces a descriptive name.
  • Keep function and method names lower case, with parentheses when discussed as calls.
  • Give packages a capital and mixed case as the exception: non-pragma modules conventionally look like Module::Name.

Do not turn this into a mechanical renaming exercise. Preserve public interfaces and make one small, reviewable change at a time.

5. Simplify difficult expressions without changing behaviour

Parentheses remove ambiguity when several list operators or defaults are stacked together. Compare a compressed expression a reader has to reverse-engineer with one that shows the intended order:

my @names = sort { lc($a) cmp lc($b) } @input_names;
my $first_name = $names[0];

For a regular expression that has turned into line noise, use the /x modifier and lay its parts out with comments or whitespace, choosing a delimiter that does not fight the expression's own slashes or backslashes:

my $valid = $value =~ m{
    \A [a-z0-9_]+ \z
}x;

The modifier changes how literal whitespace in the pattern is read, so test the expression again after expanding it. Do not use grep, map, or backticks purely to throw away their return values: use for or foreach for iteration, and an explicitly checked system call with a carefully defined argument list for an external command.

6. Check, test, and review the result

First ask Perl to compile the file without running it:

$ perl -c count_entries.pl
count_entries.pl syntax OK

Then run the normal case and the failure case again. A syntax check does not prove that directory permissions, input data, external commands, or portability assumptions are correct: test those separately.

When code uses a feature that may not exist on every target, the manual suggests testing it in an eval or checking the running Perl version. Keep the compatibility test near the feature it protects, and document the minimum version instead of making readers infer it from scattered syntax. A script that declares use v5.36; already fails early on an older interpreter, which does that job for you.

For reusable code, consider whether a one-off script should become a module or object class, and document it with consistent POD: mark functions, variables and module names as code, and make command and file names recognisable. Above all, be consistent within the project. Consistency reduces the number of local rules a reviewer has to hold in mind.

Checkpoint

Compilation passes, the intended output is reproduced, and a deliberately bad input produces a useful non-zero failure.

Done means

  • The script declares its required Perl version and gets strict checking and warnings through that declaration.
  • System calls check their return values and report the failed operation with $!.
  • Control flow, loop exits, names, indentation, and expressions are readable without relying on clever defaults.
  • perl -c reports syntax OK, and both the normal and failure cases have been exercised.
  • Nothing elevated or persistent was needed. Remove the temporary example with rm -rf "$workdir" only while $workdir still holds the path mktemp printed.