שלושה סוגי שגיאות
קוד Go משתמש בשלוש צורות של שגיאות, מהפשוטה לעשירה:
- שגיאה אד הוק:
errors.New("...")אוfmt.Errorf("...")שנוצרת במקום. הקוראים יכולים רק לקרוא את ההודעה. - שגיאת sentinel: משתנה ברמת החבילה כמו
io.EOF. הקוראים יכולים לבדוק אותה עםerrors.Is. - טיפוס שגיאה: struct עם מתודת
Error(), שנושא שדות. הקוראים יכולים לחלץ אותו עםerrors.Asולקרוא את השדות.
בחרו את הפשוטה ביותר שמאפשרת לקוראים לעשות את מה שהם צריכים.
שגיאות sentinel
sentinel הוא ערך שגיאה שמוצהר פעם אחת ומושווה לפי זהות. לפי המוסכמה השמות מתחילים ב-Err.
פלט:
mug bought
cap is out of stock, notify me later
buy "hat": not found
שתי קריאות ל-errors.New עם אותו טקסט יוצרות שגיאות שונות: errors.New("x") == errors.New("x") הוא false. לכן sentinel חייב להיות משתנה משותף יחיד.
sentinels הופכים לחלק מה-API של החבילה שלכם. ברגע שקוראים בודקים את ErrNotFound, אי אפשר להפסיק להחזיר אותה בלי לשבור אותם. ייצאו רק את אלה שהקוראים באמת צריכים כדי להסתעף לפיהם.
טיפוסי שגיאה מותאמים
כשמי שקורא צריך פרטים (איזה שדה, איזה קוד סטטוס, כמה זמן לחכות לפני ניסיון חוזר), הגדירו טיפוס.
פלט:
register: age: must be between 0 and 150
bad field: age
למתודה יש receiver מסוג מצביע והפונקציה מחזירה &ValidationError{...}, ולכן היעד של errors.As הוא *ValidationError, ומעבירים את הכתובת שלו (&ve, מטיפוס **ValidationError). טעות ברמת ההפניה הזאת היא הטעות הקלאסית עם errors.As: עם receiver מסוג ערך הייתם מחזירים ValidationError{...} ומצהירים var ve ValidationError. go vet תופס את הפספוס הנפוץ ביותר, העברת ve במקום &ve: second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type.
עטיפה עם %w
fmt.Errorf עם %w מחזירה שגיאה שזוכרת את זו שהיא עוטפת. כל שכבה מוסיפה הקשר ושומרת על השרשרת שלמה.
פלט:
start: connect db: timeout
*fmt.wrapError: start: connect db: timeout
*fmt.wrapError: connect db: timeout
*errors.errorString: timeout
true
false
עם %v ההודעה זהה אבל השרשרת נחתכת, ולכן errors.Is מחזירה false. בחרו ב-%w כשהקוראים צריכים לראות את הסיבה, וב-%v כשהסיבה היא פרט מימוש שאתם לא רוצים שיסתמכו עליו.
החל מ-Go 1.20, קריאה אחת יכולה לעטוף כמה שגיאות: fmt.Errorf("%w; %w", err1, err2). errors.Is מתאימה אז לכל אחת מהן.
errors.Join: כמה שגיאות בבת אחת
ולידציה וניקוי מייצרים לעיתים קרובות יותר משגיאה אחת. errors.Join (Go 1.20) מאגדת אותן.
פלט:
name: required
email: required
age: negative
---
true
true
errors.Join מחזירה nil כשכל הארגומנטים הם nil, ולכן הפונקציה שלמעלה לא צריכה טיפול מיוחד במקרה של "אין שגיאות".
המתודות Unwrap ו-Is
errors.Is ו-errors.As מוצאות שגיאות עטופות על ידי קריאה למתודת Unwrap. טיפוס מותאם שמחזיק סיבה צריך לחשוף אותה:
טיפוס שעוטף כמה שגיאות מממש במקום זאת את Unwrap() []error.
טיפוס יכול גם להגדיר Is(target error) bool כדי להחליט בעצמו על שוויון, למשל כדי להתאים לכל *HTTPError עם אותו קוד סטטוס. לעיתים רחוקות צריך את זה; עטיפת sentinel והחזרתו מ-Unwrap בדרך כלל עושות את העבודה.
מלכודת ה-nil עם טיפוס
אף פעם אל תצהירו על משתנה שגיאה עם הטיפוס הקונקרטי שלכם ותחזירו אותו דרך error:
func check() error {
var err *ValidationError // nil pointer
// ... no problem found
return err // non-nil error! Its type is *ValidationError
}
ה-if err != nil של מי שקרא יהיה true, כי interface שמחזיק מצביע nil עם טיפוס אינו nil. החזירו nil מפורש במקרה של הצלחה, והשאירו משתני שגיאה מקומיים מטיפוס error. הדף על interfaces מסביר למה.
איזה סוג לבחור
| הקוראים צריכים | ספקו |
|---|---|
| רק לתעד או להציג את הכישלון | fmt.Errorf("...: %w", err) |
| להסתעף לפי מצב מסוים אחד | sentinel var ErrX = errors.New(...) |
| לקרוא פרטים על הכישלון | טיפוס שגיאה עם שדות |
| לראות כמה כישלונות בלתי תלויים | errors.Join |
טעויות נפוצות
- השוואת שגיאות עטופות עם
==. השתמשו ב-errors.Is. - העברת ערך שאינו מצביע ל-
errors.As. היא צריכה מצביע למשתנה מטיפוס היעד. - יצירת "sentinel" בתוך פונקציה.
return errors.New("not found")יוצר ערך חדש בכל קריאה; הקוראים לא יכולים להשוות מולו. - ייצוא כל שגיאה. כל sentinel או טיפוס מיוצא הוא הבטחה של API.
- התאמה לפי
err.Error(). מחרוזות נועדו לבני אדם.
שאלות נפוצות
איך יוצרים שגיאה מותאמת אישית ב-Go?
למצב קבוע, הצהירו על sentinel ברמת החבילה: var ErrNotFound = errors.New("not found"). לשגיאה שנושאת נתונים, הגדירו טיפוס עם מתודת Error() string: type ValidationError struct { Field string } ו-func (e *ValidationError) Error() string { return e.Field + " is invalid" }.
איך עוטפים שגיאה ב-Go?
השתמשו ב-fmt.Errorf עם ה-verb %w: return fmt.Errorf("load user %d: %w", id, err). ההודעה של השגיאה החדשה כוללת את הישנה, ו-errors.Unwrap, errors.Is ו-errors.As יכולות להגיע למקורית. החל מ-Go 1.20 קריאה אחת ל-Errorf יכולה לעטוף כמה שגיאות עם כמה verbs של %w.
מה ההבדל בין errors.Is ל-errors.As?
errors.Is(err, target) עונה על השאלה "האם ערך השגיאה המסוים הזה נמצא איפשהו בשרשרת", עבור sentinels כמו io.EOF. errors.As(err, &target) עונה על השאלה "האם יש בשרשרת שגיאה מהטיפוס הזה", ואם כן, שומרת אותה ב-target כדי שתוכלו לקרוא את השדות שלה.
מה עושה errors.Join?
errors.Join(errs...) (Go 1.20) משלבת כמה שגיאות לשגיאה אחת. ההודעה שלה היא ההודעות הנפרדות מופרדות בשורות חדשות, שגיאות nil מושמטות, והיא מחזירה nil אם כולן nil. errors.Is ו-errors.As מתאימות אם אחת מהשגיאות המשולבות מתאימה.