Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

errors.Is() و errors.As()

جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 56 من 107.

عندما يتم تغليف الأخطاء، فإن فحوصات المساواة البسيطة مثل err == ErrNotFound لم تعد تعمل. توفر حزمة errors وظيفتين لفحص سلاسل الأخطاء المغلفة: errors.Is() و errors.As().

يتحقق errors.Is() مما إذا كان أي خطأ في السلسلة يطابق خطأً مرجعياً (sentinel error) محدداً:

import "errors"

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

func FindUser(id int) error {
    return fmt.Errorf("database lookup failed: %w", ErrNotFound)
}

err := FindUser(42)
if errors.Is(err, ErrNotFound) {
    fmt.Println("User doesn't exist")
}

على الرغم من أن الخطأ المُرجع مغلف، فإن errors.Is() يفك تغليف السلسلة ويجد ErrNotFound بالداخل.

تتحقق errors.As() مما إذا كان أي خطأ في السلسلة يطابق نوعاً معيناً وتقوم باستخراجه:

type ValidationError struct {
    Field string
}

func (e *ValidationError) Error() string {
    return "validation failed: " + e.Field
}

err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})

var ve *ValidationError
if errors.As(err, &ve) {
    fmt.Println("Invalid field:", ve.Field)
}

استخدم errors.Is() عند التحقق من أخطاء الحراسة (sentinel errors)، و errors.As() عندما تحتاج إلى الوصول إلى حقول من نوع خطأ مخصص. كلتا الدالتين تتنقلان عبر سلسلة الأخطاء بالكامل، مما يجعلهما ضروريتين للتعامل مع الأخطاء المغلفة بشكل صحيح.

challenge icon

التحدي

سهل

لنقم ببناء نظام معالجة ملفات يوضح كيفية فحص سلاسل الأخطاء المغلفة (wrapped error chains) بشكل صحيح باستخدام errors.Is() و errors.As(). ستقوم بإنشاء أخطاء يتم تغليفها أثناء مرورها عبر طبقات مختلفة، ثم تستخدم هذه الدوال لتحديد واستخراج المعلومات منها.

ستقوم بتنظيم الكود الخاص بك عبر ملفين:

  • errors.go: تحديد البنية التحتية للأخطاء لنظام الملفات الخاص بك.

    قم بإنشاء خطأ مرجعي (sentinel error):

    • ErrPermissionDenied مع الرسالة "permission denied"

    قم بإنشاء نوع خطأ مخصص:

    • هيكل FileError مع حقول Filename (string) و Operation (string)
    • يجب أن تعيد دالة Error() الخاصة به: [Operation] failed on file: [Filename]

    قم بتنفيذ دالتين تحاكيان عمليات الملفات وتغلفان الأخطاء:

    • ReadFile(filename string) error - إذا كان اسم الملف هو "secret.txt"، فقم بإرجاع ErrPermissionDenied. إذا كان اسم الملف هو "missing.txt"، فقم بإرجاع *FileError مع العملية "read" واسم الملف المعطى. خلاف ذلك، قم بإرجاع nil.
    • ProcessFile(filename string) error - تستدعي ReadFile. إذا تم إرجاع خطأ، فقم بتغليفه باستخدام fmt.Errorf بالتنسيق "processing failed: %w". خلاف ذلك، قم بإرجاع nil.
  • main.go: قراءة اسم ملف من المدخلات واستدعاء ProcessFile. استخدم errors.Is() و errors.As() لفحص سلسلة الأخطاء المغلفة وطباعة الرسائل المناسبة بناءً على ما تجده بالداخل.

سيتم توفير المدخلات التالية:

  • السطر 1: اسم الملف المراد معالجته

تعامل مع النتيجة عن طريق فحص سلسلة الأخطاء:

  • إذا وجدت errors.Is() الخطأ ErrPermissionDenied في السلسلة: اطبع رسالة الخطأ الكاملة، ثم في سطر جديد اطبع Access denied - check file permissions
  • إذا وجدت errors.As() الخطأ *FileError في السلسلة: اطبع رسالة الخطأ الكاملة، ثم في سطر جديد اطبع File issue: [Filename] during [Operation]
  • إذا لم يحدث خطأ: اطبع File '[filename]' processed successfully

على سبيل المثال، عند إدخال secret.txt، يجب أن يكون مخرجك:

processing failed: permission denied
Access denied - check file permissions

وعند إدخال missing.txt، يجب أن يكون مخرجك:

processing failed: read failed on file: missing.txt
File issue: missing.txt during read

وعند إدخال data.txt، يجب أن يكون مخرجك:

File 'data.txt' processed successfully

لاحظ كيف أنه على الرغم من تغليف الأخطاء بواسطة ProcessFile، لا يزال بإمكانك اكتشاف الخطأ المرجعي الأصلي باستخدام errors.Is() واستخراج نوع الخطأ المخصص باستخدام errors.As(). تقوم هذه الدوال بعبور سلسلة الأخطاء بالكامل للعثور على ما تبحث عنه.

ورقة مرجعية

عندما يتم تغليف الأخطاء (wrapped)، استخدم errors.Is() و errors.As() من حزمة errors لفحص سلسلة الأخطاء.

تتحقق errors.Is() مما إذا كان أي خطأ في السلسلة يطابق خطأً مرجعياً (sentinel error):

import "errors"

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

err := fmt.Errorf("database lookup failed: %w", ErrNotFound)
if errors.Is(err, ErrNotFound) {
    // تم العثور على ErrNotFound في السلسلة
}

تتحقق errors.As() مما إذا كان أي خطأ في السلسلة يطابق نوعاً معيناً وتقوم باستخراجه:

type ValidationError struct {
    Field string
}

func (e *ValidationError) Error() string {
    return "validation failed: " + e.Field
}

err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})

var ve *ValidationError
if errors.As(err, &ve) {
    // يحتوي ve الآن على ValidationError من السلسلة
    fmt.Println(ve.Field)
}

استخدم errors.Is() للأخطاء المرجعية (sentinel errors) و errors.As() عندما تحتاج إلى الوصول إلى حقول من أنواع أخطاء مخصصة. كلتا الدالتين تقومان بفحص سلسلة الأخطاء بالكامل.

جرّب بنفسك

package main

import (
	"errors"
	"fmt"
)

func main() {
	// قراءة اسم الملف من المدخلات
	var filename string
	fmt.Scanln(&filename)

	// استدعاء ProcessFile مع اسم الملف
	err := ProcessFile(filename)

	// TODO: التعامل مع النتيجة عن طريق فحص سلسلة الأخطاء
	// 
	// استخدم errors.Is() للتحقق مما إذا كان ErrPermissionDenied موجوداً في السلسلة:
	// - في حال العثور عليه: اطبع الخطأ كاملاً، ثم "Access denied - check file permissions"
	//
	// استخدم errors.As() للتحقق مما إذا كان *FileError موجوداً في السلسلة:
	// - في حال العثور عليه: اطبع الخطأ كاملاً، ثم "File issue: [Filename] during [Operation]"
	//
	// في حال عدم وجود خطأ: اطبع "File '[filename]' processed successfully"

	_ = errors.Is // تلميح: استخدم errors.Is()
	_ = errors.As // تلميح: استخدم errors.As()
	_ = err
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming