Communitygithub.com

perl-patterns

現代 Perl 5.36+ 的慣用寫法、最佳實踐與慣例,協助你打造穩固、易於維護的 Perl 應用程式。適合在編寫或審閱現代 Perl 5.36+ 程式碼時使用。

perl-patterns 是什麼?

這個技能提供了一套全面的現代 Perl 開發模式,專為 Perl 5.36 及以上版本設計。它涵蓋了從基礎的 `use v5.36` 編譯指令、子程式簽章(signatures)、後綴解參照(postfix dereferencing)到 `isa` 運算子等核心原則。此外,它還深入介紹了錯誤處理的不同策略(包括傳統的 `eval/die`、`Try::Tiny` 以及 Perl 5.40 的原生 `try/catch`),以及使用 `Moo` 進行現代物件導向程式設計的方法,包含角色(roles)和原生的 `class` 關鍵字(5.38+,Corinna)。技能中也提供了正規表示式、檔案 I/O(包含 `Path::Tiny`)、專案模組組織、工具鏈配置(如 `perltidy` 和 `perlcritic`)以及常見反模式的實用建議。此技能適用於任何支援技能檔案的 AI 代理程式平台(例如 Claude Code),開發者在編寫、審閱或重構 Perl 5.36+ 程式碼時,可以將這些模式作為起點,再依據實際的應用程式、依賴堆疊和部署模型進行調整。

相容平台~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+ 的預設值:簽章 (signatures)、明確的模組、聚焦的錯誤處理以及可測試的邊界。下方的範例是作為起點複製使用,再根據你眼前的實際應用程式、依賴堆疊與部署模型進行調整。

核心原則

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. 子程式簽章 (Signatures)

使用簽章來增加清晰度並啟用自動參數個數檢查。

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 parameter) 處理可變參數
sub log_message($level, @details) {
    say "[$level] " . join(' ', @details);
}

# 不佳:手動解包參數
sub connect_db {
    my ($host, $port, $timeout) = @_;
    $port    //= 5432;
    $timeout //= 30;
    # ...
}

3. 情境敏感度 (Context Sensitivity)

理解標量 (scalar) 與列表 (list) 情境 — 這是 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)

在處理巢狀結構時,使用後綴解參照語法來提高可讀性。

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 來實現輕量、現代的物件導向。只有在需要其元協定 (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 "Hello, I'm " . $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 角色 (Roles)

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

資料結構

參照 (References) 與安全深層存取

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,無錯誤

# 雜湊切片 (Hash slices)
my %subset;
@subset{qw(host port)} = @{$config->{database}}{qw(host port)};

# 陣列切片 (Array slices)
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 hashrefMoo 類別搭配型別
$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';                        # 幾乎永遠是錯的
${

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.

相關技能