Ροή ελέγχου

finally#

Εκτελεί κώδικα εκκαθάρισης κατά την έξοδο από ένα try/catch, είτε το σώμα ολοκληρώθηκε επιτυχώς, είτε εξεγέρθηκε εξαίρεση, είτε υπήρξε άλμα προς τα έξω.

Η finally εισάγει ένα τρίτο μπλοκ που μπορεί να ακολουθήσει ένα ζεύγος try/catch. Το σώμα του εκτελείται ακριβώς μία φορά, αφού ολοκληρωθεί το σώμα του try - με όποιον τρόπο και αν ολοκληρώθηκε. Φυσιολογική ολοκλήρωση, εξαίρεση που πιάστηκε από το catch, εξαίρεση που διαφεύγει επειδή δεν υπάρχει αντίστοιχο catch, return από την περικλείουσα υπορουτίνα, goto, last/next/redo που εγκαταλείπει τον περικλείοντα βρόχο: όλες αυτές οι διαδρομές περνούν μέσα από το μπλοκ finally πριν συνεχίσει ο έλεγχος. Το μοτίβο είναι ισοδύναμο με τον προγραμματισμό ενός μπλοκ defer στην αρχή του try, με μια σύνταξη που κρατά την εκκαθάριση οπτικά συνδεδεμένη με το try/catch στο οποίο ανήκει.

Σύνοψη#

use feature 'try';
no warnings 'experimental::try';

try    { BODY }
catch ($e) { HANDLE }
finally { CLEANUP }

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

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

my $v = do {
    try    { compute() }
    catch ($e) { $DEFAULT }
    finally { log("done"); 999 }   # 999 is discarded
};
# $v is either compute()'s value or $DEFAULT

Χρησιμοποιήστε την finally για εκκαθάριση που πρέπει οπωσδήποτε να συμβεί, όχι για τον υπολογισμό αποτελέσματος.

Κατάσταση χαρακτηριστικού#

Η finally είναι μέρος του χαρακτηριστικού try, αλλά σε αντίθεση με τα try και catch (που έπαψαν να είναι πειραματικά στην Perl 5.40) παραμένει πειραματική στην Perl 5.42. Η χρήση της χωρίς σίγαση της προειδοποίησης παράγει:

finally is experimental at ... line N.

Ενεργοποιήστε το χαρακτηριστικό και σιγάστε την προειδοποίηση μαζί:

use feature 'try';
no warnings 'experimental::try';

Επειδή η κατηγορία προειδοποίησης είναι experimental::try - η ίδια που εξέπεμπαν τα try/catch - η σίγασή της ενεργοποιεί την finally χωρίς να επανενεργοποιεί προειδοποιήσεις στα πλέον σταθερά τμήματα του χαρακτηριστικού για τα οποία θέλετε διαγνωστικά.

Πότε εκτελείται το μπλοκ#

Η σύμβαση είναι «κατά την έξοδο, με όποιον τρόπο και αν συμβεί». Συγκεκριμένα, η finally εκτελείται μετά από καθένα από τα ακόλουθα:

  • Φυσιολογική ολοκλήρωση του try. Το σώμα του try έφτασε στο τέλος του ή επέστρεψε την τιμή της τελευταίας του έκφρασης· εκτελείται έπειτα η finally, και μετά η εντολή μετά την κατασκευή try.

  • Εξαίρεση που πιάστηκε από το catch. Το σώμα του try εξήγειρε εξαίρεση, το catch ταίριαξε και τη χειρίστηκε· η finally εκτελείται μετά το σώμα του catch.

  • Εξαίρεση που δεν πιάστηκε. Δεν υπάρχει catch, ή το ίδιο το catch την επανεξήγειρε με die· η finally εκτελείται ούτως ή άλλως, και κατόπιν η εξαίρεση συνεχίζει να διαδίδεται προς την επόμενη δυναμική eval/try προς τα έξω.

  • return μέσα σε try ή catch. Η περικλείουσα υπορουτίνα πρόκειται να επιστρέψει· η finally εκτελείται, και μετά τίθεται σε ισχύ η επιστροφή.

  • Έλεγχος βρόχου (last, next, redo) μέσα σε try ή catch. Η finally εκτελείται, και μετά ο έλεγχος βρόχου τίθεται σε ισχύ στον περικλείοντα βρόχο.

  • goto &sub ή goto LABEL που εγκαταλείπει το try. Η finally εκτελείται πριν ολοκληρωθεί το άλμα.

Η σειρά έχει σημασία: αν το ίδιο το catch εγείρει νέα εξαίρεση, η finally εκτελείται ούτως ή άλλως, και η νέα εξαίρεση είναι αυτή που διαδίδεται (η αρχική αντικαθίσταται, ακριβώς όπως συμβαίνει με κάθε die μέσα σε χειριστή).

Περιορισμοί στο σώμα του μπλοκ#

Ένα μπλοκ finally είναι σημείο εκκαθάρισης, όχι ανακατεύθυνσης ροής ελέγχου. Η Perl απορρίπτει οποιαδήποτε εντολή ροής ελέγχου που θα προσπαθούσε να παρακάμψει τον τρόπο με τον οποίο η κατασκευή ξετυλίγεται ήδη:

  • Το return μέσα σε finally αποτελεί συντακτικό σφάλμα / σφάλμα μεταγλώττισης.

  • Το goto μέσα σε finally αποτελεί συντακτικό σφάλμα / σφάλμα μεταγλώττισης.

  • Οι έλεγχοι βρόχου (last, next, redo) που στοχεύουν βρόχο εκτός του μπλοκ finally απορρίπτονται.

Μέσα σε έναν απλό βρόχο που περιέχεται στο σώμα της finally, οι έλεγχοι βρόχου εφαρμόζονται σε εκείνον τον εσωτερικό βρόχο κανονικά - ο περιορισμός αφορά την έξοδο από το μπλοκ finally, όχι την επανάληψη στο εσωτερικό του.

Η die επιτρέπεται. Ένα μπλοκ finally που εγείρει εξαίρεση αντικαθιστά οποιαδήποτε εν εξελίξει εξαίρεση από το try/catch με τη νέα. Αυτό είναι συνήθως σφάλμα (χάνετε την αρχική αποτυχία), οπότε προστατεύστε κώδικα εκκαθάρισης που ίσως αποτύχει και ο ίδιος:

finally {
    eval { $fh->close };        # swallow close errors during cleanup
}

Σχέση με την defer#

Το finally BLOCK είναι «defer BLOCK τοποθετημένο στην αρχή του try, που όμως είναι προσαρτημένο στο ζεύγος try/catch». Και τα δύο εκτελούνται σε κάθε διαδρομή εξόδου από την περιέχουσα εμβέλειά τους· και τα δύο απαγορεύουν τις ίδιες εντολές ροής ελέγχου στο σώμα τους· και τα δύο απορρίπτουν την τιμή επιστροφής τους.

Η διαφορά βρίσκεται στην προσάρτηση εμβέλειας:

  • Η defer προσαρτάται στο εσώτερο περικλείον μπλοκ και ενεργοποιείται κατά την έξοδο εκείνου του μπλοκ. Αποτελεί το πρωτογενές στοιχείο εκκαθάρισης γενικής χρήσης και λειτουργεί σε οποιοδήποτε μπλοκ.

  • Η finally προσαρτάται στην κατασκευή try/catch και ενεργοποιείται όταν η κατασκευή ολοκληρώνεται. Είναι συντακτική ζάχαρη για τη συνηθισμένη περίπτωση όπου η εκκαθάριση αφορά συγκεκριμένα αυτό που έκανε το try.

# These two are behaviourally equivalent:

try {
    my $fh = acquire();
    defer { release($fh) }
    work($fh);
}
catch ($e) { warn $e }

try {
    my $fh = acquire();
    work($fh);
}
catch ($e) { warn $e }
finally { release_last() }      # if $fh were available here

Σημειώστε μια πρακτική συνέπεια: οι μεταβλητές που δηλώνονται μέσα στο σώμα του try εξέρχονται από την εμβέλεια προτού εκτελεστεί η finally. Αν η εκκαθάριση χρειάζεται έναν πόρο που έχει δεσμευτεί μέσα στο try, είτε δηλώστε τον σε εξωτερική εμβέλεια, είτε χρησιμοποιήστε defer μέσα στο try εκεί όπου η μεταβλητή είναι εν ζωή.

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

Πάντα κλείστε ένα handle, ακόμη και σε σφάλμα:

use feature 'try';
no warnings 'experimental::try';

open my $fh, '<', $path or die "open $path: $!";
try {
    process($fh);
}
catch ($e) {
    warn "processing failed: $e";
}
finally {
    close $fh;
}

Απελευθερώστε ένα κλείδωμα ανεξαρτήτως αποτελέσματος:

my $lock = $mutex->lock;
try {
    critical_section();
}
finally {
    $lock->release;
}

Παρατηρησιμότητα: καταγράψτε κάθε διαδρομή εξόδου:

try {
    run($job);
}
catch ($e) {
    $job->mark_failed($e);
    die $e;                     # re-throw; finally still runs
}
finally {
    metrics->tick('job.exit', $job->id);
}

try/catch που παράγει τιμή, με την finally αποκλειστικά για εκκαθάριση:

my $result = do {
    try    { fetch($url) }
    catch ($e) { $CACHED }
    finally { close_connection() }
};
# $result is fetch() or $CACHED; finally's value is never used

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

  • Το catch μπορεί να παραλειφθεί. Το try { ... } finally { ... } είναι νόμιμο και εκτελεί την finally τόσο σε φυσιολογική όσο και σε εξαιρετική έξοδο· η εξαίρεση στη συνέχεια διαδίδεται σαν να μην υπήρχε το try.

  • Εμφωλευμένα try/finally: τα μπλοκ finally ενεργοποιούνται από το εσώτερο προς το εξώτερο καθώς ξετυλίγονται οι εμβέλειες, σύμφωνα με τη σημασιολογία της defer.

  • Εξαίρεση μέσα στην finally: αντικαθιστά οποιαδήποτε εν εξελίξει εξαίρεση. Η αρχική χάνεται, εκτός αν την έχετε συλλάβει νωρίτερα στο catch.

  • Η $@ κατά τη διάρκεια της finally: δεν είναι αξιόπιστη για το «τι μόλις συνέβη». Αν η finally χρειάζεται να γνωρίζει αν εκτελείται μετά από επιτυχία ή αποτυχία, ορίστε μια σημαία στο μπλοκ catch και ελέγξτε την:

    my $failed;
    try   { risky() }
    catch ($e) { $failed = $e; die $e }
    finally {
        if ($failed) { rollback() } else { commit() }
    }
    
  • Καθολική καταστροφή: τα μπλοκ finally που έχουν καταχωριστεί από ένα try το οποίο είναι ακόμη ενεργό όταν ο διερμηνέας αρχίζει να αποδομείται εκτελούνται, αλλά ισχύουν οι ίδιες επιφυλάξεις με την die μέσα σε DESTROY - κρατήστε το σώμα αμυντικό.

  • Η συντακτική ανάλυση απαιτεί το χαρακτηριστικό: χωρίς use feature 'try', η λέξη finally δεν είναι δεσμευμένη λέξη και αναλύεται ως κλήση συνάρτησης τύπου bareword, που συνήθως παράγει ένα μπερδεμένο, άσχετο σφάλμα. Ενεργοποιείτε πάντα το χαρακτηριστικό πριν χρησιμοποιήσετε τη σύνταξη.

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

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

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

  • try - εισάγει το μπλοκ στην έξοδο του οποίου εγκαθίσταται η finally· δείτε Try Catch Exception Handling στο perlsyn

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

  • defer - το γενικής χρήσης μπλοκ «εκτέλεση κατά την έξοδο εμβέλειας»· η finally είναι defer προσαρτημένο σε ζεύγος try/catch

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

  • eval - ο παλαιότερος μηχανισμός σύλληψης εξαιρέσεων· τα try/catch/finally αντικαθιστούν το κοινό ιδίωμα eval { ... }; if ($@) με καθαρότερη εμβέλεια και σωστή σημασιολογία ροής ελέγχου