دروس الوحدة (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 (NOT ==)، الذي يسير في سلسلة التغليف:
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)محمي_INLINE_26 للأخطاء المتعددة
منذ 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()لبرنامج مستقل (التكوين الهام المفقود → فشل سريع). - واجهة برمجة تطبيقات المكتبة التي توثقه بشكل صريح: على سبيل المثال،
regexp.MustCompile(الذعر إذا كان النمط غير صالح - مقبول لأن النمط هو ثابت وقت البناء).
كل شيء آخر هو محمي_INLINE_35. لا تستخدم panic كاختصار للانتشار لأعلى: فهو يكسر التركيب ويتخطى defer Close() ويربك المتصل.
تمارين
قم بتغطية خطأ os.ReadFile مع fmt. حدث خطأ باستخدام الفعل %w وإضافة المسابقة 'config:' (مع المسار).
الحل متاح بعد 3 من المحاولات
أخطاء الاستخدام. هل من أجل التحقق من وجود خطأ في سلسلة التغليف المقابلة لـ ErrNotFound وختم 'manca!' في تل كاسو.
الحل متاح بعد 3 من المحاولات
عندما يتم استخدام الذعر اصطلاحيًا في مكتبة Go؟
func F(x int) {
// ??? panic(...)
}