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

تغليف الأخطاء (fmt.Errorf)

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

عندما تمر الأخطاء عبر طبقات متعددة من تطبيقك، تصبح معرفة مكان نشأة الخطأ أمراً بالغ الأهمية. تتيح لك دالة fmt.Errorf في لغة Go مع الرمز %w تغليف الأخطاء، مما يحافظ على الخطأ الأصلي مع إضافة سياق إضافي.

يؤدي تغليف الأخطاء (Error wrapping) إلى إنشاء سلسلة من الأخطاء. يمكن لكل طبقة إضافة معلومات حول ما كانت تحاول القيام به عند حدوث الخطأ:

func ReadConfig(filename string) error {
    data, err := os.ReadFile(filename)
    if err != nil {
        return fmt.Errorf("reading config file: %w", err)
    }
    // معالجة البيانات...
    return nil
}

الفعل %w مميز—فهو يغلف الخطأ الأصلي داخل الخطأ الجديد. وهذا يختلف عن %v، الذي يقوم فقط بتحويل الخطأ إلى سلسلة نصية ويفقد هوية الخطأ الأصلي.

func LoadSettings() error {
    err := ReadConfig("settings.json")
    if err != nil {
        return fmt.Errorf("loading settings: %w", err)
    }
    return nil
}

عندما يفشل LoadSettings، تظهر رسالة الخطأ السلسلة الكاملة: "loading settings: reading config file: open settings.json: no such file or directory". تضيف كل طبقة سياقاً، مما يجعل تصحيح الأخطاء أسهل بكثير.

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

challenge icon

التحدي

سهل

لنقم ببناء نظام معالجة طلبات يوضح تغليف الأخطاء (error wrapping) عبر طبقات متعددة. ستقوم بإنشاء سلسلة من الدوال حيث تضيف كل طبقة سياقاً للأخطاء، مما يسهل تتبع مكان حدوث المشكلات بالضبط.

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

  • orders.go: قم بإنشاء منطق معالجة الطلبات مع طبقات متعددة تقوم بتغليف الأخطاء أثناء انتشارها للأعلى.

    قم بتنفيذ ثلاث دوال تشكل سلسلة معالجة:

    • ValidateItem(itemID string) error - تعيد خطأ برسالة "item not found" إذا كان itemID هو "INVALID"، وإلا تعيد nil
    • ProcessOrder(orderID, itemID string) error - تستدعي ValidateItem. إذا أعادت خطأً، قم بتغليفه بالسياق: "processing order [orderID]: %w". وإلا تعيد nil
    • SubmitOrder(customerName, orderID, itemID string) error - تستدعي ProcessOrder. إذا أعادت خطأً، قم بتغليفه بالسياق: "submitting order for [customerName]: %w". وإلا تعيد nil

    يجب أن تستخدم كل طبقة fmt.Errorf مع الفعل %w لتغليف الخطأ القادم من الطبقة الأدنى، مما يبني سلسلة من السياق.

  • main.go: اقرأ تفاصيل الطلب من المدخلات، واستدعِ SubmitOrder، واعرض النتيجة. في حالة حدوث خطأ، اطبع سلسلة الأخطاء الكاملة. في حالة النجاح، اطبع رسالة تأكيد.

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

  • السطر 1: اسم العميل (Customer name)
  • السطر 2: معرف الطلب (Order ID)
  • السطر 3: معرف العنصر (Item ID)

اطبع النتيجة:

  • في حالة حدوث خطأ: Error: [full error chain]
  • في حالة النجاح: Order [orderID] submitted successfully for [customerName]

على سبيل المثال، إذا كانت المدخلات Alice، و ORD-123، و INVALID، يجب أن يكون مخرجك:

Error: submitting order for Alice: processing order ORD-123: item not found

لاحظ كيف تظهر رسالة الخطأ السلسلة الكاملة — يمكنك تتبع المشكلة من محاولة الإرسال في المستوى الأعلى، مروراً بمعالجة الطلب، وصولاً إلى فشل التحقق الفعلي. أضافت كل طبقة سياقها الخاص باستخدام %w.

وإذا كانت المدخلات Bob، و ORD-456، و ITEM-001، يجب أن يكون مخرجك:

Order ORD-456 submitted successfully for Bob

ورقة مرجعية

استخدم fmt.Errorf مع الفعل %w لتغليف الأخطاء مع الحفاظ على الخطأ الأصلي:

func ReadConfig(filename string) error {
    data, err := os.ReadFile(filename)
    if err != nil {
        return fmt.Errorf("reading config file: %w", err)
    }
    return nil
}

يقوم الفعل %w بتغليف الخطأ الأصلي داخل الخطأ الجديد، على عكس %v الذي يقوم فقط بتحويل الخطأ إلى سلسلة نصية ويفقد هوية الخطأ الأصلي.

يؤدي تغليف الخطأ إلى إنشاء سلسلة حيث تضيف كل طبقة سياقاً:

func LoadSettings() error {
    err := ReadConfig("settings.json")
    if err != nil {
        return fmt.Errorf("loading settings: %w", err)
    }
    return nil
}

تظهر رسالة الخطأ الناتجة السلسلة الكاملة: "loading settings: reading config file: open settings.json: no such file or directory"

جرّب بنفسك

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// قراءة اسم العميل
	scanner.Scan()
	customerName := scanner.Text()
	
	// قراءة معرف الطلب
	scanner.Scan()
	orderID := scanner.Text()
	
	// قراءة معرف العنصر
	scanner.Scan()
	itemID := scanner.Text()
	
	// TODO: استدعاء SubmitOrder مع قيم الإدخال
	// TODO: في حالة حدوث خطأ، اطبع: Error: [full error chain]
	// TODO: في حالة النجاح، اطبع: Order [orderID] submitted successfully for [customerName]
	
	_ = customerName
	_ = orderID
	_ = itemID
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming