2010-01-29 8 views
28

Una discussione su another question mi ha fatto riflettere: che cosa hanno i sistemi di eccezioni di altri linguaggi di programmazione?Cosa c'è di rotto sulle eccezioni in Perl?

eccezioni built-in di Perl sono un po 'ad hoc in quanto erano, come il sistema oggetto Perl 5, sorta-di imbullonati su come un ripensamento, e sovraccarico altre parole chiave (eval e die) che sono non dedicato specificamente alle eccezioni.

La sintassi può essere un po 'brutta, rispetto alle lingue con sintassi incorporata try/throw/catch. Io di solito faccio così:

eval { 
    do_something_that_might_barf(); 
}; 

if (my $err = [email protected]) { 
    # handle $err here 
} 

Ci sono diversi moduli CPAN che forniscono zucchero sintattico per aggiungere parole chiave try/catch e per consentire la facile dichiarazione di gerarchie di classi eccezione e quant'altro.

Il problema principale che vedo con il sistema di eccezione di Perl è l'uso dello speciale globale [email protected] per contenere l'errore corrente, piuttosto che un meccanismo dedicato catch -tipo che potrebbe essere più sicuro, dal punto di vista dell'ottica, sebbene non abbia mai Incontrare personalmente eventuali problemi con [email protected] ottenere munged.

+14

Forse la gente che come gli altri linguaggi di programmazione fanno più errori. – mob

+6

Oh merda, l'ho appena detto ad alta voce? Stavo solo scherzando! – mob

+4

Bene, altri linguaggi hanno eccezioni e Perl no. Questa è la differenza. Il fatto che li fingiamo non rende il Perl davvero eccezioni. –

risposta

13

Alcune classi di eccezioni, ad es. Error, non può gestire il controllo di flusso dai blocchi try/catch. Questo porta ad errori sottili:

use strict; use warnings; 
use Error qw(:try); 

foreach my $blah (@somelist) 
{ 
    try 
    { 
     somemethod($blah); 
    } 
    catch Error with 
    { 
     my $exception = shift; 
     warn "error while processing $blah: " . $exception->stacktrace(); 
     next; # bzzt, this will not do what you want it to!!! 
    }; 

    # do more stuff... 
} 

La soluzione è quella di utilizzare una variabile di stato e verificare che al di fuori del blocco try/catch, che a me sembra orribilmente come il codice n00b puzzolente.

Due altri "trucchi" in errore (entrambi i quali mi hanno causato il dolore in quanto sono orribili per eseguire il debug se non è stato eseguito in questo prima):

use strict; use warnings; 
try 
{ 
    # do something 
} 
catch Error with 
{ 
    # handle the exception 
} 

Sembra ragionevole, giusto? Questo codice viene compilato, ma porta a errori bizzarri e imprevedibili. I problemi sono:

  1. use Error qw(:try) è stato omesso, in modo che il blocco try {}... saranno misparsed (si può o non può vedere un avvertimento, a seconda del resto del codice)
  2. e virgola mancante dopo il blocco catch!Non intuitivo poiché i blocchi di controllo non utilizzano il punto e virgola, ma in realtà try è una chiamata al metodo prototipo .

Oh sì, che mi ricorda, inoltre, che a causa try, catch ecc sono chiamate di metodo, ciò significa che lo stack di chiamate all'interno di tali blocchi non sarà quello che vi aspettate. (C'è in realtà due livelli di stack in più a causa di una chiamata interna all'interno Error.pm.) Di conseguenza, ho un paio di moduli pieni di codice standard come questo, che aggiunge solo confusione:

my $errorString; 
try 
{ 
    $x->do_something(); 
    if ($x->failure()) 
    { 
     $errorString = 'some diagnostic string'; 
     return;  # break out of try block 
    } 

    do_more_stuff(); 
} 
catch Error with 
{ 
    my $exception = shift; 
    $errorString = $exception->text(); 
} 
finally 
{ 
    local $Carp::CarpLevel += 2; 
    croak "Could not perform action blah on " . $x->name() . ": " . $errorString if $errorString; 
}; 
+0

Tutti punti eccellenti. Soprattutto l'articolo di controllo del flusso. Non l'avevo considerato prima. – friedo

+3

Per il downvoter: gradirei suggerimenti per migliorare questo brutto codice, mi offende pure. Sono passato a TryCatch nei progetti successivi, ma questo post aveva lo scopo di dimostrare le insidie ​​con una particolare classe di eccezioni (e potrebbe estendersi ad altri, non li ho ancora provati tutti). – Ether

1

In C++ e C#, è possibile definire i tipi che possono essere lanciati, con blocchi di cattura separati che gestiscono ciascun tipo. I sistemi di tipo Perl hanno alcune problematiche relative a RTTI e ereditarietà, secondo quanto ho letto sul blog di Chomatic.

Non sono sicuro di come altri linguaggi dinamici gestiscono le eccezioni; sia C++ che C# sono linguaggi statici e hanno un certo potere nel sistema di tipi.

Il problema filosofico è che le eccezioni di Perl 5 sono imbullonate; non sono costruiti dall'inizio del linguaggio design come qualcosa di integrale su come è scritto Perl.

+0

I vincoli di tipo non sono tipi. Perl non ha alcun problema con i tipi, perché non ha alcun tipo. – jrockway

1

È stato un lungo periodo da quando ho usato Perl, quindi la mia memoria potrebbe essere sfocata e/o Perl potrebbe essere migliorata, ma da quello che ricordo (rispetto a Python, che uso quotidianamente):

  1. in quanto le eccezioni sono una tarda Inoltre, essi non sono sempre sostenuto nelle librerie di base

    (non è vero, non sono costantemente supportati in librerie di base, perché i programmatori che hanno scritto queste librerie non piace eccezioni)

  2. non esiste una gerarchia predefinita di eccezioni - non si può prendere un gruppo collegato di eccezioni da catturare la classe di base

  3. non esiste un equivalente di prova: ... finalmente: ... per definire un codice che sarà chiamato indipendentemente dal fatto che sia stata sollevata o meno un'eccezione, ad es liberare risorse.

    (finally in Perl è in gran parte inutili - i distruttori degli oggetti eseguiti subito dopo l'uscita portata, non ogni volta che ci capita di essere la pressione di memoria in modo che si può effettivamente rilasciare tutte le risorse non-memoria nel distruttore, e funzionerà. in modo sano.)

  4. (per quanto posso dire) si può buttare solo le stringhe -.. Non si può buttare gli oggetti che hanno ulteriori informazioni

    (completamente falso die $object funziona altrettanto bene come die $string)

  5. non si può ottenere una traccia dello stack che vi mostra dove l'eccezione è stato gettato - in Python si ottiene informazioni dettagliate compreso il codice sorgente per ogni linea nello stack di chiamate

    (Falso. perl -MCarp::Always e divertiti.)

  6. è un brutto scherzo.

    (. Soggettivo E 'implementato allo stesso modo in Perl come è ovunque E' appena usa parole chiave in modo diverso dai nomi..)

+3

# 5 non è vero - vedi 'Carp :: cluck()' e 'Carp :: confess()', e non è troppo difficile impostare un '$ SIG {__ DIE __}' e '$ SIG {__ WARN __}' gestore con Carp per ottenere tracce di stack per impostazione predefinita. Ma # 7 è più vero, quindi si uniforma. – mob

+2

Oggetto: # 4, puoi lanciare oggetti da Perl 5.6 almeno (la maggior parte dei sistemi di eccezione su CPAN sono basati su questo). Sono d'accordo che la semantica 'finally' può diventare pelosa – friedo

+0

# 3 - basta mettere questo codice dopo l'eval, o usare qualcosa come Error.pm o Try :: Tiny. –

2

Con Perl, il linguaggio e le eccezioni scritti dagli utenti sono combinati: entrambi impostano [email protected]. In altre lingue, le eccezioni della lingua sono separate dalle eccezioni scritte dall'utente e creano un flusso completamente separato.

È possibile individuare la base delle eccezioni scritte dall'utente.

Se c'è My::Exception::one e My::Exception::two

if ([email protected] and [email protected]>isa('My::Exception')) 

cattura entrambi.

Ricordare di rilevare eventuali eccezioni non utente con un else.

elsif ([email protected]) 
    { 
    print "Other Error [email protected]\n"; 
    exit; 
    } 

È anche bello avvolgere l'eccezione in una sottochiamata per lanciarla.

+1

Ciao, sembri nuovo qui. La tua risposta non sembra essere una risposta all'OP (cosa non funziona nelle eccezioni Perl), ma un commento sulla risposta di qualcun altro (forse quella di Dave Kirby)? Come tale, anche se le informazioni che presenti sono utili in generale, dal momento che non è una risposta all'OP, quindi avrebbe dovuto essere pubblicato come commento o altrove. –

+2

@Adam Bellaire: capito. Ho modificato la mia risposta. – bitbucket

23

Il metodo tipico maggior parte delle persone hanno imparato a gestire le eccezioni è vulnerabile a mancanti eccezioni intrappolati:

eval { some code here }; 
if([email protected]) { handle exception here }; 

Si può fare:

eval { some code here; 1 } or do { handle exception here }; 

Questo protegge dalla mancanza l'eccezione a causa di [email protected] essere stata colpita , ma è ancora vulnerabile a perdere il valore di [email protected].

Per essere sicuri di non clobere un'eccezione, quando si esegue la valutazione, è necessario localizzare [email protected];

eval { local [email protected]; some code here; 1 } or do { handle exception here }; 

Questa è tutta una rottura sottile, e la prevenzione richiede molta caldaia esoterica.

Nella maggior parte dei casi questo non è un problema. Ma sono stato bruciato dall'eccezione mangiando oggetti distruttori in codice reale. Il debug del problema è stato terribile.

La situazione è chiaramente negativa. Guarda tutti i moduli su CPAN costruiti offrono una gestione delle eccezioni decente.

Le risposte schiaccianti a favore di Try::Tiny combinate con il fatto che Try :: Tiny non è "troppo intelligente per metà", mi hanno convinto a provarlo. Cose come TryCatch e Exception::Class::TryCatch, Error e così via sono troppo complesse per me da fidarmi. Prova :: Tiny è un passo nella giusta direzione, ma non ho ancora una classe di eccezioni leggera da usare.

24

Try::Tiny (o moduli costruiti su di esso) è l'unico modo corretto per gestire le eccezioni in Perl 5. I problemi coinvolti sono sottili, ma l'articolo collegato li spiega in dettaglio.

Ecco come usarlo:

use Try::Tiny; 

try { 
    my $code = 'goes here'; 
    succeed() or die 'with an error'; 
} 
catch { 
    say "OH NOES, YOUR PROGRAM HAZ ERROR: $_"; 
}; 

eval e [email protected] sono parti in movimento non è necessario preoccuparvi di.

Alcune persone pensano che questo sia un problema, ma dopo aver letto le implementazioni di altri linguaggi (oltre a Perl 5), non è diverso da qualsiasi altro. C'è solo la parte mobile [email protected] che puoi catturare la tua mano ... ma come con altri pezzi di macchinari con parti mobili scoperte ... se non la tocchi, non ti strapperà le dita. Quindi usa Try :: Tiny e mantieni la velocità di digitazione;)

+4

Si noti che le versioni di Perl> = 5.14 ora cerca di mantenere le eccezioni sensate, quindi questo non è più l '"unico modo corretto". Ma è ancora abbastanza buono. – jrockway

8

Un problema che ho riscontrato recentemente con il meccanismo di eccezione eval riguarda il gestore $SIG{__DIE__}. Avevo - erroneamente - assunto che questo gestore venisse chiamato solo quando l'interprete Perl fosse uscito tramite die() e volesse utilizzare questo gestore per la registrazione degli eventi fatali. Si è poi scoperto che stavo registrando le eccezioni nel codice della libreria come errori fatali che chiaramente erano sbagliati.

La soluzione era per controllare lo stato della variabile $^S o $EXCEPTIONS_BEING_CAUGHT:

use English; 
$SIG{__DIE__} = sub { 
    if (!$EXCEPTION_BEING_CAUGHT) { 
     # fatal logging code here 
    } 
}; 

Il problema che vediamo qui è che il gestore __DIE__ viene utilizzato in due situazioni simili ma diverse. Quella variabile $^S sembra molto un add-on in ritardo per me. Non so se questo è davvero il caso, però.

0

Non usare eccezioni per errori regolari. Solo i problemi fatali che interromperanno l'esecuzione corrente dovrebbero morire. Tutti gli altri devono essere gestiti senza die.

Esempio: convalida del parametro del sub chiamato: non morire al primo problema. Controllare tutti gli altri parametri e quindi decidere di fermarsi restituendo qualcosa o avvisare e correggere i parametri difettosi e procedere. Che fanno in modalità test o di sviluppo. Ma probabilmente die in modalità produzione. Lascia che l'applicazione decida questo.

JPR (il mio login CPAN)

Saluti da Sögel, Germania

Problemi correlati