Panic, Defer ו-Recover
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 57 מתוך 107.
אף ש-Go מדגישה החזרת שגיאות, יש מצבים שאי אפשר באמת להתאושש מהם. למקרים כאלה, Go מספקת את panic, את defer ואת recover: מנגנון לטיפול בנסיבות חריגות.
panic עוצרת את הביצוע הרגיל באופן מיידי. היא מיועדת לשגיאות תכנות כמו גישה לאינדקס מחוץ לטווח, ולא לשגיאות צפויות כמו קלט משתמש לא תקין:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer מתזמן פונקציה להפעלה כשהפונקציה העוטפת מסתיימת, בין אם באופן רגיל ובין אם עקב panic. קריאות מושהות מתבצעות בסדר הפוך (אחרון נכנס, ראשון יוצא):
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// פלט: Opening file, Processing..., Closing file
}recover לוכדת panic ומחזירה את הביצוע למצב רגיל. היא פועלת רק בתוך פונקציה דחויה:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}התבנית הזאת ממירה מצב panic לשגיאה רגילה, וכך מאפשרת לתוכנית שלך להמשיך לפעול. השתמש בה במשורה: העדף להחזיר שגיאות במקרים צפויים של כשל, ושמור את panic/recover למצבים חריגים באמת, כמו מצב פגום או תנאים שאין מהם התאוששות.
אתגר
קלבואו נבנה מחשבון חילוק בטוח שמדגים כיצד להשתמש ב־panic, defer ו־recover כדי לטפל במצבים חריגים בצורה מסודרת. המחשבון ינסה לבצע פעולות מסוכנות וימיר מצבי panic לשגיאות רגילות שאפשר לטפל בהן כרגיל.
תארגנו את הקוד בשני קבצים:
calculator.go: צרו את לוגיקת החילוק עם התאוששות מ־panic.ממשו פונקציה בשם
Divideשמקבלת שני מספרים שלמים ומפעילה panic עם ההודעה"division by zero"אם המחלק הוא אפס. אחרת, היא מחזירה את תוצאת החילוק כמספר שלם.ממשו פונקציה בשם
SafeDivideשמקבלת שני מספרים שלמים ומחזירה גם תוצאה כמספר שלם וגם שגיאה. הפונקציה צריכה:- להשתמש ב־
deferעם פונקציה אנונימית כדי להתאושש מכל מצב panic - אם מתבצעת התאוששות מ־panic, להמיר אותו לשגיאה בפורמט
"calculation error: [panic message]" - לקרוא לפונקציה
Divideמתוך הפונקציה - להחזיר את התוצאה ואת
nilאם הפעולה הצליחה, או אפס ואת השגיאה אם התבצעה התאוששות מ־panic
- להשתמש ב־
main.go: קראו שני מספרים שלמים מהקלט והשתמשו ב־SafeDivideכדי לבצע את החישוב בבטחה. הדגימו גם אתdeferבאמצעות הדפסת הודעות ניקוי בסדר הנכון.הפונקציה הראשית שלכם צריכה:
- להדפיס
Starting calculationבתחילת הפעולה - להשתמש ב־
deferכדי לתזמן הדפסה שלCleanup complete - להשתמש ב־
deferנוסף כדי לתזמן הדפסה שלReleasing resources - לקרוא ל־
SafeDivideעם ערכי הקלט - להדפיס
Result: [value]אוError: [error message], בהתאם לתוצאה
- להדפיס
הקלטים הבאים יסופקו:
- שורה 1: המספר השלם הראשון (המחולק)
- שורה 2: המספר השלם השני (המחלק)
זכרו שקריאות שמבוצעות באמצעות defer מתבצעות בסדר הפוך (האחרונה שנכנסה היא הראשונה שיוצאת), ולכן הודעות הניקוי צריכות להופיע בסדר הפוך לסדר שבו תוזמנו.
לדוגמה, עבור 20 ו־4, הפלט שלכם צריך להיות:
Starting calculation
Result: 5
Releasing resources
Cleanup completeועבור 10 ו־0, הפלט שלכם צריך להיות:
Starting calculation
Error: calculation error: division by zero
Releasing resources
Cleanup completeנסו בעצמכם
package main
import (
"fmt"
)
func main() {
// קרא את הקלט
var dividend, divisor int
fmt.Scanln(÷nd)
fmt.Scanln(&divisor)
// הדפס את הודעת הפתיחה
fmt.Println("Starting calculation")
// TODO: השתמש ב-defer כדי לתזמן את ההודעה "Cleanup complete"
// TODO: השתמש ב-defer כדי לתזמן את ההודעה "Releasing resources"
// זכור: קריאות שנדחו מתבצעות בסדר LIFO (אחרון נכנס, ראשון יוצא)
// TODO: קרא ל-SafeDivide עם ערכי הקלט
// TODO: הדפס את "Result: [value]" או את "Error: [error message]"
// בהתאם לשאלה אם הוחזרה שגיאה
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP ב-Go
קבצים חיצונייםסביבת העבודה והמודולים של Goחבילות וייבואשמות מיוצאים לעומת שמות לא מיוצאיםמבוא ל-OOP ב-GoStructs כמחלקותהגדרת מתודות עבור Structsמקבלי Pointer לעומת Valueאתחול Structפונקציות Constructorחזרה – מחשבון פשוט4ממשקים
מבוא לממשקיםמימוש משתמעממשק כחוזהממשק ריק (any)אימות טיפוסמתג טיפוסיםהרכבת ממשקיםממשקי Stringer ו-Errorסיכום – מחשבון צורות7כימוס
שדות מיוצאים לעומת שדות לא מיוצאיםכימוס ברמת החבילהמתודות Getter ו-Setterהסתרת מידע ב-Goסיכום – רשומות תלמידים10Generics (Go 1.18 ומעלה)
מבוא ל־Genericsפרמטרים של טיפוסיםאילוצי טיפוסיםמבנים גנרייםפתרון עוקף למתודות גנריותסיכום – אוסף גנרי2העמקה בסוגים וב-Structs
סוגים בסיסיים ומורכביםהגדרות של סוגים מותאמים אישיתתגיות StructStructs אנונימייםStructs מקונניםערכי אפס וברירות מחדלסיכום – ספר אנשי קשר5קומפוזיציה במקום ירושה
למה ב-Go אין ירושהיסודות הטמעת Structקידום מתודותהטמעת כמה Structsהטמעה לעומת אגרגציההסתרת מתודות מוטמעותסיכום – היררכיית עובדים8טיפול בשגיאות ו-OOP
ממשק errorסוגי שגיאות מותאמים אישיתעטיפת שגיאות (fmt.Errorf)שגיאות Sentinelerrors.Is() ו-errors.As()Panic, Defer ו-Recoverסיכום – מנתח קבצים11ספרייה סטנדרטית ותכנות מונחה עצמים
io.Reader ו-io.Writersort.Interfaceהממשק fmt.Stringerencoding/json עם מבניםהממשק http.Handlerסיכום – מודלים של REST API3מצביעים וזיכרון
יסודות המצביעים ב-Goמצביעים למבניםהעברה לפי ערך לעומת לפי הפניההפונקציה new()איסוף אשפה ב-Goסיכום - בונה רשימה מקושרת6פולימורפיזם ב-Go
פולימורפיזם באמצעות ממשקיםטיפוס ברווז ב-Goכללי התאמה לממשקאוספים פולימורפייםהזרקת תלויותסיכום - מעבד תשלומים9מקביליות ו-OOP
יסודות GoroutinesChannels ותקשורתChannel עם חוצץ לעומת ללא חוצץפקודת Selectsync.Mutex ו-sync.RWMutexsync.WaitGroupתכנון Struct בטוח לשימוש במקבילסיכום - מאגר עובדים12מושגים מתקדמים בתכנות מונחה עצמים
דפוס האפשרויות הפונקציונליותדפוס Builder ב-Goשרשור מתודותכינויי טיפוסים לעומת הגדרותיסודות Reflectionסקירה כללית של יצירת קודתרגלו בעצמכם: קומפיילר Go אונליין