فك التغليف باستخدام `errors.Is`
جزء من قسم Logic & Flow في رحلة GO على Coddy — الدرس 40 من 68.
عند العمل مع الأخطاء المغلفة (wrapped errors)، فأنت بحاجة إلى طريقة للتحقق مما إذا كان هناك خطأ معين موجوداً في أي مكان ضمن سلسلة الأخطاء. توفر الدالة errors.Is هذه الإمكانية تماماً، مما يسمح لك بتحديد أخطاء الحراسة (sentinel errors) حتى عندما يتم تغليفها عدة مرات.
تأخذ الدالة errors.Is معاملين: الخطأ الذي تريد فحصه والخطأ المستهدف الذي تبحث عنه. وتعيد القيمة true إذا تم العثور على الخطأ المستهدف في أي مكان ضمن سلسلة الأخطاء:
var ErrNotFound = errors.New("not found")
wrappedErr := fmt.Errorf("failed to load user: %w", ErrNotFound)
if errors.Is(wrappedErr, ErrNotFound) {
fmt.Println("This is a not found error")
}هذه هي الطريقة الحديثة والموصى بها للتحقق من أخطاء معينة في Go. على عكس مقارنة النصوص أو فحوصات المساواة المباشرة، تعمل errors.Is بشكل صحيح مع الأخطاء المغلفة (wrapped errors) وتتبع سلسلة الأخطاء للعثور على التطابقات. وهذا يجعل معالجة الأخطاء لديك قوية وموثوقة، حتى مع مرور الأخطاء عبر طبقات متعددة من تطبيقك.
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
التحدي
سهلقم ببناء نظام مراقبة لاتصال قاعدة البيانات يستخدم errors.Is لتحديد أخطاء اتصال محددة في سلاسل الأخطاء المغلفة (wrapped error chains). يوضح هذا التحدي كيفية استخدام errors.Is لاكتشاف أخطاء الحراسة (sentinel errors) حتى عندما يتم تغليفها بسياق إضافي من خلال طبقات تطبيقات متعددة.
ستتلقى مدخلين:
- سلسلة نصية تحتوي على تفاصيل عملية قاعدة البيانات بالتنسيق
"database_name,operation,error_type"(على سبيل المثال،"users_db,connect,connection_timeout") - سلسلة نصية تحتوي على سياق التطبيق بالتنسيق
"service_name,retry_count"(على سبيل المثال،"AuthService,3")
مهمتك هي:
- تعريف ثلاثة أخطاء حراسة (sentinel errors) باستخدام
errors.New():ErrConnectionTimeoutمع الرسالة"connection timeout"ErrConnectionRefusedمع الرسالة"connection refused"ErrDatabaseNotFoundمع الرسالة"database not found"
- تحليل المدخل الأول عن طريق التقسيم عند الفواصل للحصول على اسم قاعدة البيانات، والعملية، ونوع الخطأ
- تحليل المدخل الثاني عن طريق التقسيم عند الفواصل للحصول على اسم الخدمة وعدد محاولات إعادة المحاولة
- إنشاء الخطأ الأصلي بناءً على نوع الخطأ:
- إذا كان نوع الخطأ هو
"connection_timeout": استخدمErrConnectionTimeout - إذا كان نوع الخطأ هو
"connection_refused": استخدمErrConnectionRefused - إذا كان نوع الخطأ هو
"database_not_found": استخدمErrDatabaseNotFound - لأي نوع خطأ آخر: استخدم
errors.New("unknown database error")
- إذا كان نوع الخطأ هو
- إنشاء خطأ مغلف على مستوى قاعدة البيانات باستخدام
fmt.Errorfمع%w:"failed to [operation] database [database_name]: %w" - إنشاء خطأ مغلف على مستوى الخدمة:
"[service_name] database operation failed after [retry_count] retries: %w" - استخدام
errors.Isللتحقق مما إذا كان الخطأ المغلف النهائي يطابق كل خطأ حراسة وعرض النتائج:"Checking for connection timeout: [true/false]""Checking for connection refused: [true/false]""Checking for database not found: [true/false]"
- عرض سلسلة الأخطاء (error chain):
"Error Chain:""Original: [original_error_message]""Database level: [database_level_error_message]""Service level: [service_level_error_message]"
- عرض تحليل الخطأ:
"Error Analysis:""Database: [database_name]""Operation: [operation]""Service: [service_name]""Retries: [retry_count]""Error type detected: [detected_error_type]"حيث يكون detected_error_type هو واحد من "Connection Timeout" أو "Connection Refused" أو "Database Not Found" أو "Unknown Error"
استخدم حزمة strings لتقسيم سلاسل المدخلات عند الفواصل، وحزمة errors لأخطاء الحراسة و errors.Is، وحزمة fmt لتغليف الأخطاء. يوضح هذا التحدي كيف توفر errors.Is تحديداً قوياً للأخطاء يعمل بشكل صحيح مع الأخطاء المغلفة، مما يجعلها ضرورية لمعالجة الأخطاء الموثوقة في التطبيقات المعقدة.
ورقة مرجعية
تتحقق الدالة errors.Is مما إذا كان هناك خطأ معين موجوداً في أي مكان ضمن سلسلة الأخطاء، حتى عندما يتم تغليفه عدة مرات:
var ErrNotFound = errors.New("not found")
wrappedErr := fmt.Errorf("failed to load user: %w", ErrNotFound)
if errors.Is(wrappedErr, ErrNotFound) {
fmt.Println("This is a not found error")
}تأخذ errors.Is معاملين: الخطأ المراد فحصه والخطأ المستهدف للعثور عليه. وتُرجع true إذا تم العثور على الخطأ المستهدف في أي مكان في سلسلة الأخطاء.
هذه هي الطريقة الحديثة والموصى بها للتحقق من أخطاء معينة في Go، حيث أنها تعمل بشكل صحيح مع الأخطاء المغلفة (wrapped errors) على عكس مقارنة النصوص أو فحوصات المساواة المباشرة.
جرّب بنفسك
package main
import (
"errors"
"fmt"
"strings"
)
func main() {
// قراءة المدخلات
var dbOperation string
var appContext string
fmt.Scanln(&dbOperation)
fmt.Scanln(&appContext)
// تحليل المدخلات
dbParts := strings.Split(dbOperation, ",")
databaseName := dbParts[0]
operation := dbParts[1]
errorType := dbParts[2]
contextParts := strings.Split(appContext, ",")
serviceName := contextParts[0]
retryCount := contextParts[1]
// TODO: اكتب الكود الخاص بك أدناه
// 1. قم بتعريف أخطاء الحراسة (sentinel errors) الثلاثة باستخدام errors.New()
// 2. قم بإنشاء الخطأ الأصلي بناءً على نوع الخطأ
// 3. قم بإنشاء خطأ مغلف على مستوى قاعدة البيانات باستخدام fmt.Errorf مع %w
// 4. قم بإنشاء خطأ مغلف على مستوى الخدمة
// 5. استخدم errors.Is للتحقق من كل خطأ حراسة
// 6. عرض النتائج وسلسلة الأخطاء
}يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Logic & Flow
1تدفق التحكم المتقدم
جملة Switch مع `fallthrough`الخروج من الحلقات المتداخلةالاستمرار في حلقة محددةجملة `goto`ملخص - التحكم المتقدم في الحلقات7معالجة الأخطاء بعمق
أنواع الأخطاء المخصصةتغليف الأخطاء باستخدام `%w`فك التغليف باستخدام `errors.Is`فك التغليف باستخدام `errors.As`فهم `panic`استخدام `recover`ملخص - القسمة الآمنة2الـ Structs والميثودز (Methods)
تعريف الـ Methods على الـ Structsالـ Value Receiversالـ Pointer Receiversاختيار الـ Receiversالـ Methods مقابل الـ Functionsملخص - سلوك الـ Struct5تعمق في الـ Maps
Maps من نوع Structsالـ Pointers كقيم في الـ Mapsالتحقق من الـ Nil Mapsمقارنة الـ Mapsمراجعة - عداد تكرار الكلمات3الواجهات (الأساسيات)
ما هي الواجهة؟تعريف الواجهةتطبيق الواجهةاستخدام أنواع الواجهاتالواجهة الفارغةتأكيدات النوع (Type Assertions)تبديل النوع (Type Switch)ملخص - الأشكال والسلوكيات