perl-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseModern 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
v5.361. 使用v5.36
编译指示
v5.36A single replaces the old boilerplate and enables strict, warnings, and subroutine signatures.
use v5.36perl
undefined一行即可替代旧的样板代码,同时启用strict、warnings和子例程签名功能。
use v5.36perl
undefinedGood: 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!";
}
undefineduse strict;
use warnings;
use feature 'say', 'signatures';
no warnings 'experimental::signatures';
sub greet {
my ($name) = @_;
say "Hello, $name!";
}
undefined2. 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;
# ...
}
undefinedsub connect_db {
my ($host, $port, $timeout) = @_;
$port //= 5432;
$timeout //= 30;
# ...
}
undefined3. 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 context4. 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} };
undefinedmy @users = @{ $data->{users} };
my @roles = @{ $data->{users}[0]{roles} };
undefined5. The isa
Operator (5.32+)
isa5. isa
运算符(5.32+)
isaInfix 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
undefinedGood: 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;
undefinedpackage User;
sub new {
my ($class, %args) = @;
return bless %args, $class;
}
sub name { return $[0]->{name} }
1;
undefinedMoo 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原生class
关键字(5.38+,Corinna)
classperl
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; # 5perl
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; # 5Regular Expressions
正则表达式
Named Captures and /x
Flag
/x命名捕获与/x
标志
/xperl
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";
}
undefinedif ($line =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+[(\w+)]\s+(.+)$/) {
say "Time: $1, Level: $2";
}
undefinedPrecompiled 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;
}
undefinedmy $email_re = qr/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}$/;
sub validate_emails(@emails) {
return grep { $_ =~ $email_re } @emails;
}
undefinedData 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";
}
undefineduse feature 'for_list';
no warnings 'experimental::for_list';
for my ($key, $val) (%$config) {
say "$key => $val";
}
undefinedFile 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
undefinedopen FH, $path; # NEVER do this
open FH, "< $path"; # Still bad — user data in mode string
undefinedPath::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;
}
undefinedfor my $child (path('src')->children(qr/.pl$/)) {
say $child->basename;
}
undefinedModule 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 configtext
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 configExporter 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 stringstext
-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 stringsperlcritic 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 -1ini
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 -1Dependency 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 depsperl
undefinedbash
cpanm App::cpanminus Carton # Install tools
carton install # Install deps from cpanfile
carton exec -- perl bin/myapp # Run with local depsperl
undefinedcpanfile
cpanfile
requires 'Moo', '>= 2.005';
requires 'Path::Tiny';
requires 'JSON::MaybeXS';
requires 'Try::Tiny';
on test => sub {
requires 'Test2::V0';
requires 'Test::MockModule';
};
undefinedrequires 'Moo', '>= 2.005';
requires 'Path::Tiny';
requires 'JSON::MaybeXS';
requires 'Try::Tiny';
on test => sub {
requires 'Test2::V0';
requires 'Test::MockModule';
};
undefinedQuick Reference: Modern Perl Idioms
快速参考:现代Perl编程范式
| Legacy Pattern | Modern Replacement |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| Manual file ops | |
| |
| |
| 遗留模式 | 现代替代方案 |
|---|---|
| |
| |
| |
| |
| |
| 哈希引用 bless | 带类型检查的Moo类 |
| |
| |
| |
| 手动文件操作 | |
| |
| |
Anti-Patterns
反模式
perl
undefinedperl
undefined1. 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中经过实战检验的模块而非自行实现。