Preflight: catching bugs before runtime#

This chapter covers the pragmas, compile-time checks, and runtime hygiene settings that prevent most bugs from reaching the debugger at all. A clean preflight is the shortest path to a short debugging session.

Readers who already use v5.36 or later run under strict and warnings by default and can skip straight to the diagnostics and autoflush sections. The rest of this chapter assumes you might be maintaining older code where neither is implicit.

Strict and warnings#

Since Perl 5.36, use v5.36; (and any declaration of a higher version) implicitly enables use strict; and use warnings;. Modern scripts do not write those lines — the version declaration covers both:

use v5.40;

use strict refuses three classes of sloppy code at compile time: undeclared variables (strict vars), symbolic references (strict refs), and barewords used as strings (strict subs). use warnings turns on runtime diagnostics for uninitialised reads, lossy numeric conversions, filehandles used after close, and several dozen other sharp edges.

Legacy code that uses neither still benefits from both: add them, fix the symbols they complain about, commit.

Narrow opt-outs remain useful:

no strict 'refs';                   # genuine symbolic deref
no warnings 'uninitialized';        # known-safe defaulting
no warnings 'experimental::try';    # on 5.36, 5.38 — see below

Never blanket-disable: no warnings; with no category list is almost always wrong.

Promote warnings to fatal for a region:

local $SIG{__WARN__} = sub { die $_[0] };

Useful at program top during development; the first warning becomes a stack-trace-producing crash.

use diagnostics#

Expands each warning or fatal message into the full paragraph from perldiag:

use diagnostics;

The output is verbose — too verbose for production — but during debugging it explains what a terse message like “Odd number of elements in hash assignment” actually means and how to fix it.

Post-hoc version: pipe a program’s stderr through the splain command to annotate warnings after the fact.

Compile-time checks#

perl -c script.pl parses the script and runs every BEGIN / use, then stops before the first runtime statement. A clean -c means the file and its dependencies compile; it does not mean the program runs correctly.

perl -c script.pl
script.pl syntax OK

Combine with -w to enable warnings globally for the compile phase and any compile-time code:

perl -cw script.pl

perl -MO=Deparse script.pl prints what perl actually parsed — precedence misparses, constant-folded branches, for rewritten as while. Use it to verify that perl saw what you meant:

perl -MO=Deparse script.pl

perl -MO=Deparse,-p forces full parenthesisation and makes precedence explicit.

perl -MO=Concise script.pl prints a compact op-tree dump; mostly for readers doing deep optimisation work.

autodie: make failure throw#

Built-ins that signal failure via false return values (open, close, chdir, unlink) are easy to forget to check. autodie replaces them with versions that throw:

use autodie qw(open close chdir);

open my $fh, '<', $path;     # now dies on failure; no `or die` needed

autodie is lexical: only calls in its scope are affected. Narrow the opt-out for a specific call site:

{
    no autodie 'open';
    open my $fh, '<', $path or warn "cannot read $path: $!";
}

Prefer autodie to the retired Fatal module, which did not cover functions returning counts.

Unbuffered output#

When diagnostic output vanishes before a crash, or arrives interleaved oddly with warn messages, the cause is almost always block-buffered stdout. Set $| at the top of the script:

$| = 1;

Every print now flushes immediately. The cost is a small I/O-throughput hit; the gain is that what you see on the terminal matches what the program has actually done, in order.

For a specific filehandle without changing the selected one:

use IO::Handle;
$log_fh->autoflush(1);

Common early diagnostics and what they mean#

Diagnostic

Meaning

Global symbol "$x" requires explicit package name

Strict is on and $x was never declared; add my.

Odd number of elements in hash assignment

A list assigned to a hash has odd count; a qw(...) or string with an embedded space produced two values where one was expected.

Use of uninitialized value in ...

An undef reached an operator. Usually a typo in a variable name or a missing return.

Name "main::x" used only once: possible typo

A package variable was referenced exactly once. Almost always a typo or a missing my.

Can't locate Foo.pm in @INC

A use or require could not find the module. Check spelling, @INC, PERL5LIB.

Reference found where even-sized list expected

my %h = { ... } — braces build a hashref; use parens.

Find out more#

  • print-and-die — when preflight is not enough and you need structured runtime diagnostics.

  • exceptions — turning failure paths into typed exceptions the caller can match on.