Communitygithub.com

perl-patterns

最新のPerl 5.36+イディオム、ベストプラクティス、および堅牢で保守しやすいPerlアプリケーションを構築するための規則。最新のPerl 5.36+コードを書いたりレビューしたりする際に使用します。

perl-patterns とは?

このスキルは、Perl 5.36以降のモダンなイディオムとベストプラクティスを提供します。`use v5.36`プラグマによるボイラープレートの削減、サブルーチンシグネチャ、後置デリファレンス、`isa`演算子などの言語機能を活用し、より読みやすく安全なコードを書くための指針を示します。また、Mooを使ったモダンなオブジェクト指向プログラミング、Try::Tinyやネイティブtry/catchによるエラー処理、Path::Tinyを用いたファイルI/O、名前付きキャプチャとプリコンパイルパターンを用いた正規表現の改善など、実践的なパターンを多数カバーしています。さらに、perltidyやperlcriticなどのツール設定、cpanfileによる依存関係管理、プロジェクトレイアウトの標準など、開発環境全体をモダナイズするための情報も含まれています。このスキルは、Perlのコードレビュー、新規コード作成、レガシーコードのリファクタリング、Perlモジュール設計、および5.36以前のコードからの移行において有効です。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/perl-patterns

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

モダンPerl開発パターン

イディオマティックなPerl 5.36+のパターンとベストプラクティス。堅牢で保守しやすいアプリケーションを構築するためのものです。

アクティブにするタイミング

  • 新しいPerlコードやモジュールを書くとき
  • Perlコードのイディオム準拠をレビューするとき
  • レガシーPerlをモダンな標準にリファクタリングするとき
  • Perlモジュールのアーキテクチャを設計するとき
  • 5.36以前のコードをモダンPerlに移行するとき

仕組み

これらのパターンを、モダンPerl 5.36+のデフォルト(シグネチャ、明示的なモジュール、集中したエラー処理、テスト可能な境界)へのバイアスとして適用します。以下の例はコピーして開始点とし、実際のアプリ、依存関係スタック、デプロイメントモデルに合わせて調整することを意図しています。

コア原則

1. v5.36プラグマを使用する

単一の use v5.36 で、strict、warnings、サブルーチンシグネチャが有効になり、古いボイラープレートを置き換えます。

# Good: モダンなプリアンブル
use v5.36;

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

# Bad: レガシーなボイラープレート
use strict;
use warnings;
use feature 'say', 'signatures';
no warnings 'experimental::signatures';

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

2. サブルーチンシグネチャ

シグネチャを使用して明確にし、自動引数チェックを行います。

use v5.36;

# Good: デフォルト値付きシグネチャ
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: 可変個引数のためのスラーピーパラメータ
sub log_message($level, @details) {
    say "[$level] " . join(' ', @details);
}

# Bad: 手動での引数アンパック
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'] },
    ],
};

# Good: 後置デリファレンス
my @users = $data->{users}->@*;
my @roles = $data->{users}[0]{roles}->@*;
my %first = $data->{users}[0]->%*;

# Bad: サーカムフィックスデリファレンス(チェーンで読むのが難しい)
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

軽量でモダンなOOにはMooを推奨します。メタプロトコルが必要な場合にのみMooseを使用してください。

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

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

# Bad:  blessed 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;

# Good: 可読性のための/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}";
}

# Bad: 位置キャプチャ(保守が難しい)
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;

# Good: 一度コンパイルして何度も使用
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

3引数open

use v5.36;

# Good: autodieを使用した3引数open(コアモジュール、'or die'を排除)
use autodie;

sub read_file($path) {
    open my $fh, '<:encoding(UTF-8)', $path;
    local $/;
    my $content = <$fh>;
    close $fh;
    return $content;
}

# Bad: 2引数open(シェルインジェクションリスク、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         # 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. 2引数open(セキュリティリスク)
open FH, $filename;                     # 絶対にしないでください

# 2. 間接オブジェクト構文(曖昧な解析)
my $obj = new Foo(bar => 1);            # Bad
my $obj = Foo->new(bar => 1);           # Good

# 3. $_への過度な依存
map { process($_) } grep { validate($_) } @items;  # 追跡が難しい
my @valid = grep { validate($_) } @items;           # Better: 分割する
my @results = map { process($_) } @valid;

# 4. strict refsの無効化
no strict 'refs';                        # ほとんどの場合間違い
${"My::Package::$var"} = $value;         # 代わりにハッシュを使用

# 5. 設定としてのグローバル変数
our $TIMEOUT = 30;                       # Bad: 可変グローバル
use constant TIMEOUT => 30;              # Better: 定数
# Best: デフォルト値を持つMoo属性

# 6. モジュールロードのための文字列eval
eval "require $module";                  # Bad: コードインジェクションリスク
eval "use $module";                      # Bad
use Module::Runtime 'require_module';    # Good: 安全なモジュールロード
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.

関連スキル