Εξαιρέσεις, die, και τυποποιημένα αντικείμενα σφαλμάτων#
Αυτό το κεφάλαιο καλύπτει το πώς εγείρετε, παγιδεύετε, και ταξινομείτε αποτυχίες χρόνου εκτέλεσης: η die και η eval, το ενσωματωμένο try/catch, η υγιεινή των ειδικών μεταβλητών ($@, $!, $?), το $SIG{__DIE__}, και ο σχεδιασμός κλάσεων εξαιρέσεων.
Οι αναγνώστες που γνωρίζουν τα die και eval { } θα βρουν εδώ τους σύγχρονους αντικαταστάτες για αποστολή με βάση συμβολοσειρές πάνω σε μηνύματα εξαιρέσεων και τους κινδύνους του $SIG{__DIE__} που το τυπικό μοτίβο eval σιωπηρά κρύβει.
Η die και η eval#
Η die εκτοξεύει μια εξαίρεση· το πλησιέστερο περικλείον eval { } την παγιδεύει και αφήνει το μήνυμα στο $@:
eval {
risky();
};
if (my $err = $@) {
warn "caught: $err";
}
Χωρίς τελικό \n, το όρισμα της die παίρνει at FILE line N. προσαρτημένο από το πλαίσιο του καλούντος. Με \n, δεν προσαρτάται θέση - χρησιμοποιήστε το για μηνύματα που θα παρουσιαστούν στους χρήστες, παραλείψτε το για μηνύματα που θέλετε να ιχνηλατήσετε.
Μη χρησιμοποιείτε ποτέ τη μορφή συμβολοσειράς eval "..." για χειρισμό εξαιρέσεων. Μεταγλωττίζει το όρισμά της και φέρει κάθε κίνδυνο κώδικα που παρέχει ο χρήστης.
Το ενσωματωμένο try/catch#
Σε Perl 5.34 και μεταγενέστερες, υπάρχει διαθέσιμο ένα ειδικό try/catch πίσω από σημαία χαρακτηριστικού. Από την 5.40 δεν είναι πειραματικό:
use v5.40;
use feature 'try';
try {
risky();
}
catch ($e) {
warn "caught: $e";
}
Το finally παραμένει πειραματικό μέχρι την 5.42. Αναμένετε:
use feature 'try';
no warnings 'experimental::try';
σε perl μεταξύ 5.34 και 5.40.
Για κώδικα που πρέπει να τρέχει σε 5.34 ή 5.36 και επίσης σε νεότερες perl, το Feature::Compat::Try διαλέγει το χαρακτηριστικό του πυρήνα όπου είναι διαθέσιμο και πέφτει εφεδρικά στο Syntax::Keyword::Try:
use Feature::Compat::Try;
try { risky(); }
catch ($e) { warn "caught: $e" }
Γιατί το ενσωματωμένο try έναντι του Try::Tiny#
Το Try::Tiny εξακολουθεί να δουλεύει και παραμένει σύνηθες. Προτιμήστε την ενσωματωμένη μορφή για νέο κώδικα: αποφεύγει το πλαίσιο κλήσης sub που εισάγει το Try::Tiny (το οποίο εμφανίζεται σε ίχνη στοίβας), έχει μηδενικό κόστος εκκίνησης, και συντίθεται καθαρά με τον τελεστή isa.
Τα παρακάτω έχουν αποσυρθεί - μην τα υιοθετείτε:
TryCatch- εγκαταλελειμμένο.Error.pm- έχει αντικατασταθεί.Exception::Class::TryCatch- έχει αντικατασταθεί από τοThrowable.
Αλλοίωση του $@ - ο κλασικός κίνδυνος της eval#
Το $@ τίθεται από την eval· είναι επίσης παρατηρήσιμο καθολικά. Οποιοσδήποτε κώδικας τρέχει μεταξύ της επιστροφής της eval και του ελέγχου σας στο $@ - μια μέθοδος DESTROY, ένας χειριστής tied μεταβλητής, μια άσχετη εμφωλευμένη eval - μπορεί να το επανεγγράψει. Πάντα αντιγράψτε πρώτα:
eval { risky(); };
my $err = $@;
return unless $err;
Το ενσωματωμένο try/catch και το Syntax::Keyword::Try αποφεύγουν αυτή την παγίδα δίνοντάς σας το σφάλμα ως παράμετρο του catch· η σκέτη eval όχι. Προτιμήστε το πρώτο για οποιονδήποτε κώδικα παίρνει στα σοβαρά τις εξαιρέσεις.
Ειδικές μεταβλητές γειτονικές στις εξαιρέσεις#
Μεταβλητή | Τίθεται από | Διαβάστε άμεσα διότι… |
|---|---|---|
|
| οποιαδήποτε ενδιάμεση κλήση μπορεί να το επανεγγράψει. |
| Τελευταία αποτυχημένη κλήση συστήματος | οποιαδήποτε επόμενη κλήση συστήματος το επανεγγράφει. |
| παιδί | το επόμενο παιδί το επανεγγράφει. |
| Εκτεταμένο σφάλμα OS | σε μη-Unix· σε Linux συνήθως αντικατοπτρίζει το |
Αποκωδικοποίηση του $? μετά από system / backticks:
if ($? == -1) { warn "child failed to start: $!" }
elsif ($? & 127) { warn "child killed by signal " . ($? & 127) }
elsif ($? & 128) { warn "core dumped" } # combined with above
else { my $exit = $? >> 8; ... }
Ταξινόμηση εξαιρέσεων: αποστολή με βάση τον τύπο#
Για μη τετριμμένα προγράμματα, η διάκριση «δεν βρέθηκε χρήστης» από «η βάση δεδομένων είναι εκτός» από «σφάλμα προγραμματισμού» μέσω ανάλυσης συμβολοσειρών σφάλματος είναι εύθραυστη. Εκτοξεύστε τυποποιημένα αντικείμενα και ταιριάξτε σε αυτά:
use v5.40;
use feature 'try';
use Scalar::Util qw(blessed);
try {
do_business();
}
catch ($e) {
if ($e isa MyApp::Err::NotFound) { return http_404($e) }
elsif ($e isa MyApp::Err::AuthFailure) { return http_403($e) }
elsif ($e isa MyApp::Err) { return http_500($e) }
elsif (blessed($e) && $e->isa('DBIx::Class::Exception')) {
return http_500($e);
}
else {
MyApp::Err::Unknown->throw(cause => "$e");
}
}
Ο τελεστής isa (σταθερός σε 5.36+) κάνει το σωστό για overloaded αντικείμενα και είναι μηδενικού κόστους όταν ο αριστερός τελεστέος δεν είναι αναφορά. Συνδυάστε τον με έναν τυποποιημένο περιτυλιγτή στο πιο εξωτερικό όριο ώστε όλα προς τα κάτω να είναι MyApp::Err.
Σχεδιασμός κλάσεων εξαιρέσεων#
Η προεπιλογή του 2026: ρόλος Throwable από το CPAN, προαιρετικά μέσω του Throwable::SugarFactory για δηλωτικές ιεραρχίες.
Ελάχιστη βιώσιμη κλάση:
package MyApp::Err::NotFound;
use Moo;
with 'Throwable';
has resource => ( is => 'ro', required => 1 );
has id => ( is => 'ro', required => 1 );
sub message {
my $self = shift;
sprintf 'not found: %s/%s', $self->resource, $self->id;
}
1;
Χρήση:
MyApp::Err::NotFound->throw(resource => 'user', id => $uid);
Η throw παρέχεται από τον ρόλο· κάνει die με $self (το blessed αντικείμενο). Η ->new χωρίς throw κατασκευάζει χωρίς να εγείρει.
Δηλωτική μορφή μέσω Throwable::SugarFactory:
package MyApp::Err;
use Throwable::SugarFactory;
exception 'GenericError' => 'something bad happened';
exception 'NotFound' => 'resource not found'
=> (has => [ id => (is => 'ro') ]);
exception 'AuthFailure' => 'auth failed'
=> (has => [ reason => (is => 'ro') ]);
exception 'AuthFailureExpired' => 'session expired'
=> (extends => 'AuthFailure');
1;
Αυτό παράγει: την κλάση, έναν βοηθό κατασκευαστή-και-εκτόξευσης not_found(id => ...), ένα κατηγόρημα is_not_found($e), και ->to_hash σε κάθε στιγμιότυπο για σειριοποίηση JSON.
Τι πρέπει να έχει μια κλάση#
Όνομα κλάσης - ώστε το
$e isa MyApp::Err::Xνα αποστέλλει.Δομημένα πεδία -
id,resource,code.Ένα ανθρώπινο
message- χωρίς αυτό, ηwarn $eεκτυπώνειMyApp::Err=HASH(0x...).Σειριοποίηση -
->to_hashγια καταγραφή / aggregators / JSON API.Προέλευση - αρχείο + γραμμή που συλλαμβάνονται στο
throw. ΤοThrowableτα συλλαμβάνει αυτόματα.Ίχνος στοίβας αν αξίζει να διερευνηθεί το σφάλμα αργότερα: το
with 'StackTrace::Auto'προσθέτει την->stack_trace.
Πότε να μη χρησιμοποιείτε κλάση#
Η die με σκέτη συμβολοσειρά παραμένει σωστή για σύντομα σενάρια, εφάπαξ εργασίες, και κάθε περίπτωση όπου κανένας καλών δεν θα αποστείλει με βάση το σφάλμα:
die "permission denied: $path\n";
Το τελικό \n καταστέλλει την προσάρτηση αρχείου-και-γραμμής· το μήνυμα απευθύνεται στον χρήστη.
Η die με αναφορά κατακερματισμού - die { type => 'not_found', id => $uid } - είναι μεταβατική μορφή. Τη στιγμή που ένας καλών γράφει if (ref $e eq 'HASH' && $e->{type} eq 'not_found') τρεις φορές, αναδιαρθρώστε σε κλάση.
Αποσυρμένα frameworks εξαιρέσεων - μην τα υιοθετείτε:
Error.pm- χρησιμοποιήστεThrowable(βασισμένο σε ρόλους) ήException::Class.Class::Throwable- λειτουργικό· τοThrowableείναι η σύγχρονη επιλογή.Exception::Class::TryCatch- έχει αντικατασταθεί από το ενσωματωμένοtry.
$SIG{__DIE__} - χειριστείτε με προσοχή#
Το $SIG{__DIE__} πυροδοτείται σε κάθε die, συμπεριλαμβανομένων των die μέσα σε eval. Μια αφελής εγκατάσταση μετατρέπει κάθε παγιδευμένη εξαίρεση σε θόρυβο:
# WRONG: fires on every eval that catches an error
$SIG{__DIE__} = sub { Carp::confess(@_) };
Η ειδική μεταβλητή $^S σας λέει αν βρίσκεστε μέσα σε eval: αληθές σημαίνει «ναι, κάποιος θα την παγιδεύσει αυτή»· ψευδές σημαίνει «αυτή φτάνει στην κορυφή». Φυλαχτείτε με βάση αυτή:
$SIG{__DIE__} = sub {
return if $^S; # inside eval - not for us
my ($err) = @_;
eval { ship_to_sentry($err) }; # never let the handler itself die
warn "reporter failed: $@" if $@;
die $err; # re-raise
};
Για ενσωμάτωση Sentry / aggregator, τυλίξτε την κλήση του reporter σε δικό της eval - ένας reporter σφαλμάτων που αποτυγχάνει δεν πρέπει να επισκιάσει την αρχική εξαίρεση.
Πριν εγκαταστήσετε πάνω από έναν υπάρχοντα χειριστή, αλυσιδώστε:
my $previous = $SIG{__DIE__};
$SIG{__DIE__} = sub {
return if $^S;
my ($err) = @_;
eval { ship_to_sentry($err) };
warn "reporter failed: $@" if $@;
goto &$previous if $previous;
die $err;
};
$SIG{__WARN__} - κάντε τις προειδοποιήσεις μοιραίες, ή δρομολογήστε τις#
Συμμετρικό προς το $SIG{__DIE__}, πυροδοτείται σε κάθε warn:
$SIG{__WARN__} = sub { die $_[0] }; # promote warnings to exceptions
Χρήσιμο κατά την ανάπτυξη ως παγίδα συναγερμού· σπάνια επιθυμητό στην παραγωγή. Πιο συνηθισμένη χρήση στην παραγωγή: δρομολόγηση προειδοποιήσεων μέσω logger.
Μάθετε περισσότερα#
print-and-die - η οικογένεια Carp και διακόπτες για ίχνος στοίβας παντού.
perlfunc/die,perlfunc/eval,perlfunc/warn,perlvar- σελίδες αναφοράς για τα γλωσσικά πρωτογενή.