Padrões Modernos de Desenvolvimento Perl
Padrões idiomáticos de Perl 5.36+ e boas práticas para construir aplicações robustas e sustentáveis.
Quando Ativar
- Escrevendo novo código ou módulos Perl
- Revisando código Perl para conformidade com expressões idiomáticas
- Refatorando código Perl legado para padrões modernos
- Projetando arquitetura de módulos Perl
- Migrando código pré-5.36 para Perl moderno
Como Funciona
Aplique esses padrões como uma tendência para os padrões modernos do Perl 5.36+: assinaturas, módulos explícitos, tratamento de erros focado e limites testáveis. Os exemplos abaixo devem ser copiados como pontos de partida e, em seguida, ajustados para o aplicativo, a pilha de dependências e o modelo de implantação reais à sua frente.
Princípios Centrais
1. Use o Pragma v5.36
Um único use v5.36 substitui a burocracia antiga e ativa strict, warnings e assinaturas de sub-rotinas.
# Bom: Preâmbulo moderno
use v5.36;
sub cumprimentar($nome) {
say "Olá, $nome!";
}
# Ruim: Burocracia legada
use strict;
use warnings;
use feature 'say', 'signatures';
no warnings 'experimental::signatures';
sub cumprimentar {
my ($nome) = @_;
say "Olá, $nome!";
}
2. Assinaturas de Sub-rotinas
Use assinaturas para clareza e verificação automática de aridade.
use v5.36;
# Bom: Assinaturas com valores padrão
sub conectar_bd($host, $porta = 5432, $timeout = 30) {
# $host é obrigatório, outros têm padrões
return DBI->connect("dbi:Pg:host=$host;port=$porta", undef, undef, {
RaiseError => 1,
PrintError => 0,
});
}
# Bom: Parâmetro slurpy para argumentos variáveis
sub log_mensagem($nivel, @detalhes) {
say "[$nivel] " . join(' ', @detalhes);
}
# Ruim: Desempacotamento manual de argumentos
sub conectar_bd {
my ($host, $porta, $timeout) = @_;
$porta //= 5432;
$timeout //= 30;
# ...
}
3. Sensibilidade a Contexto
Entenda o contexto escalar vs. lista — um conceito central do Perl.
use v5.36;
my @itens = (1, 2, 3, 4, 5);
my @copia = @itens; # Contexto de lista: todos os elementos
my $contagem = @itens; # Contexto escalar: contagem (5)
say "Itens: " . scalar @itens; # Forçar contexto escalar
4. Desreferenciação Pós-fixa
Use a sintaxe de desreferenciação pós-fixa para legibilidade com estruturas aninhadas.
use v5.36;
my $dados = {
usuarios => [
{ nome => 'Alice', papeis => ['admin', 'usuario'] },
{ nome => 'Bob', papeis => ['usuario'] },
],
};
# Bom: Desreferenciação pós-fixa
my @usuarios = $dados->{usuarios}->@*;
my @papeis = $dados->{usuarios}[0]{papeis}->@*;
my %primeiro = $dados->{usuarios}[0]->%*;
# Ruim: Desreferenciação circunflexa (mais difícil de ler em cadeias)
my @usuarios = @{ $dados->{usuarios} };
my @papeis = @{ $dados->{usuarios}[0]{papeis} };
5. O Operador isa (5.32+)
Verificação de tipo infixa — substitui blessed($o) && $o->isa('X').
use v5.36;
if ($obj isa 'My::Class') { $obj->fazer_algo }
Tratamento de Erros
Padrão eval/die
use v5.36;
sub parsear_config($caminho) {
my $conteudo = eval { path($caminho)->slurp_utf8 };
die "Erro no config: $@" if $@;
return decode_json($conteudo);
}
Try::Tiny (Tratamento de Exceções Confiável)
use v5.36;
use Try::Tiny;
sub buscar_usuario($id) {
my $usuario = try {
$db->resultset('User')->find($id)
// die "Usuário $id não encontrado\n";
}
catch {
warn "Falha ao buscar usuário $id: $_";
undef;
};
return $usuario;
}
try/catch Nativo (5.40+)
use v5.40;
sub dividir($x, $y) {
try {
die "Divisão por zero" if $y == 0;
return $x / $y;
}
catch ($e) {
warn "Erro: $e";
return;
}
}
OO Moderno com Moo
Prefira Moo para OO moderno e leve. Use Moose apenas quando seu metaprotocolo for necessário.
# Bom: Classe Moo
package Usuario;
use Moo;
use Types::Standard qw(Str Int ArrayRef);
use namespace::autoclean;
has nome => (is => 'ro', isa => Str, required => 1);
has email => (is => 'ro', isa => Str, required => 1);
has idade => (is => 'ro', isa => Int, default => sub { 0 });
has papeis => (is => 'ro', isa => ArrayRef[Str], default => sub { [] });
sub eh_admin($self) {
return grep { $_ eq 'admin' } $self->papeis->@*;
}
sub cumprimentar($self) {
return "Olá, eu sou " . $self->nome;
}
1;
# Uso
my $usuario = Usuario->new(
nome => 'Alice',
email => '[email protected]',
papeis => ['admin', 'usuario'],
);
# Ruim: Blessed hashref (sem validação, sem accessors)
package Usuario;
sub new {
my ($class, %args) = @_;
return bless \%args, $class;
}
sub nome { return $_[0]->{nome} }
1;
Roles Moo
package Role::Serializavel;
use Moo::Role;
use JSON::MaybeXS qw(encode_json);
requires 'PARA_HASH';
sub para_json($self) { encode_json($self->PARA_HASH) }
1;
package Usuario;
use Moo;
with 'Role::Serializavel';
has nome => (is => 'ro', required => 1);
has email => (is => 'ro', required => 1);
sub PARA_HASH($self) { { nome => $self->nome, email => $self->email } }
1;
Palavra-chave class Nativa (5.38+, Corinna)
use v5.38;
use feature 'class';
no warnings 'experimental::class';
class Ponto {
field $x :param;
field $y :param;
method magnitude() { sqrt($x**2 + $y**2) }
}
my $p = Ponto->new(x => 3, y => 4);
say $p->magnitude; # 5
Expressões Regulares
Capturas Nomeadas e Sinalizador /x
use v5.36;
# Bom: Capturas nomeadas com /x para legibilidade
my $log_re = qr{
^ (?<timestamp> \d{4}-\d{2}-\d{2} \s \d{2}:\d{2}:\d{2} )
\s+ \[ (?<nivel> \w+ ) \]
\s+ (?<mensagem> .+ ) $
}x;
if ($linha =~ $log_re) {
say "Hora: $+{timestamp}, Nível: $+{nivel}";
say "Mensagem: $+{mensagem}";
}
# Ruim: Capturas posicionais (difícil de manter)
if ($linha =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+\[(\w+)\]\s+(.+)$/) {
say "Hora: $1, Nível: $2";
}
Padrões Pré-compilados
use v5.36;
# Bom: Compile uma vez, use muitas
my $email_re = qr/^[A-Za-z0-9._%+-]+\@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
sub validar_emails(@emails) {
return grep { $_ =~ $email_re } @emails;
}
Estruturas de Dados
Referências e Acesso Seguro a Dados Profundos
use v5.36;
# Referências a hash e array
my $config = {
database => {
host => 'localhost',
porta => 5432,
opcoes => ['utf8', 'sslmode=require'],
},
};
# Acesso seguro a dados profundos (retorna undef se qualquer nível estiver faltando)
my $porta = $config->{database}{porta}; # 5432
my $faltando = $config->{cache}{host}; # undef, nenhum erro
# Fatias de hash
my %subconjunto;
@subconjunto{qw(host porta)} = @{$config->{database}}{qw(host porta)};
# Fatias de array
my @primeiros_dois = $config->{database}{opcoes}->@[0, 1];
# Loop for com múltiplas variáveis (experimental em 5.36, estável em 5.40)
use feature 'for_list';
no warnings 'experimental::for_list';
for my ($chave, $valor) (%$config) {
say "$chave => $valor";
}
Entrada/Saída de Arquivos
Open de Três Argumentos
use v5.36;
# Bom: open de três argumentos com autodie (módulo central, elimina 'or die')
use autodie;
sub ler_arquivo($caminho) {
open my $fh, '<:encoding(UTF-8)', $caminho;
local $/;
my $conteudo = <$fh>;
close $fh;
return $conteudo;
}
# Ruim: open de dois argumentos (risco de injeção de shell, veja perl-security)
open FH, $caminho; # NUNCA faça isso
open FH, "< $caminho"; # Ainda ruim — dados do usuário na string de modo
Path::Tiny para Operações de Arquivo
use v5.36;
use Path::Tiny;
my $arquivo = path('config', 'app.json');
my $conteudo = $arquivo->slurp_utf8;
$arquivo->spew_utf8($novo_conteudo);
# Iterar diretório
for my $item (path('src')->children(qr/\.pl$/)) {
say $item->basename;
}
Organização de Módulos
Layout Padrão de Projeto
MyApp/
├── lib/
│ └── MyApp/
│ ├── App.pm # Módulo principal
│ ├── Config.pm # Configuração
│ ├── DB.pm # Camada de banco de dados
│ └── Util.pm # Utilitários
├── bin/
│ └── myapp # Script de entrada
├── t/
│ ├── 00-load.t # Testes de compilação
│ ├── unit/ # Testes unitários
│ └── integration/ # Testes de integração
├── cpanfile # Dependências
├── Makefile.PL # Sistema de build
└── .perlcriticrc # Configuração de linting
Padrões 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;
Ferramentas
Configuração do perltidy (.perltidyrc)
-i=4 # Indentação de 4 espaços
-l=100 # Comprimento de linha de 100 caracteres
-ci=4 # Indentação de continuação
-ce # cuddled else
-bar # Chave de abertura na mesma linha
-nolq # Não recuar strings longas entre aspas
Configuração do 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
Gerenciamento de Dependências (cpanfile + carton)
cpanm App::cpanminus Carton # Instalar ferramentas
carton install # Instalar dependências do cpanfile
carton exec -- perl bin/myapp # Executar com dependências locais
# cpanfile
requires 'Moo', '>= 2.005';
requires 'Path::Tiny';
requires 'JSON::MaybeXS';
requires 'Try::Tiny';
on test => sub {
requires 'Test2::V0';
requires 'Test::MockModule';
};
Referência Rápida: Expressões Idiomáticas Modernas do Perl
| Padrão Legado | Substituição Moderna |
|---|---|
use strict; use warnings; | use v5.36; |
my ($x, $y) = @_; | sub foo($x, $y) { ... } |
@{ $ref } | $ref->@* |
%{ $ref } | $ref->%* |
open FH, "< $arquivo" | open my $fh, '<:encoding(UTF-8)', $arquivo |
blessed hashref | Classe Moo com tipos |
$1, $2, $3 | $+{nome} (capturas nomeadas) |
eval { }; if ($@) | Try::Tiny ou try/catch nativo (5.40+) |
BEGIN { require Exporter; } | use Exporter 'import'; |
| Operações manuais de arquivo | Path::Tiny |
blessed($o) && $o->isa('X') | $o isa 'X' (5.32+) |
builtin::true / false | use builtin 'true', 'false'; (5.36+, experimental) |
Anti-padrões
# 1. open de dois argumentos (risco de segurança)
open FH, $nome_arquivo; # NUNCA
# 2. Sintaxe de objeto indireto (análise ambígua)
my $obj = new Foo(bar => 1); # Ruim
my $obj = Foo->new(bar => 1); # Bom
# 3. Dependência excessiva de $_
map { processar($_) } grep { validar($_) } @itens; # Difícil de acompanhar
my @validos = grep { validar($_) } @itens; # Melhor: dividir
my @resultados = map { processar($_) } @validos;
# 4. Desabilitar strict refs
no strict 'refs'; # Quase sempre errado
${"My::Package::$var"} = $valor; # Use um hash
# 5. Variáveis globais como configuração
our $TIMEOUT = 30; # Ruim: global mutável
use constant TIMEOUT => 30; # Melhor: constante
# Melhor: Atributo Moo com valor padrão
# 6. eval de string para carregar módulos
eval "require $modulo"; # Ruim: risco de injeção de código
eval "use $modulo"; # Ruim
use Module::Runtime 'require_module'; # Bom: carregamento seguro de módulos
require_module($modulo);
Lembre-se: Perl moderno é limpo, legível e seguro. Deixe use v5.36 lidar com a burocracia, use Moo para objetos e prefira os módulos testados em batalha do CPAN em vez de soluções feitas à mão.