Perl Patterns
堅牢でメンテナブルなPerlアプリケーションを構築するためのModern Perl 5.36+のイディオム、ベストプラクティス、規約。
- Skill ID
- affaan-m/ecc/perl-patterns
- Publisher
- affaan-m
- Repository
- ecc
- Installs
- 0
- Files
- 1
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
affaan-m/ecc/perl-patternsInstalls these files- SKILL.md
What this skill tells the agent
モダンPerl開発パターン
堅牢でメンテナブルなアプリケーションを構築するためのイディオマティックなPerl 5.36+パターンとベストプラクティス。
アクティベートするタイミング
- 新しいPerlコードまたはモジュールを書くとき
- イディオム準拠のためにPerlコードをレビューするとき
- レガシーPerlをモダンな標準にリファクタリングするとき
- PerlモジュールのアーキテクチャをDesignするとき
- 5.36以前のコードをモダンなPerlに移行するとき
仕組み
これらのパターンをModern 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. コンテキスト感度
スカラーvsリストコンテキストを理解する — 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;
# 使用法
my $user = User->new(
name => 'Alice',
email => 'alice@example.com',
roles => ['admin', 'user'],
);
# Bad: ブレスされた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: 1回コンパイル、複数回使用
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 # リンティング設定エクスポーターパターン
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 {More from affaan-m
Use this skill in a project
Open any RiverX project, search the Skills panel for affaan-m/ecc/perl-patterns, and install it in one click.
