שיעורי מודול (2/5)
טיפול בשגיאות אידיומטי
טיפול בשגיאות ב-Go הוא מפורש: אין חריגים, אין ניסיון/תפוס. כל פונקציה שיכולה להיכשל מחזירה את error בתור הערך האחרון שלה, והמתקשר מחליט. פרק זה אוסף את הדפוסים האידיומטים כדי לייצר, להפיץ ולבדוק שגיאות בצורה חזקה.
כלל הזהב
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("config %s: %w", path, err)
}ארבעה עקרונות:
errorהיא ההחזר האחרון.- בדוק מיד עם
if err != nil. - הוסף הקשר לפני ההפצה (עם
fmt.Errorf+%w). - לעולם אל תתעלם משגיאה: או לטפל בה, לרשום אותה או להחזיר אותה.
_ = file.Close() // EXPLICITLY ignored (you know what you're doing)שגיאות זקיף
שגיאת זקיף היא משתנה גלובלי מיוצא var ErrXxx = errors.New("...") שהמתקשר יכול להשוות מולו:
package store
var ErrNotFound = errors.New("not found")
func Get(key string) (string, error) {
if !exists(key) {
return "", ErrNotFound
}
// ...
}המתקשר משתמש ב-errors.Is (לא ==), אשר הולך בשרשרת העוטפת:
v, err := store.Get(k)
if errors.Is(err, store.ErrNotFound) {
// handle 404
}דוגמאות סטנדרטיות: io.EOF, sql.ErrNoRows, os.ErrNotExist.
שגיאות הקלדה מותאמת אישית
כדי לשאת נתונים מובנים (קוד HTTP, שדה לא חוקי וכו'), הגדר סוג:
type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}המתקשר משתמש ב-errors.As כדי להוריד בבטחה לאורך השרשרת:
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println("invalid field:", ve.Field)
}errors.As הולך בשרשרת Unwrap() עד שהוא מוצא שגיאה שניתן להקצות למצביע שהועבר פנימה.
גלישה עם %w
fmt.Errorf עם הפועל %w מייצר שגיאה שעוטפת את המקורית, ומשמרת אותה עבור errors.Is/errors.As:
if err := db.Query(); err != nil {
return fmt.Errorf("load users: %w", err)
}ללא %w (באמצעות %v או %s) אתה מקבל מחרוזת עשירה יותר אבל מאבד את השרשרת: המתקשר כבר לא יכול לזהות שגיאות ספציפיות.
כללים עבור %w:
- רק אחת לכל שיחה
fmt.Errorf(ריבוי גלישה דורשerrors.Join). - הארגומנט חייב להיות
error, לא מחרוזת. - הוסף הקשר, אל תשכפל את ההודעה המקורית.
// bad: duplicates
return fmt.Errorf("error: %w", err) // "error: file not found"
// good: useful context
return fmt.Errorf("load config %s: %w", path, err)errors.Join עבור שגיאות מרובות
מאז Go 1.20, errors.Join(errs...) משלב שגיאות מרובות לאחת ששומרת את כולן ניתנות לזיהוי על ידי errors.Is/As:
var errs []error
for _, item := range items {
if err := process(item); err != nil {
errs = append(errs, fmt.Errorf("item %s: %w", item.ID, err))
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}מתי להשתמש ב-panic
לעולם לא לטעויות צפויות. רק בשלושה מקרים:
- באג שלא ניתן לשחזר: אי משתנה שהופר במקום ש"לא יכול לקרות" (
panic("unreachable")). - אתחול נכשל ב-
init()/main()של תוכנית עצמאית (חסרה תצורה קריטית → כשל מהיר). - ממשק API של ספרייה שמתעד זאת במפורש: למשל,
regexp.MustCompile(נכנס לפאניקה אם התבנית לא חוקית - מקובל כי התבנית היא קבועה של זמן בנייה).
כל השאר error. אל תשתמש ב-panic בתור קיצור דרך להפצה: זה שובר חיבור, מדלג על defer Close() ומציף את המתקשר.
תרגילים
Wrappa l'errore di os.ReadFile con fmt.Errorf usando il verbo %w e aggiungendo il contesto 'config:' (con il path).
הפתרון זמין לאחר 3 ניסיונות
שגיאות ארה"ב.Is per verificare se err nella catena di wrap corrisponde alla sentinella ErrNotFound e stampa 'manca!' בטל קאסו.
הפתרון זמין לאחר 3 ניסיונות
האם יש פאניקה ב-una libreria Go?
func F(x int) {
// ??? panic(...)
}