זרימת בקרה

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 רץ, ואז ה־return נכנס לתוקף.

  • בקרת לולאה (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 היכן שהמשתנה חי.

דוגמאות#

לסגור תמיד מטפל, גם בשגיאה:

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, מה שמפיק בדרך כלל שגיאה מבלבלת לא־קשורה. תמיד הפעילו את התכונה לפני שימוש בתחביר.

הבדלים מהמקור#

תאימות מלאה עם 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 ($@) עם תיחום נקי יותר וסמנטיקת זרימת־בקרה נכונה