Communitygithub.com

perl-patterns

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

perl-patterns 是什么?

该技能提供了基于 Perl 5.36+ 的现代开发模式与最佳实践指南。它涵盖核心原则,如使用 `use v5.36` 替换旧样板代码、子程序签名、上下文敏感性、后缀解引用和 `isa` 运算符。技能详细说明了错误处理模式,包括传统的 eval/die、Try::Tiny 以及 5.40+ 原生的 try/catch。面向对象编程部分重点介绍现代方式:优先使用 Moo 进行轻量级 OO,提供 Moo 角色示例,并展示 5.38+ 原生的 `class` 关键字(Corinna)。正则表达式部分强调命名捕获和预编译模式以提高可读性和性能。技能还包括三个参数 open、Path::Tiny 进行文件 I/O、标准模块布局、Exporter 模式以及 perltidy 和 perlcritic 等重要工具的配置。最后,它有现代习惯用法的快速参考表以及应避免的反模式列表。该技能适用于任何支持此模式的 AI 代理工具,如 Claude Code、Cursor 等。

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

在你喜欢的 AI 中提问

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

文档

现代 Perl 开发模式

符合惯例的 Perl 5.36+ 模式与最佳实践,用于构建健壮且可维护的应用程序。

何时激活

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

工作原理

将这些模式作为现代 Perl 5.36+ 默认值(签名、显式模块、专注的错误处理、可测试的边界)的偏置来应用。下面的示例旨在作为起点复制,然后根据您面前的实际应用程序、依赖栈和部署模型进行收紧。

核心原则

1. 使用 v5.36 编译指令

单条 use v5.36 替换了旧的样板代码,并启用了 strict、warnings 和子程序签名。

# 好:现代序言
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 实现现代 OO

优先使用 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            # 代码检查配置

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         # 紧凑的 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. 禁用 strict refs
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/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

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.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

相关技能