Ροή ελέγχου

try#

Εκτελεί ένα μπλοκ και εκτρέπει οποιαδήποτε εξαίρεση εκπέμψει σε ένα μπλοκ catch, με προαιρετικό μπλοκ finally που εκτελείται πάντα κατά την έξοδο.

Η try/catch είναι η δομημένη σύνταξη χειρισμού εξαιρέσεων της Perl. Το μπλοκ try εκτελείται· αν οποιαδήποτε εντολή σε αυτό εκπέμψει (μέσω die, μη ληφθείσας εξαίρεσης από καλούμενο, ή σφάλματος κατά τον χρόνο εκτέλεσης), ο έλεγχος μεταφέρεται στο μπλοκ catch με την τιμή της εξαίρεσης δεσμευμένη στην εντός παρενθέσεων λεξιλογική μεταβλητή. Αν δεν εκπέμψει τίποτα, το μπλοκ catch παραλείπεται. Σε αντίθεση με την eval, ένα return μέσα σε try επιστρέφει από την περικλείουσα υπορουτίνα, όχι από το μπλοκ - η try είναι ροή ελέγχου, όχι πλαίσιο κλήσης.

Η σύνταξη πρέπει να ενεργοποιηθεί με φύλακα χαρακτηριστικού:

use feature 'try';

Σύνοψη#

try BLOCK catch (VAR) BLOCK
try BLOCK catch (VAR) BLOCK finally BLOCK

Τι επιστρέφεται#

Η try είναι εντολή, όχι έκφραση, αλλά αποδίδει την τελευταία αξιολογούμενη τιμή όποιου κλάδου εκτελέστηκε - του μπλοκ try σε επιτυχία, ή του μπλοκ catch σε αποτυχία. Όταν χρησιμοποιείται ως η τελευταία εντολή υπορουτίνας ή μπλοκ do, αυτή η τιμή γίνεται το αποτέλεσμα:

my $value = do {
    try {
        fetch_thing(@args);
    }
    catch ($e) {
        warn "fetch failed: $e";
        $DEFAULT;
    }
};

Do not write return inside a try that is being used this way

  • return exits the enclosing sub, bypassing the assignment.

Η τελική έκφραση ενός μπλοκ finally απορρίπτεται· δεν μπορεί να συνεισφέρει στην τιμή.

Καθολική κατάσταση που επηρεάζει#

  • Το $@ δεν είναι ο μηχανισμός εδώ. Η try/catch δεσμεύει την εξαίρεση στη λεξιλογική μεταβλητή που ονοματίζεται στο catch (VAR), όχι στο $@. Ο κώδικας μέσα στο catch βλέπει ό,τι έτυχε να κρατά το $@ πριν την try· βασιστείτε στη λεξιλογική.

  • Τα $_, $!, και άλλες δυναμικές καθολικές δεν επηρεάζονται από την ίδια την κατασκευή - μόνο από όποιον κώδικα εκτελείται μέσα στα μπλοκ.

Η μεταβλητή catch#

Το catch πρέπει να ακολουθείται αμέσως από δήλωση μεταβλητής σε παρενθέσεις. Το my είναι έμμεσο - γράψτε catch ($e), όχι catch (my $e). Η μεταβλητή είναι λεξιλογική στο μπλοκ catch και κρατά την εκπεμπόμενη τιμή ακριβώς όπως την παρέδωσε η die: συμβολοσειρά, blessed αναφορά, ή οποιοδήποτε βαθμωτό:

try {
    risky();
}
catch ($e) {
    if (ref $e && $e->isa('My::Exception')) {
        $e->rethrow if $e->is_fatal;
        warn $e->message;
    }
    else {
        warn "plain die: $e";
    }
}

Δεν υπάρχει αποστολή σε πολλαπλά catch με βάση την κλάση εξαίρεσης - γράψτε εσείς την ταξινόμηση μέσα στο ένα μπλοκ catch.

finally#

Ένα προαιρετικό τρίτο μπλοκ εκτελείται σε κάθε διαδρομή εξόδου από την κατασκευή try/catch: κανονική απρόσκοπτη ολοκλήρωση, εξαίρεση που πιάστηκε από το catch, εξαίρεση που ξανα-εκπέμφθηκε από το catch, ή μη τοπική μεταφορά (return, last, next, redo, goto) από οποιοδήποτε από τα δύο μπλοκ.

try {
    open_resource();
    use_resource();
}
catch ($e) {
    warn "failed: $e";
}
finally {
    close_resource();
}

Το finally είναι το σωστό μέρος για κώδικα απελευθέρωσης που πρέπει να εκτελεστεί όπως και αν εξέλθει το προστατευμένο τμήμα - file handles, κλειδώματα, επαναφορά καθολικής κατάστασης.

Ένα μπλοκ finally έχει περιορισμούς που τα άλλα δύο μπλοκ δεν έχουν:

  • Κανένα return, κανένα goto, κανένα last / next / redo. Η απόπειρα κάποιου από αυτά αποτελεί σφάλμα κατά τη μεταγλώττιση ή τον χρόνο εκτέλεσης.

  • Η τιμή της τελικής έκφρασης αγνοείται - το finally δεν μπορεί να συνεισφέρει στην αποδιδόμενη τιμή της κατασκευής.

Το finally παραμένει πειραματικό στην Perl 5.42 και εκπέμπει προειδοποίηση στην κατηγορία experimental::try όταν χρησιμοποιείται. Τα ίδια τα try και catch έπαψαν να είναι πειραματικά στην 5.40 και δεν προειδοποιούν πλέον.

Αντιπαραβολή με eval { ... } / $@#

Το παλαιότερο μοτίβο εξακολουθεί να υποστηρίζεται, αλλά η try/catch διορθώνει τρεις μακροχρόνιες παγίδες:

Παγίδα

eval / $@

try / catch

Επικάλυψη του $@ μεταξύ της εξόδου από το eval και του ελέγχου if ($@)

πραγματικός κίνδυνος, χρειάζεται χορό local $@

η εξαίρεση δεσμεύεται σε φρέσκια λεξιλογική

return μέσα στο προστατευμένο μπλοκ

επιστρέφει από το eval, όχι από την υπορουτίνα

επιστρέφει από την περικλείουσα υπορουτίνα

Διάκριση μεταξύ «κανένα σφάλμα» και «το σφάλμα ήταν ψευδές»

το $@ μπορεί να είναι ψευδές-αλλά-ορισμένο

το catch εκτελείται μόνο σε πραγματική εκπομπή

Χρησιμοποιήστε try για νέο κώδικα. Καταφύγετε σε eval EXPR μόνο όταν πραγματικά χρειάζεστε τη μεταγλώττιση κατά τον χρόνο εκτέλεσης του string-eval.

Παραδείγματα#

Παγίδευση εξαίρεσης και καταγραφή της:

use feature 'try';

try {
    my $x = call_a_function();
    $x < 100 or die "too big: $x";
    send_output($x);
}
catch ($e) {
    warn "unable to output a value: $e";
}
print "finished\n";

Χρήση της try ως έκφρασης μέσα σε do για επιλογή εναλλακτικής λύσης:

use feature 'try';

my $config = do {
    try { load_config($path) }
    catch ($e) { warn "using defaults: $e"; default_config() }
};

Άνευ όρων απελευθέρωση πόρου με finally:

use feature 'try';
no warnings 'experimental::try';   # finally is still experimental

my $lock = acquire_lock();
try {
    do_critical_work();
}
catch ($e) {
    warn "critical work failed: $e";
}
finally {
    release_lock($lock);
}

Ταξινόμηση εξαιρέσεων μέσα σε ένα μόνο μπλοκ catch:

use feature 'try';

try {
    fetch_remote();
}
catch ($e) {
    if (ref $e && $e->isa('Net::Timeout')) {
        retry_later();
    }
    elsif (ref $e && $e->isa('Net::Auth')) {
        die $e;                     # rethrow auth failures
    }
    else {
        warn "unexpected: $e";
    }
}

Το return μέσα σε try επιστρέφει από την περικλείουσα υπορουτίνα - όχι μόνο από το μπλοκ:

use feature 'try';

sub first_valid {
    for my $path (@_) {
        try {
            return load($path);     # exits first_valid on success
        }
        catch ($e) {
            warn "skip $path: $e";
        }
    }
    return;                         # all paths failed
}

Οριακές περιπτώσεις#

  • Απαιτείται φύλακας χαρακτηριστικού. Χωρίς use feature 'try' (ή ένα bundle use v5.34 ή νεότερο που το ενεργοποιεί), το try είναι απλός προσδιοριστής και η κατασκευή είναι συντακτικό σφάλμα.

  • Το catch είναι υποχρεωτικό. Το try { ... } από μόνο του δεν είναι νόμιμο - κάθε try χρειάζεται catch. Χρησιμοποιήστε defer ή finally αν θέλετε καθαρισμό χωρίς χειρισμό εξαιρέσεων, σε συνδυασμό με ένα τετριμμένο catch ($e) { die $e } rethrow αν θέλετε επίσης να αφήσετε την εξαίρεση να διαδοθεί.

  • Επανεκπομπή. Μέσα στο catch, το die $e ξανα-εκπέμπει. Δεν υπάρχει έμμεση επανεκπομπή αν απλώς πέσετε στο τέλος του catch· κάνοντάς το αυτό καταπίνετε την εξαίρεση. Αποφασίστε σκόπιμα.

  • Το $@ δεν εκκαθαρίζεται με την είσοδο στο try και δεν ορίζεται από μια εξαίρεση που πιάστηκε. Μη διαβάζετε το $@ μέσα στο catch περιμένοντας την εκπεμπόμενη τιμή - διαβάστε τη λεξιλογική.

  • Η caller() δεν βλέπει την try. Όπως ο while ή ο foreach, η try δεν είναι πλαίσιο κλήσης. Η caller την παρακάμπτει, που είναι σωστό: αφού η return διαδίδεται μέσα από την try, η try δεν έχει ανεξάρτητη σημασιολογία επιστροφής που να αξίζει να εκτεθεί στην ενδοσκόπηση.

  • Οι έλεγχοι βρόχου περνούν διαπεραστικά. Τα last, next και redo μέσα σε try ή catch δρουν στον περικλείοντα βρόχο, όχι στην κατασκευή try. Ένα μπλοκ finally θα εκτελεστεί ακόμη και πριν ολοκληρωθεί η μεταφορά.

  • Οι περιορισμοί του finally επιβάλλονται. Τα return, goto, και οι έλεγχοι βρόχου μέσα σε finally είναι σφάλματα. Αν χρειάζεστε υπό συνθήκη καθαρισμό που μπορεί να ματαιωθεί, κάντε το στο catch ή έξω από την κατασκευή.

  • Κανένα συντακτικό σιρόπι έκφρασης try πέρα από την απόδοση τελευταίας εντολής. Η try δεν είναι γενική έκφραση· μπορεί να παράγει τιμή μόνο όταν είναι η τελευταία εντολή μπλοκ που αποδίδει τιμή (do, υπορουτίνα). Το my $x = try { ... } catch ($e) { ... }; είναι συντακτικό σφάλμα.

Διαφορές από το upstream#

Πλήρως συμβατό με το upstream Perl 5.42.

  • Τα try και catch δεν είναι πειραματικά (από την 5.40) και δεν εκπέμπουν προειδοποιήσεις.

  • Το finally εξακολουθεί να είναι πειραματικό στην 5.42 και εκπέμπει προειδοποιήσεις experimental::try όταν χρησιμοποιείται· αποσιωπήστε με no warnings 'experimental::try'.

Δείτε επίσης#

  • catch - το υποχρεωτικό δεύτερο μπλοκ· λαμβάνει την εξαίρεση σε μια λεξιλογική μεταβλητή εντός παρενθέσεων

  • finally - προαιρετικό τρίτο μπλοκ για άνευ όρων καθαρισμό· εξακολουθεί πειραματικό στην 5.42

  • die - η πλευρά εκπομπής της κατασκευής· οποιοδήποτε βαθμωτό (συμβολοσειρά, blessed αναφορά, αντικείμενο) μπορεί να είναι η εκπεμπόμενη τιμή

  • eval - το παλαιότερο μοτίβο block-eval / $@, ακόμη απαραίτητο για string eval· προτιμήστε try για αμιγή χειρισμό εξαιρέσεων

  • defer - άνευ όρων καθαρισμός χωρίς περιβάλλον try/catch· η σημασιολογική βάση πάνω στην οποία ορίζεται το finally

  • return - μέσα σε try επιστρέφει από την περικλείουσα υπορουτίνα, όχι από το μπλοκ· μέσα σε finally είναι σφάλμα