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
推荐:现代前置代码
use v5.36;
sub greet($name) {
say "Hello, $name!";
}
use v5.36;
sub greet($name) {
say "Hello, $name!";
}
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
推荐:带默认值的签名
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为必填参数,其余参数有默认值
return DBI->connect("dbi:Pg:host=$host;port=$port", undef, undef, {
RaiseError => 1,
PrintError => 0,
});
}
Good: Slurpy parameter for variable args
推荐:用于可变参数的Slurpy参数
sub log_message($level, @details) {
say "[$level] " . join(' ', @details);
}
sub log_message($level, @details) {
say "[$level] " . join(' ', @details);
}
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; # 列表上下文:获取所有元素
my $count = @items; # 标量上下文:获取元素数量(5)
say "Items: " . scalar @items; # 强制使用标量上下文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
推荐:后缀解引用
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)
不推荐:前缀解引用(链式结构中可读性差)
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 "配置错误:$@" 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 "未找到用户$id\n";
}
catch {
warn "获取用户$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 "除以零错误" if $y == 0;
return $x / $y;
}
catch ($e) {
warn "错误:$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
推荐:Moo类
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
使用示例
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)
不推荐:Blessed哈希引用(无验证、无访问器)
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
推荐:使用命名捕获和/x标志提升可读性
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 "时间:$+{timestamp}, 级别:$+{level}";
say "消息:$+{message}";
}
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 "时间:$1, 级别:$2";
}
undefinedPrecompiled Patterns
预编译模式
perl
use v5.36;perl
use v5.36;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
哈希和数组引用
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)
安全深度访问(若某层级不存在则返回undef)
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,无错误
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
数组切片
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)
多变量for循环(5.36中为实验特性,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')
推荐:带autodie的三参数open(核心模块,无需手动写'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)
不推荐:双参数open(存在shell注入风险,参见perl-security)
open FH, $path; # NEVER do this
open FH, "< $path"; # Still bad — user data in mode string
undefinedopen FH, $path; # 绝对不要这样做
open FH, "< $path"; # 仍然不安全——模式字符串中包含用户数据
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
遍历目录
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 # 主模块
│ ├── Config.pm # 配置模块
│ ├── DB.pm # 数据库层
│ └── Util.pm # 工具模块
├── bin/
│ └── myapp # 入口脚本
├── t/
│ ├── 00-load.t # 编译测试
│ ├── unit/ # 单元测试
│ └── integration/ # 集成测试
├── cpanfile # 依赖声明文件
├── Makefile.PL # 构建系统
└── .perlcriticrc # 代码检查配置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 stringstext
-i=4 # 4空格缩进
-l=100 # 行宽100字符
-ci=4 # 续行缩进4空格
-ce # else与if的右大括号同行
-bar # 左大括号与语句同行
-nolq # 长字符串不缩进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 -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 # 安装工具
carton install # 从cpanfile安装依赖
carton exec -- perl bin/myapp # 使用本地依赖运行perl
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 | |
| |
| |
| 遗留模式 | 现代替代方案 |
|---|---|
| |
| |
| |
| |
| |
| Blessed哈希引用 | 带类型检查的Moo类 |
| |
| |
| |
| 手动文件操作 | |
| |
| |
Anti-Patterns
反模式
perl
undefinedperl
undefined1. Two-arg open (security risk)
1. 双参数open(安全风险)
open FH, $filename; # NEVER
open FH, $filename; # 绝对不要这样做
2. Indirect object syntax (ambiguous parsing)
2. 间接对象语法(解析歧义)
my $obj = new Foo(bar => 1); # Bad
my $obj = Foo->new(bar => 1); # Good
my $obj = new Foo(bar => 1); # 不推荐
my $obj = Foo->new(bar => 1); # 推荐
3. Excessive reliance on $_
3. 过度依赖$_变量
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; # 可读性差
my @valid = grep { validate($) } @items; # 更优:拆分步骤
my @results = map { process($) } @valid;
4. Disabling strict refs
4. 禁用strict refs
no strict 'refs'; # Almost always wrong
${"My::Package::$var"} = $value; # Use a hash instead
no strict 'refs'; # 几乎全是错误用法
${"My::Package::$var"} = $value; # 改用哈希存储
5. Global variables as configuration
5. 使用全局变量存储配置
our $TIMEOUT = 30; # Bad: mutable global
use constant TIMEOUT => 30; # Better: constant
our $TIMEOUT = 30; # 不推荐:可变全局变量
use constant TIMEOUT => 30; # 更优:常量
Best: Moo attribute with default
最佳方案:Moo属性+默认值
6. String eval for module loading
6. 使用字符串eval加载模块
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"; # 不推荐:代码注入风险
eval "use $module"; # 不推荐
use Module::Runtime 'require_module'; # 推荐:安全的模块加载方式
require_module($module);
**注意**:现代Perl的特点是简洁、可读、安全。让`use v5.36`处理模板代码,使用Moo构建对象,优先选择CPAN中经过实战检验的模块而非自行实现。