use v5.36 — Reemplaza el boilerplate antiguo# DataPulse::Connector::Meta — código heredado use strict; use warnings; use feature 'say', 'signatures', 'state'; no warnings 'experimental::signatures'; sub connect_meta { my ($app_id, $secret) = @_; # ... }
# Una sola línea activa todo use v5.36; # → strict ON # → warnings ON # → say, state, signatures ON # → no experimental warnings sub connect_meta($app_id, $secret) { # firma limpia, arity checked }
Moo — Atributos tipados y declarativospackage DataPulse::Connector::Meta; use v5.36; use Moo; use Types::Standard qw(Str Int Bool); use namespace::autoclean; # Atributos declarativos con tipos y defaults has app_id => (is => 'ro', isa => Str, required => 1); has app_secret => (is => 'ro', isa => Str, required => 1); has access_token=> (is => 'rw', isa => Str, default => sub { '' }); has api_version => (is => 'ro', isa => Str, default => sub { 'v19.0' }); has max_retries => (is => 'ro', isa => Int, default => sub { 3 }); has debug => (is => 'ro', isa => Bool, default => sub { 0 }); # Constantes — mejor que variables globales mutables ($TIMEOUT) use constant { BASE_URL => 'https://graph.facebook.com', ERROR_RATE_LIMIT => 17, ERROR_EXPIRED => 190, };
Try::Tiny — Parámetros con defaults y excepciones# MODERNO: firma con $self, parámetro opcional con default sub get_campaign_insights($self, $campaign_id, $date_range = undef) { # Try::Tiny: bloques try/catch explícitos (sin $@ mágico) my $token = try { my $raw = $self->_fetch_token(); DataPulse::Util::sanitize_token($raw); # puede morir } catch { warn "[Meta] Auth error: $_"; undef; # valor de retorno del catch }; # Retry con back-off — label de bucle explícito my $attempt = 0; RETRY: while ($attempt < $self->max_retries) { $attempt++; my $res = try { $self->_http_get(...) } catch { warn "HTTP err: $_"; undef }; next RETRY unless defined $res; return $self->_parse_insights($res); } }
/x y capturas nombradas — Parseando errores de Meta API# $1, $2, $3 — ¿qué era cada uno? if ($res =~ /"error".*?"code":(\d+).*?"message":"([^"]+)"/s) { my ($code, $msg) = ($1, $2); warn "Error $code: $msg"; }
# qr// compilado una sola vez al cargar my $meta_err_re = qr{ "error"\s*:\s*\{ .*? "code"\s*:\s*(?<code> \d+ ) .*? "message"\s*:\s*"(?<message> [^"]+ )" \} }xs; if ($res =~ $meta_err_re) { my ($code, $msg) = @+{qw(code message)}; }
DataPulse::Util — Funciones exportables con firmaspackage DataPulse::Util; use v5.36; use Exporter 'import'; # solo importar 'import', no todo Exporter our @EXPORT_OK = qw(trim slugify format_currency sanitize_token); our %EXPORT_TAGS = (all => \@EXPORT_OK); # /r devuelve copia modificada (no muta $str) sub trim($str) { $str =~ s/^\s+|\s+$//gr } # Normaliza acentos + guiones para slugs de campañas sub slugify($text) { my $s = lc $text; $s =~ s/[áàäâ]/a/g; $s =~ s/[éèëê]/e/g; $s =~ s/[íìïî]/i/g; $s =~ s/[óòöô]/o/g; $s =~ s/ñ/n/g; $s =~ s/[^a-z0-9]+/-/g; $s =~ s/^-|-$//g; $s } # Parámetros opcionales con default en la firma sub format_currency($amount, $symbol = '€', $decimals = 2) { sprintf "%s %.*f", $symbol, $decimals, $amount } # Regex /x con captura nombrada para validar tokens OAuth my $token_re = qr{ ^ (?<token> [A-Za-z0-9\-_\.~]+ ) $ # chars válidos OAuth }x; sub sanitize_token($token) { return $+{token} if $token =~ $token_re; die "Token inválido: caracteres no permitidos\n"; } 1;
| Patrón Legacy | Sustitución Moderna | Versión |
|---|---|---|
| use strict; use warnings; | use v5.36; | 5.36+ |
| my ($x, $y) = @_; | sub foo($x, $y) { ... } | 5.36+ |
| @{ $ref } | $ref->@* | 5.24+ |
| open FH, "< $file" | open my $fh, '<:encoding(UTF-8)', $file | 5.6+ |
| bless \%args, $class | Moo + Types::Standard | CPAN |
| $1, $2, $3 (capturas) | $+{nombre} (capturas nombradas) | 5.10+ |
| eval { }; if ($@) | Try::Tiny / try/catch (5.40+) | CPAN / 5.40 |
| BEGIN { require Exporter } | use Exporter 'import'; | 5.8+ |
| blessed($o) && $o->isa('X') | $o isa 'X' | 5.32+ |
| our $TIMEOUT = 30; | use constant TIMEOUT => 30; | 5.0+ |
| eval "require $module" | Module::Runtime::require_module($m) | CPAN |
| open FH, $filename | Path::Tiny->new($file)->slurp_utf8 | CPAN |
cpanfilerequires 'Moo', '>= 2.005'; requires 'Types::Standard'; requires 'Try::Tiny'; requires 'Path::Tiny'; requires 'JSON::MaybeXS'; requires 'namespace::autoclean'; requires 'Module::Runtime'; on test => sub { requires 'Test2::V0'; requires 'Test::MockModule'; };