الانتقال إلى المحتوى الرئيسي
الوحدة 10 · الدرس 2 من 547/50 في الدورة~14 min
دروس الوحدة (2/5)

معالجة الأخطاء الاصطلاحية

معالجة الأخطاء في Go واضحة: لا توجد استثناءات، ولا توجد محاولة/التقاط. كل دالة يمكن أن تفشل ترجع error باعتبارها القيمة الأخيرة، ويقرر المتصل ذلك. يجمع هذا الفصل الأنماط الاصطلاحية لإنتاج الأخطاء ونشرها وفحصها بطريقة قوية.

القاعدة الذهبية

Go
data, err := os.ReadFile(path)
if err != nil {
    return fmt.Errorf("config %s: %w", path, err)
}

أربعة مبادئ:

  1. error هو الإرجاع الأخير.
  2. تحقق على الفور باستخدام if err != nil.
  3. أضف سياقًا قبل النشر (باستخدام fmt.Errorf + %w).
  4. لا تتجاهل أبدًا الخطأ: إما التعامل معه أو تسجيله أو إعادته.
Go
_ = file.Close() // EXPLICITLY ignored (you know what you're doing)

أخطاء الحارس

الخطأ الحارس هو متغير عام مُصدَّر var ErrXxx = errors.New("...") يمكن للمتصل مقارنته بما يلي:

Go
package store

var ErrNotFound = errors.New("not found")

func Get(key string) (string, error) {
    if !exists(key) {
        return "", ErrNotFound
    }
    // ...
}

يستخدم المتصل errors.Is (NOT ==)، الذي يسير في سلسلة التغليف:

Go
v, err := store.Get(k)
if errors.Is(err, store.ErrNotFound) {
    // handle 404
}

الأمثلة القياسية: io.EOF، sql.ErrNoRows، os.ErrNotExist.

أخطاء كتابية مخصصة

لنقل البيانات المنظمة (رمز HTTP، حقل غير صالح، وما إلى ذلك)، حدد النوع:

Go
type ValidationError struct {
    Field string
    Msg   string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}

يستخدم المتصل errors.As للبث بأمان على طول السلسلة:

Go
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:

Go
if err := db.Query(); err != nil {
    return fmt.Errorf("load users: %w", err)
}

بدون %w (باستخدام %v أو %s) ستحصل على سلسلة أكثر ثراءً ولكن تفقد السلسلة: لم يعد بإمكان المتصل التعرف على أخطاء محددة.

قواعد %w:

  • مكالمة واحدة فقط لكل fmt.Errorf مكالمة (يتطلب التغليف المتعدد errors.Join).
  • يجب أن تكون الوسيطة error، وليست سلسلة.
  • أضف السياق، لا تكرر الرسالة الأصلية.
Go
// bad: duplicates
return fmt.Errorf("error: %w", err) // "error: file not found"

// good: useful context
return fmt.Errorf("load config %s: %w", path, err)

محمي_INLINE_26 للأخطاء المتعددة

منذ Go 1.20، يجمع errors.Join(errs...) أخطاء متعددة في خطأ واحد يبقيها كلها قابلة للاكتشاف بواسطة errors.Is/As:

Go
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

أبدا للأخطاء المتوقعة. فقط في ثلاث حالات:

  1. خطأ غير قابل للاسترداد: تم انتهاك ثابت في مكان "لا يمكن أن يحدث" (panic("unreachable")).
  2. فشلت التهيئة في init()/main() لبرنامج مستقل (التكوين الهام المفقود → فشل سريع).
  3. واجهة برمجة تطبيقات المكتبة التي توثقه بشكل صريح: على سبيل المثال، regexp.MustCompile (الذعر إذا كان النمط غير صالح - مقبول لأن النمط هو ثابت وقت البناء).

كل شيء آخر هو محمي_INLINE_35. لا تستخدم panic كاختصار للانتشار لأعلى: فهو يكسر التركيب ويتخطى defer Close() ويربك المتصل.

تمارين

تمرين#go.m10.l2.e1
المحاولات: 0جارٍ التحميل…

قم بتغطية خطأ os.ReadFile مع fmt. حدث خطأ باستخدام الفعل %w وإضافة المسابقة 'config:' (مع المسار).

جارٍ تحميل المحرر…

الحل متاح بعد 3 من المحاولات

تمرين#go.m10.l2.e2
المحاولات: 0جارٍ التحميل…

أخطاء الاستخدام. هل من أجل التحقق من وجود خطأ في سلسلة التغليف المقابلة لـ ErrNotFound وختم 'manca!' في تل كاسو.

جارٍ تحميل المحرر…

الحل متاح بعد 3 من المحاولات

Quiz#go.m10.l2.e3
جاهز

عندما يتم استخدام الذعر اصطلاحيًا في مكتبة Go؟

Go
func F(x int) {
  // ??? panic(...)
}
خيارات الإجابة