Communitygithub.com

perl-patterns

現代 Perl 5.36+ 的慣用寫法、最佳實踐與慣例,用於建構穩固且易於維護的 Perl 應用程式。在撰寫或審查現代 Perl 5.36+ 程式碼時使用。

perl-patterns 是什麼?

此技能提供了一套全面的現代 Perl 5.36+ 開發模式與最佳實踐指南。它涵蓋了核心語言功能,如 use v5.36 語法、子常式簽名、後綴解參照以及 isa 運算子。在物件導向程式設計方面,它推薦使用 Moo 作為輕量級選項,並介紹了原生的 class 關鍵字(Corinna)。錯誤處理部分展示了 eval/die、Try::Tiny 以及原生的 try/catch(5.40+)。此外,它還包含了正規表示式、資料結構、檔案 I/O(包括 Path::Tiny)、模組組織和工具配置(perltidy、perlcritic)的指南。此技能的目標是幫助開發者從舊有寫法遷移到更安全、更易讀且更易維護的現代 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,
    });
}

# 良好的做法:用於可變參數的 Slurpy 參數
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 "配置錯誤: $@" 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 "找不到使用者 $id\n";
    }
    catch {
        warn "擷取使用者 $id 失敗: $_";
        undef;
    };
    return $user;
}

原生的 try/catch (5.40+)

use v5.40;

sub divide($x, $y) {
    try {
        die "除以零" if $y == 0;
        return $x / $y;
    }
    catch ($e) {
        warn "錯誤: $e";
        return;
    }
}

使用 Moo 的現代 OO

偏好使用 Moo 來實現輕量級、現代的物件導向程式設計。僅在其元協定(metaprotocol)被需要時才使用 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 "你好,我是 " . $self->name;
}

1;

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

# 不好的做法:受祝福的 hashref(無驗證,無存取子)
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 "時間: $+{timestamp}, 等級: $+{level}";
    say "訊息: $+{message}";
}

# 不好的做法:位置捕獲(難以維護)
if ($line =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+\[(\w+)\]\s+(.+)$/) {
    say "時間: $1, 等級: $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. 停用 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/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.

相關技能