perl-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Modern Perl Development Patterns

现代Perl开发模式

Idiomatic Perl 5.36+ patterns and best practices for building robust, maintainable applications.
适用于构建健壮、可维护应用程序的Perl 5.36+惯用模式与最佳实践。

When to Activate

适用场景

  • Writing new Perl code or modules
  • Reviewing Perl code for idiom compliance
  • Refactoring legacy Perl to modern standards
  • Designing Perl module architecture
  • Migrating pre-5.36 code to modern Perl
  • 编写新的Perl代码或模块
  • 审查Perl代码是否符合惯用范式
  • 将遗留Perl代码重构为现代标准
  • 设计Perl模块架构
  • 将5.36之前的代码迁移到现代Perl

How It Works

实践方式

Apply these patterns as a bias toward modern Perl 5.36+ defaults: signatures, explicit modules, focused error handling, and testable boundaries. The examples below are meant to be copied as starting points, then tightened for the actual app, dependency stack, and deployment model in front of you.
遵循现代Perl 5.36+的默认规范来应用这些模式:签名、显式模块、针对性错误处理以及可测试的边界。以下示例可作为起始模板,你可以根据实际应用、依赖栈和部署模型进行调整优化。

Core Principles

核心原则

1. Use
v5.36
Pragma

1. 使用
v5.36
编译指示

A single
use v5.36
replaces the old boilerplate and enables strict, warnings, and subroutine signatures.
perl
undefined
一行
use v5.36
即可替代旧的样板代码,同时启用strict、warnings和子例程签名功能。
perl
undefined

Good: Modern preamble

Good: Modern preamble

use v5.36;
sub greet($name) { say "Hello, $name!"; }
use v5.36;
sub greet($name) { say "Hello, $name!"; }

Bad: Legacy boilerplate

Bad: Legacy boilerplate

use strict; use warnings; use feature 'say', 'signatures'; no warnings 'experimental::signatures';
sub greet { my ($name) = @_; say "Hello, $name!"; }
undefined
use strict; use warnings; use feature 'say', 'signatures'; no warnings 'experimental::signatures';
sub greet { my ($name) = @_; say "Hello, $name!"; }
undefined

2. Subroutine Signatures

2. 子例程签名

Use signatures for clarity and automatic arity checking.
perl
use v5.36;
使用签名提升代码清晰度并自动检查参数数量。
perl
use v5.36;

Good: Signatures with defaults

Good: Signatures with defaults

sub connect_db($host, $port = 5432, $timeout = 30) { # $host is required, others have defaults return DBI->connect("dbi:Pg:host=$host;port=$port", undef, undef, { RaiseError => 1, PrintError => 0, }); }
sub connect_db($host, $port = 5432, $timeout = 30) { # $host is required, others have defaults return DBI->connect("dbi:Pg:host=$host;port=$port", undef, undef, { RaiseError => 1, PrintError => 0, }); }

Good: Slurpy parameter for variable args

Good: Slurpy parameter for variable args

sub log_message($level, @details) { say "[$level] " . join(' ', @details); }
sub log_message($level, @details) { say "[$level] " . join(' ', @details); }

Bad: Manual argument unpacking

Bad: Manual argument unpacking

sub connect_db { my ($host, $port, $timeout) = @_; $port //= 5432; $timeout //= 30; # ... }
undefined
sub connect_db { my ($host, $port, $timeout) = @_; $port //= 5432; $timeout //= 30; # ... }
undefined

3. Context Sensitivity

3. 上下文敏感性

Understand scalar vs list context — a core Perl concept.
perl
use v5.36;

my @items = (1, 2, 3, 4, 5);

my @copy  = @items;            # List context: all elements
my $count = @items;            # Scalar context: count (5)
say "Items: " . scalar @items; # Force scalar context
理解标量与列表上下文——这是Perl的核心概念。
perl
use v5.36;

my @items = (1, 2, 3, 4, 5);

my @copy  = @items;            # List context: all elements
my $count = @items;            # Scalar context: count (5)
say "Items: " . scalar @items; # Force scalar context

4. Postfix Dereferencing

4. 后缀解引用

Use postfix dereference syntax for readability with nested structures.
perl
use v5.36;

my $data = {
    users => [
        { name => 'Alice', roles => ['admin', 'user'] },
        { name => 'Bob',   roles => ['user'] },
    ],
};
使用后缀解引用语法提升嵌套结构的可读性。
perl
use v5.36;

my $data = {
    users => [
        { name => 'Alice', roles => ['admin', 'user'] },
        { name => 'Bob',   roles => ['user'] },
    ],
};

Good: Postfix dereferencing

Good: Postfix dereferencing

my @users = $data->{users}->@; my @roles = $data->{users}[0]{roles}->@; my %first = $data->{users}[0]->%*;
my @users = $data->{users}->@; my @roles = $data->{users}[0]{roles}->@; my %first = $data->{users}[0]->%*;

Bad: Circumfix dereferencing (harder to read in chains)

Bad: Circumfix dereferencing (harder to read in chains)

my @users = @{ $data->{users} }; my @roles = @{ $data->{users}[0]{roles} };
undefined
my @users = @{ $data->{users} }; my @roles = @{ $data->{users}[0]{roles} };
undefined

5. The
isa
Operator (5.32+)

5.
isa
运算符(5.32+)

Infix type-check — replaces
blessed($o) && $o->isa('X')
.
perl
use v5.36;
if ($obj isa 'My::Class') { $obj->do_something }
中缀类型检查——替代
blessed($o) && $o->isa('X')
perl
use v5.36;
if ($obj isa 'My::Class') { $obj->do_something }

Error Handling

错误处理

eval/die Pattern

eval/die模式

perl
use v5.36;

sub parse_config($path) {
    my $content = eval { path($path)->slurp_utf8 };
    die "Config error: $@" if $@;
    return decode_json($content);
}
perl
use v5.36;

sub parse_config($path) {
    my $content = eval { path($path)->slurp_utf8 };
    die "Config error: $@" if $@;
    return decode_json($content);
}

Try::Tiny (Reliable Exception Handling)

Try::Tiny(可靠的异常处理)

perl
use v5.36;
use Try::Tiny;

sub fetch_user($id) {
    my $user = try {
        $db->resultset('User')->find($id)
            // die "User $id not found\n";
    }
    catch {
        warn "Failed to fetch user $id: $_";
        undef;
    };
    return $user;
}
perl
use v5.36;
use Try::Tiny;

sub fetch_user($id) {
    my $user = try {
        $db->resultset('User')->find($id)
            // die "User $id not found\n";
    }
    catch {
        warn "Failed to fetch user $id: $_";
        undef;
    };
    return $user;
}

Native try/catch (5.40+)

原生try/catch(5.40+)

perl
use v5.40;

sub divide($x, $y) {
    try {
        die "Division by zero" if $y == 0;
        return $x / $y;
    }
    catch ($e) {
        warn "Error: $e";
        return;
    }
}
perl
use v5.40;

sub divide($x, $y) {
    try {
        die "Division by zero" if $y == 0;
        return $x / $y;
    }
    catch ($e) {
        warn "Error: $e";
        return;
    }
}

Modern OO with Moo

基于Moo的现代面向对象编程

Prefer Moo for lightweight, modern OO. Use Moose only when its metaprotocol is needed.
perl
undefined
优先使用Moo进行轻量级现代面向对象开发。仅当需要元协议时才使用Moose。
perl
undefined

Good: Moo class

Good: Moo class

package User; use Moo; use Types::Standard qw(Str Int ArrayRef); use namespace::autoclean;
has name => (is => 'ro', isa => Str, required => 1); has email => (is => 'ro', isa => Str, required => 1); has age => (is => 'ro', isa => Int, default => sub { 0 }); has roles => (is => 'ro', isa => ArrayRef[Str], default => sub { [] });
sub is_admin($self) { return grep { $_ eq 'admin' } $self->roles->@*; }
sub greet($self) { return "Hello, I'm " . $self->name; }
1;
package User; use Moo; use Types::Standard qw(Str Int ArrayRef); use namespace::autoclean;
has name => (is => 'ro', isa => Str, required => 1); has email => (is => 'ro', isa => Str, required => 1); has age => (is => 'ro', isa => Int, default => sub { 0 }); has roles => (is => 'ro', isa => ArrayRef[Str], default => sub { [] });
sub is_admin($self) { return grep { $_ eq 'admin' } $self->roles->@*; }
sub greet($self) { return "Hello, I'm " . $self->name; }
1;

Usage

Usage

my $user = User->new( name => 'Alice', email => 'alice@example.com', roles => ['admin', 'user'], );
my $user = User->new( name => 'Alice', email => 'alice@example.com', roles => ['admin', 'user'], );

Bad: Blessed hashref (no validation, no accessors)

Bad: Blessed hashref (no validation, no accessors)

package User; sub new { my ($class, %args) = @; return bless %args, $class; } sub name { return $[0]->{name} } 1;
undefined
package User; sub new { my ($class, %args) = @; return bless %args, $class; } sub name { return $[0]->{name} } 1;
undefined

Moo Roles

Moo角色

perl
package Role::Serializable;
use Moo::Role;
use JSON::MaybeXS qw(encode_json);
requires 'TO_HASH';
sub to_json($self) { encode_json($self->TO_HASH) }
1;

package User;
use Moo;
with 'Role::Serializable';
has name  => (is => 'ro', required => 1);
has email => (is => 'ro', required => 1);
sub TO_HASH($self) { { name => $self->name, email => $self->email } }
1;
perl
package Role::Serializable;
use Moo::Role;
use JSON::MaybeXS qw(encode_json);
requires 'TO_HASH';
sub to_json($self) { encode_json($self->TO_HASH) }
1;

package User;
use Moo;
with 'Role::Serializable';
has name  => (is => 'ro', required => 1);
has email => (is => 'ro', required => 1);
sub TO_HASH($self) { { name => $self->name, email => $self->email } }
1;

Native
class
Keyword (5.38+, Corinna)

原生
class
关键字(5.38+,Corinna)

perl
use v5.38;
use feature 'class';
no warnings 'experimental::class';

class Point {
    field $x :param;
    field $y :param;
    method magnitude() { sqrt($x**2 + $y**2) }
}

my $p = Point->new(x => 3, y => 4);
say $p->magnitude;  # 5
perl
use v5.38;
use feature 'class';
no warnings 'experimental::class';

class Point {
    field $x :param;
    field $y :param;
    method magnitude() { sqrt($x**2 + $y**2) }
}

my $p = Point->new(x => 3, y => 4);
say $p->magnitude;  # 5

Regular Expressions

正则表达式

Named Captures and
/x
Flag

命名捕获与
/x
标志

perl
use v5.36;
perl
use v5.36;

Good: Named captures with /x for readability

Good: Named captures with /x for readability

my $log_re = qr{ ^ (?<timestamp> \d{4}-\d{2}-\d{2} \s \d{2}:\d{2}:\d{2} ) \s+ [ (?<level> \w+ ) ] \s+ (?<message> .+ ) $ }x;
if ($line =~ $log_re) { say "Time: $+{timestamp}, Level: $+{level}"; say "Message: $+{message}"; }
my $log_re = qr{ ^ (?<timestamp> \d{4}-\d{2}-\d{2} \s \d{2}:\d{2}:\d{2} ) \s+ [ (?<level> \w+ ) ] \s+ (?<message> .+ ) $ }x;
if ($line =~ $log_re) { say "Time: $+{timestamp}, Level: $+{level}"; say "Message: $+{message}"; }

Bad: Positional captures (hard to maintain)

Bad: Positional captures (hard to maintain)

if ($line =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+[(\w+)]\s+(.+)$/) { say "Time: $1, Level: $2"; }
undefined
if ($line =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+[(\w+)]\s+(.+)$/) { say "Time: $1, Level: $2"; }
undefined

Precompiled Patterns

预编译模式

perl
use v5.36;
perl
use v5.36;

Good: Compile once, use many

Good: Compile once, use many

my $email_re = qr/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}$/;
sub validate_emails(@emails) { return grep { $_ =~ $email_re } @emails; }
undefined
my $email_re = qr/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}$/;
sub validate_emails(@emails) { return grep { $_ =~ $email_re } @emails; }
undefined

Data Structures

数据结构

References and Safe Deep Access

引用与安全深度访问

perl
use v5.36;
perl
use v5.36;

Hash and array references

Hash and array references

my $config = { database => { host => 'localhost', port => 5432, options => ['utf8', 'sslmode=require'], }, };
my $config = { database => { host => 'localhost', port => 5432, options => ['utf8', 'sslmode=require'], }, };

Safe deep access (returns undef if any level missing)

Safe deep access (returns undef if any level missing)

my $port = $config->{database}{port}; # 5432 my $missing = $config->{cache}{host}; # undef, no error
my $port = $config->{database}{port}; # 5432 my $missing = $config->{cache}{host}; # undef, no error

Hash slices

Hash slices

my %subset; @subset{qw(host port)} = @{$config->{database}}{qw(host port)};
my %subset; @subset{qw(host port)} = @{$config->{database}}{qw(host port)};

Array slices

Array slices

my @first_two = $config->{database}{options}->@[0, 1];
my @first_two = $config->{database}{options}->@[0, 1];

Multi-variable for loop (experimental in 5.36, stable in 5.40)

Multi-variable for loop (experimental in 5.36, stable in 5.40)

use feature 'for_list'; no warnings 'experimental::for_list'; for my ($key, $val) (%$config) { say "$key => $val"; }
undefined
use feature 'for_list'; no warnings 'experimental::for_list'; for my ($key, $val) (%$config) { say "$key => $val"; }
undefined

File I/O

文件I/O

Three-Argument Open

三参数Open

perl
use v5.36;
perl
use v5.36;

Good: Three-arg open with autodie (core module, eliminates 'or die')

Good: Three-arg open with autodie (core module, eliminates 'or die')

use autodie;
sub read_file($path) { open my $fh, '<:encoding(UTF-8)', $path; local $/; my $content = <$fh>; close $fh; return $content; }
use autodie;
sub read_file($path) { open my $fh, '<:encoding(UTF-8)', $path; local $/; my $content = <$fh>; close $fh; return $content; }

Bad: Two-arg open (shell injection risk, see perl-security)

Bad: Two-arg open (shell injection risk, see perl-security)

open FH, $path; # NEVER do this open FH, "< $path"; # Still bad — user data in mode string
undefined
open FH, $path; # NEVER do this open FH, "< $path"; # Still bad — user data in mode string
undefined

Path::Tiny for File Operations

使用Path::Tiny进行文件操作

perl
use v5.36;
use Path::Tiny;

my $file = path('config', 'app.json');
my $content = $file->slurp_utf8;
$file->spew_utf8($new_content);
perl
use v5.36;
use Path::Tiny;

my $file = path('config', 'app.json');
my $content = $file->slurp_utf8;
$file->spew_utf8($new_content);

Iterate directory

Iterate directory

for my $child (path('src')->children(qr/.pl$/)) { say $child->basename; }
undefined
for my $child (path('src')->children(qr/.pl$/)) { say $child->basename; }
undefined

Module Organization

模块组织

Standard Project Layout

标准项目结构

text
MyApp/
├── lib/
│   └── MyApp/
│       ├── App.pm           # Main module
│       ├── Config.pm        # Configuration
│       ├── DB.pm            # Database layer
│       └── Util.pm          # Utilities
├── bin/
│   └── myapp                # Entry-point script
├── t/
│   ├── 00-load.t            # Compilation tests
│   ├── unit/                # Unit tests
│   └── integration/         # Integration tests
├── cpanfile                 # Dependencies
├── Makefile.PL              # Build system
└── .perlcriticrc            # Linting config
text
MyApp/
├── lib/
│   └── MyApp/
│       ├── App.pm           # Main module
│       ├── Config.pm        # Configuration
│       ├── DB.pm            # Database layer
│       └── Util.pm          # Utilities
├── bin/
│   └── myapp                # Entry-point script
├── t/
│   ├── 00-load.t            # Compilation tests
│   ├── unit/                # Unit tests
│   └── integration/         # Integration tests
├── cpanfile                 # Dependencies
├── Makefile.PL              # Build system
└── .perlcriticrc            # Linting config

Exporter Patterns

导出器模式

perl
package MyApp::Util;
use v5.36;
use Exporter 'import';

our @EXPORT_OK   = qw(trim);
our %EXPORT_TAGS = (all => \@EXPORT_OK);

sub trim($str) { $str =~ s/^\s+|\s+$//gr }

1;
perl
package MyApp::Util;
use v5.36;
use Exporter 'import';

our @EXPORT_OK   = qw(trim);
our %EXPORT_TAGS = (all => \@EXPORT_OK);

sub trim($str) { $str =~ s/^\s+|\s+$//gr }

1;

Tooling

工具链

perltidy Configuration (.perltidyrc)

perltidy配置(.perltidyrc)

text
-i=4        # 4-space indent
-l=100      # 100-char line length
-ci=4       # continuation indent
-ce         # cuddled else
-bar        # opening brace on same line
-nolq       # don't outdent long quoted strings
text
-i=4        # 4-space indent
-l=100      # 100-char line length
-ci=4       # continuation indent
-ce         # cuddled else
-bar        # opening brace on same line
-nolq       # don't outdent long quoted strings

perlcritic Configuration (.perlcriticrc)

perlcritic配置(.perlcriticrc)

ini
severity = 3
theme = core + pbp + security

[InputOutput::RequireCheckedSyscalls]
functions = :builtins
exclude_functions = say print

[Subroutines::ProhibitExplicitReturnUndef]
severity = 4

[ValuesAndExpressions::ProhibitMagicNumbers]
allowed_values = 0 1 2 -1
ini
severity = 3
theme = core + pbp + security

[InputOutput::RequireCheckedSyscalls]
functions = :builtins
exclude_functions = say print

[Subroutines::ProhibitExplicitReturnUndef]
severity = 4

[ValuesAndExpressions::ProhibitMagicNumbers]
allowed_values = 0 1 2 -1

Dependency Management (cpanfile + carton)

依赖管理(cpanfile + carton)

bash
cpanm App::cpanminus Carton   # Install tools
carton install                 # Install deps from cpanfile
carton exec -- perl bin/myapp  # Run with local deps
perl
undefined
bash
cpanm App::cpanminus Carton   # Install tools
carton install                 # Install deps from cpanfile
carton exec -- perl bin/myapp  # Run with local deps
perl
undefined

cpanfile

cpanfile

requires 'Moo', '>= 2.005'; requires 'Path::Tiny'; requires 'JSON::MaybeXS'; requires 'Try::Tiny';
on test => sub { requires 'Test2::V0'; requires 'Test::MockModule'; };
undefined
requires 'Moo', '>= 2.005'; requires 'Path::Tiny'; requires 'JSON::MaybeXS'; requires 'Try::Tiny';
on test => sub { requires 'Test2::V0'; requires 'Test::MockModule'; };
undefined

Quick Reference: Modern Perl Idioms

快速参考:现代Perl编程范式

Legacy PatternModern Replacement
use strict; use warnings;
use v5.36;
my ($x, $y) = @_;
sub foo($x, $y) { ... }
@{ $ref }
$ref->@*
%{ $ref }
$ref->%*
open FH, "< $file"
open my $fh, '<:encoding(UTF-8)', $file
blessed hashref
Moo
class with types
$1, $2, $3
$+{name}
(named captures)
eval { }; if ($@)
Try::Tiny
or native
try/catch
(5.40+)
BEGIN { require Exporter; }
use Exporter 'import';
Manual file ops
Path::Tiny
blessed($o) && $o->isa('X')
$o isa 'X'
(5.32+)
builtin::true / false
use builtin 'true', 'false';
(5.36+, experimental)
遗留模式现代替代方案
use strict; use warnings;
use v5.36;
my ($x, $y) = @_;
sub foo($x, $y) { ... }
@{ $ref }
$ref->@*
%{ $ref }
$ref->%*
open FH, "< $file"
open my $fh, '<:encoding(UTF-8)', $file
哈希引用 bless带类型检查的Moo类
$1, $2, $3
$+{name}
(命名捕获)
eval { }; if ($@)
Try::Tiny
或原生
try/catch
(5.40+)
BEGIN { require Exporter; }
use Exporter 'import';
手动文件操作
Path::Tiny
blessed($o) && $o->isa('X')
$o isa 'X'
(5.32+)
builtin::true / false
use builtin 'true', 'false';
(5.36+,实验性)

Anti-Patterns

反模式

perl
undefined
perl
undefined

1. Two-arg open (security risk)

1. Two-arg open (security risk)

open FH, $filename; # NEVER
open FH, $filename; # NEVER

2. Indirect object syntax (ambiguous parsing)

2. Indirect object syntax (ambiguous parsing)

my $obj = new Foo(bar => 1); # Bad my $obj = Foo->new(bar => 1); # Good
my $obj = new Foo(bar => 1); # Bad my $obj = Foo->new(bar => 1); # Good

3. Excessive reliance on $_

3. Excessive reliance on $_

map { process($) } grep { validate($) } @items; # Hard to follow my @valid = grep { validate($) } @items; # Better: break it up my @results = map { process($) } @valid;
map { process($) } grep { validate($) } @items; # Hard to follow my @valid = grep { validate($) } @items; # Better: break it up my @results = map { process($) } @valid;

4. Disabling strict refs

4. Disabling strict refs

no strict 'refs'; # Almost always wrong ${"My::Package::$var"} = $value; # Use a hash instead
no strict 'refs'; # Almost always wrong ${"My::Package::$var"} = $value; # Use a hash instead

5. Global variables as configuration

5. Global variables as configuration

our $TIMEOUT = 30; # Bad: mutable global use constant TIMEOUT => 30; # Better: constant
our $TIMEOUT = 30; # Bad: mutable global use constant TIMEOUT => 30; # Better: constant

Best: Moo attribute with default

Best: Moo attribute with default

6. String eval for module loading

6. String eval for module loading

eval "require $module"; # Bad: code injection risk eval "use $module"; # Bad use Module::Runtime 'require_module'; # Good: safe module loading require_module($module);

**Remember**: Modern Perl is clean, readable, and safe. Let `use v5.36` handle the boilerplate, use Moo for objects, and prefer CPAN's battle-tested modules over hand-rolled solutions.
eval "require $module"; # Bad: code injection risk eval "use $module"; # Bad use Module::Runtime 'require_module'; # Good: safe module loading require_module($module);

**注意**:现代Perl代码简洁、可读且安全。让`use v5.36`处理样板代码,使用Moo实现面向对象,优先选择CPAN中经过实战检验的模块而非自行实现。