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() عندما تحتاج إلى الوصول إلى حقول من نوع خطأ مخصص. كلتا الدالتين تتنقلان عبر سلسلة الأخطاء بالكامل، مما يجعلهما ضروريتين للتعامل مع الأخطاء المغلفة بشكل صحيح.
التحدي
سهللنقم ببناء نظام معالجة ملفات يوضح كيفية فحص سلاسل الأخطاء المغلفة (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
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP في Go
الملفات الخارجيةمساحة العمل والـ Modules في Goالـ Packages والـ Importsالأسماء المصدرة مقابل غير المصدرةمقدمة إلى الـ OOP في Goالـ Structs كـ Classesتعريف الـ Methods في الـ Structsالـ Pointer Receivers مقابل الـ Value Receiversتهيئة الـ Structدوال الـ Constructorمراجعة - آلة حاسبة بسيطة4الواجهات
مقدمة في الواجهاتالتنفيذ الضمنيالواجهة كعقدالواجهة الفارغة (any)تأكيد النوع (Type Assertion)تبديل النوع (Type Switch)تركيب الواجهاتواجهات Stringer و Errorمراجعة - حاسبة الأشكال7التغليف
الحقول المصدرة وغير المصدرةالتغليف على مستوى الحزمةدوال الـ Getter والـ Setterإخفاء المعلومات في Goملخص - سجلات الطلاب10الأنواع العامة (Go 1.18+)
مقدمة في الأنواع العامةمعاملات الأنواعقيود الأنواعالـ Structs العامةحل بديل للـ Methods العامةملخص - المجموعات العامة13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Abstract Factoryنمط Observerنمط Strategy2تعمق في الأنواع و Structs
الأنواع الأساسية والمركبةتعريفات الأنواع المخصصةStruct TagsStructs مجهولةStructs متداخلةالقيم الصفرية والافتراضيةمراجعة - دفتر العناوين5التركيب بدلاً من الوراثة
لماذا لا تدعم Go الوراثةأساسيات تضمين الـ Structترقية الـ Methodتضمين عدة Structsالتضمين مقابل التجميعحجب الـ Methods المضمنةملخص - الهيكل الهرمي للموظفين8معالجة الأخطاء و OOP
واجهة errorأنواع الأخطاء المخصصةتغليف الأخطاء (fmt.Errorf)أخطاء Sentinelerrors.Is() و errors.As()Panic و Defer و Recoverملخص - File Parser11المكتبة القياسية والبرمجة كائنية التوجه (OOP)
io.Reader و io.Writerواجهة sort.Interfaceواجهة fmt.Stringerencoding/json مع Structsواجهة http.Handlerمراجعة - نماذج REST API14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبMiddleware كنمط مزين3المؤشرات والذاكرة
أساسيات المؤشرات في Goالمؤشرات إلى الـ Structsالتمرير بالقيمة مقابل التمرير بالمرجعدالة ()newGarbage Collection في Goملخص - بناء Linked List6تعدد الأشكال في Go
تعدد الأشكال عبر InterfacesDuck Typing في Goقواعد استيفاء الـ Interfaceمجموعات متعددة الأشكالحقن التبعيةمراجعة - معالج الدفع9التزامن والبرمجة كائنية التوجه (OOP)
أساسيات الـ Goroutinesالقنوات (Channels) والاتصالالقنوات المخزنة (Buffered) مقابل غير المخزنةجملة Selectsync.Mutex و sync.RWMutexsync.WaitGroupتصميم الـ Structs الآمنة للخيوط (Thread-Safe)مراجعة - Worker Pool