Communitygithub.com

perl-patterns

现代 Perl 5.36+ 的惯用法、最佳实践和约定,用于构建健壮、可维护的 Perl 应用程序。在编写或审查现代 Perl 5.36+ 代码时使用。

perl-patterns 是什么?

这是一个专注于现代 Perl 5.36+ 编程惯用法和最佳实践的技能。它旨在帮助开发者编写健壮、可维护且符合现代标准的 Perl 应用程序。该技能详细介绍了核心原则,包括使用 `use v5.36` 来简化代码头部、使用子例程签名提高代码清晰度、利用上下文敏感性、后缀解引用以及 `isa` 运算符。错误处理方面涵盖了经典的 eval/die 模式、Try::Tiny 库以及 Perl 5.40 的原生 try/catch。面向对象编程部分推荐使用轻量级的 Moo 模块,并介绍了 Perl 5.38 中的原生 `class` 关键字。此外,还提供了正则表达式、数据结构、文件 I/O、模块组织和工具配置(如 perltidy 和 perlcritic)的最佳实践。该技能适用于编写新 Perl 代码、审查现有代码的合规性、重构遗留代码以及设计模块架构,尤其适合从旧版本 Perl 迁移到 5.36+ 的场景。它不依赖于特定平台,是纯 Perl 开发的最佳实践指南。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/perl-patterns

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

现代 Perl 开发模式

用于构建健壮、可维护应用程序的惯用 Perl 5.36+ 模式和最佳实践。

何时激活

  • 编写新的 Perl 代码或模块时
  • 审查 Perl 代码是否符合惯用法时
  • 将遗留 Perl 代码重构为现代标准时
  • 设计 Perl 模块架构时
  • 将 5.36 之前的代码迁移到现代 Perl 时

工作原理

将这些模式作为偏向现代 Perl 5.36+ 默认值的一种倾向来应用:签名、显式模块、聚焦的错误处理以及可测试的边界。下面的示例旨在作为起始点进行复制,然后根据您面前的实际应用程序、依赖栈和部署模型进行精简。

核心原则

1. 使用 use v5.36 编译指令

一个 use v5.36 语句取代了旧的样板代码,并启用了严格模式、警告和子例程签名。

# 好:现代代码头
use v5.36;

sub greet($name) {
    say "Hello, $name!";
}

# 差:遗留样板代码
use strict;
use warnings;
use feature 'say', 'signatures';
no warnings 'experimental::signatures';

sub greet {
    my ($name) = @_;
    say "Hello, $name!";
}

2. 子例程签名

使用签名以提高清晰度并实现自动参数数量检查。

use v5.36;

# 好:带默认值的签名
sub connect_db($host, $port = 5432, $timeout = 30) {
    # $host 是必需的,其他参数有默认值
    return DBI->connect("dbi:Pg:host=$host;port=$port", undef, undef, {
        RaiseError => 1,
        PrintError => 0,
    });
}

# 好:用于可变参数的吸收参数
sub log_message($level, @details) {
    say "[$level] " . join(' ', @details);
}

# 差:手动参数解包
sub connect_db {
    my ($host, $port, $timeout) = @_;
    $port    //= 5432;
    $timeout //= 30;
    # ...
}

3. 上下文敏感性

理解标量上下文与列表上下文——这是 Perl 的核心概念。

use v5.36;

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

my @copy  = @items;            # 列表上下文:所有元素
my $count = @items;            # 标量上下文:计数(5)
say "Items: " . scalar @items; # 强制标量上下文

4. 后缀解引用

在处理嵌套结构时,使用后缀解引用语法以提高可读性。

use v5.36;

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

# 好:后缀解引用
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} };

5. isa 运算符(5.32+)

中缀类型检查——替代了 blessed($o) && $o->isa('X')。

use v5.36;
if ($obj isa 'My::Class') { $obj->do_something }

错误处理

eval/die 模式

use v5.36;

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

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

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;
}

原生 try/catch(5.40+)

use v5.40;

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

使用 Moo 实现现代面向对象

优先选择 Moo 来实现轻量级的现代面向对象。仅当需要 Moose 的元协议时才使用它。

# 好: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;

# 使用
my $user = User->new(
    name  => 'Alice',
    email => '[email protected]',
    roles => ['admin', 'user'],
);

# 差:被祝福的哈希引用(无验证,无访问器)
package User;
sub new {
    my ($class, %args) = @_;
    return bless \%args, $class;
}
sub name { return $_[0]->{name} }
1;

Moo 角色

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;

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

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
}

正则表达式

命名捕获和 /x 标志

use v5.36;

# 好:使用 /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}";
}

# 差:位置捕获(难以维护)
if ($line =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+\[(\w+)\]\s+(.+)$/) {
    say "Time: $1, Level: $2";
}

预编译模式

use v5.36;

# 好:编译一次,多次使用
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;
}

数据结构

引用与安全深层访问

use v5.36;

# 哈希和数组引用
my $config = {
    database => {
        host => 'localhost',
        port => 5432,
        options => ['utf8', 'sslmode=require'],
    },
};

# 安全的深层访问(如果任何层级缺失,则返回 undef)
my $port = $config->{database}{port};           # 5432
my $missing = $config->{cache}{host};           # undef, 无错误

# 哈希切片
my %subset;
@subset{qw(host port)} = @{$config->{database}}{qw(host port)};

# 数组切片
my @first_two = $config->{database}{options}->@[0, 1];

# 多变量 for 循环(5.36 中为实验性,5.40 中稳定)
use feature 'for_list';
no warnings 'experimental::for_list';
for my ($key, $val) (%$config) {
    say "$key => $val";
}

文件 I/O

三参数 open

use v5.36;

# 好:使用 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;
}

# 差:两参数 open(shell 注入风险,参见 perl-security)
open FH, $path;            # 永远不要这样做
open FH, "< $path";        # 仍然不好——模式字符串中包含用户数据

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

use v5.36;
use Path::Tiny;

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

# 遍历目录
for my $child (path('src')->children(qr/\.pl$/)) {
    say $child->basename;
}

模块组织

标准项目布局

MyApp/
├── lib/
│   └── MyApp/
│       ├── App.pm           # 主模块
│       ├── Config.pm        # 配置
│       ├── DB.pm            # 数据库层
│       └── Util.pm          # 工具
├── bin/
│   └── myapp                # 入口脚本
├── t/
│   ├── 00-load.t            # 编译测试
│   ├── unit/                # 单元测试
│   └── integration/         # 集成测试
├── cpanfile                 # 依赖
├── Makefile.PL              # 构建系统
└── .perlcriticrc            # Linting 配置

Exporter 模式

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;

工具

perltidy 配置 (.perltidyrc)

-i=4        # 4 空格缩进
-l=100      # 100 字符行长度
-ci=4       # 连续行缩进
-ce         # cuddled else
-bar        # 左大括号放在同一行
-nolq       # 不缩进长带引号的字符串

perlcritic 配置 (.perlcriticrc)

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

依赖管理 (cpanfile + carton)

cpanm App::cpanminus Carton   # 安装工具
carton install                 # 从 cpanfile 安装依赖
carton exec -- perl bin/myapp  # 使用本地依赖运行
# cpanfile
requires 'Moo', '>= 2.005';
requires 'Path::Tiny';
requires 'JSON::MaybeXS';
requires 'Try::Tiny';

on test => sub {
    requires 'Test2::V0';
    requires 'Test::MockModule';
};

快速参考:现代 Perl 惯用法

遗留模式现代替代方案
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 类
$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 / falseuse builtin 'true', 'false'; (5.36+, 实验性)

反模式

# 1. 两参数 open(安全风险)
open FH, $filename;                     # 永远不要

# 2. 间接对象语法(解析歧义)
my $obj = new Foo(bar => 1);            # 差
my $obj = Foo->new(bar => 1);           # 好

# 3. 过度依赖 $_
map { process($_) } grep { validate($_) } @items;  # 难以理解
my @valid = grep { validate($_) } @items;           # 更好:拆分它
my @results = map { process($_) } @valid;

# 4. 禁用严格引用
no strict 'refs';                        # 几乎总是错的
${"My::Package::$var"} = $value;         # 改用哈希

# 5. 使用全局变量作为配置
our $TIMEOUT = 30;                       # 差:可变的全局变量
use constant TIMEOUT => 30;              # 更好:常量
# 最好:带默认值的 Moo 属性

# 6. 使用字符串 eval 加载模块
eval "require $module";                  # 差:代码注入风险
eval "use $module";                      # 差
use Module::Runtime 'require_module';    # 好:安全的模块加载
require_module($module);

记住:现代 Perl 干净、可读且安全。让 use v5.36 处理样板代码,使用 Moo 处理对象,并优先选择 CPAN 上久经考验的模块,而不是自己从头开始写。

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

相关技能