npx claudepluginhub sam42-lab/everything-claude-code-krThis skill uses the workspace's default tool permissions.
견고하고 유지보수가 쉬운 애플리케이션을 구축하기 위한 관용적인 Perl 5.36+ 패턴 및 모범 사례입니다.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
견고하고 유지보수가 쉬운 애플리케이션을 구축하기 위한 관용적인 Perl 5.36+ 패턴 및 모범 사례입니다.
signatures, 명시적 모듈, 집중된 오류 처리 및 테스트 가능한 경계와 같은 Modern Perl 5.36+ 기본값을 지향하는 방향으로 이 패턴들을 적용합니다. 아래 예제들은 시작점으로 복사하여 사용할 수 있으며, 실제 애플리케이션, 의존성 스택 및 배포 모델에 맞게 조정하십시오.
v5.36 Pragma 사용단일 use v5.36 문구가 오래된 상형문자 같은 설정들을 대체하며 strict, warnings 및 서브루틴 signatures를 활성화합니다.
# 좋음: 현대적인 서문
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!";
}
명확성과 자동 인수 개수 확인을 위해 signatures를 사용합니다.
use v5.36;
# 좋음: 기본값이 있는 Signatures
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);
}
# 나쁨: 수동 인구 해제(unpacking)
sub connect_db {
my ($host, $port, $timeout) = @_;
$port //= 5432;
$timeout //= 30;
# ...
}
Perl의 핵심 개념인 scalar vs list 컨텍스트를 이해하십시오.
use v5.36;
my @items = (1, 2, 3, 4, 5);
my @copy = @items; # List 컨텍스트: 모든 요소
my $count = @items; # Scalar 컨텍스트: 개수 (5)
say "Items: " . scalar @items; # Scalar 컨텍스트 강제
중첩된 구조에서 가독성을 위해 postfix dereference 구문을 사용합니다.
use v5.36;
my $data = {
users => [
{ name => 'Alice', roles => ['admin', 'user'] },
{ name => 'Bob', roles => ['user'] },
],
};
# 좋음: Postfix dereferencing
my @users = $data->{users}->@*;
my @roles = $data->{users}[0]{roles}->@*;
my %first = $data->{users}[0]->%*;
# 나쁨: Circumfix dereferencing (체이닝 시 읽기 어려움)
my @users = @{ $data->{users} };
my @roles = @{ $data->{users}[0]{roles} };
isa 연산자 (5.32+)중치(Infix) 타입 확인 — blessed($o) && $o->isa('X')를 대체합니다.
use v5.36;
if ($obj isa 'My::Class') { $obj->do_something }
use v5.36;
sub parse_config($path) {
my $content = eval { path($path)->slurp_utf8 };
die "Config error: $@" if $@;
return decode_json($content);
}
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;
}
use v5.40;
sub divide($x, $y) {
try {
die "Division by zero" if $y == 0;
return $x / $y;
}
catch ($e) {
warn "Error: $e";
return;
}
}
가볍고 현대적인 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 => 'alice@example.com',
roles => ['admin', 'user'],
);
# 나쁨: Blessed hashref (검증 없음, 접근자 없음)
package User;
sub new {
my ($class, %args) = @_;
return bless \%args, $class;
}
sub name { return $_[0]->{name} }
1;
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;
# Hash 및 array 레퍼런스
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 슬라이스
my %subset;
@subset{qw(host port)} = @{$config->{database}}{qw(host port)};
# Array 슬라이스
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";
}
use v5.36;
# 좋음: 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;
}
# 나쁨: 2개 인수 open (셸 인젝션 위험, perl-security 참조)
open FH, $path; # 절대 하지 마십시오
open FH, "< $path"; # 여전히 나쁨 — 모드 문자열에 사용자 데이터 포함
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;
-i=4 # 4칸 들여쓰기
-l=100 # 100자 라인 길이
-ci=4 # 연속 들여쓰기
-ce # else를 닫는 중괄호와 같은 줄에 (cuddled else)
-bar # 여는 중괄호를 같은 줄에
-nolq # 긴 인용 문자열을 내어쓰지 않음
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
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';
};
| 레거시 패턴 | 현대적 대체 |
|---|---|
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 / false | use builtin 'true', 'false'; (5.36+, 실험적) |
# 1. 2개 인수 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);
기억하십시오: Modern Perl은 깔끔하고 가독성이 좋으며 안전합니다. use v5.36이 상형문자 같은 설정들을 처리하게 하고, 객체를 위해 Moo를 사용하며, 직접 만든 솔루션보다는 CPAN의 검증된 모듈을 선호하십시오.