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

Panic و Defer و Recover

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

بينما تركز لغة Go على إرجاع الأخطاء، فإن بعض الحالات تكون غير قابلة للاسترداد حقاً. لهذه الحالات، توفر Go كلاً من panic و defer و recover—وهي آلية للتعامل مع الظروف الاستثنائية.

panic يوقف التنفيذ العادي فوراً. هو مخصص لأخطاء البرمجة مثل الوصول إلى فهرس خارج الحدود، وليس للأخطاء المتوقعة مثل مدخلات المستخدم غير الصالحة:

func MustGetConfig(key string) string {
    value, exists := config[key]
    if !exists {
        panic("missing required config: " + key)
    }
    return value
}

تقوم defer بجدولة دالة لتعمل عندما تنتهي الدالة المحيطة بها—سواء كان ذلك بشكل طبيعي أو بسبب panic. تُنفذ الاستدعاءات المؤجلة بترتيب عكسي (الأخير دخولاً، الأول خروجاً):

func ProcessFile() {
    fmt.Println("Opening file")
    defer fmt.Println("Closing file")
    fmt.Println("Processing...")
    // المخرجات: Opening file, Processing..., Closing file
}

recover يلتقط الـ panic ويستعيد التنفيذ الطبيعي. لا يعمل إلا داخل دالة مؤجلة (deferred function):

func SafeOperation() (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered from panic: %v", r)
        }
    }()
    
    riskyOperation()
    return nil
}

يحول هذا النمط الـ panic إلى خطأ عادي، مما يسمح لبرنامجك بالاستمرار. استخدمه باعتدال—يفضل إرجاع الأخطاء (errors) في حالات الفشل المتوقعة، وخصص panic/recover للحالات الاستثنائية حقاً مثل الحالة التالفة أو الظروف التي لا يمكن التعافي منها.

challenge icon

التحدي

سهل

لنقم ببناء آلة حاسبة للقسمة الآمنة توضح كيفية استخدام panic و defer و recover للتعامل مع الحالات الاستثنائية بسلاسة. ستحاول الآلة الحاسبة الخاصة بك إجراء عمليات محفوفة بالمخاطر وتحويل الـ panics إلى أخطاء عادية يمكن التعامل معها بشكل طبيعي.

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

  • calculator.go: إنشاء منطق القسمة مع استعادة الـ panic.

    قم بتنفيذ دالة Divide تأخذ عددين صحيحين وتطلق panic مع الرسالة "division by zero" إذا كان القاسم صفراً. خلاف ذلك، فإنها تعيد النتيجة الصحيحة للقسمة.

    قم بتنفيذ دالة SafeDivide تأخذ عددين صحيحين وتعيد كلاً من نتيجة عدد صحيح وخطأ (error). يجب على هذه الدالة:

    • استخدام defer مع دالة مجهولة (anonymous function) للاستعادة من أي panic
    • إذا تم استعادة panic، قم بتحويله إلى خطأ بالتنسيق "calculation error: [panic message]"
    • استدعاء دالة Divide داخلياً
    • إرجاع النتيجة و nil في حالة النجاح، أو صفر والخطأ إذا تم استعادة panic
  • main.go: قراءة عددين صحيحين من المدخلات واستخدام SafeDivide لإجراء العملية الحسابية بأمان. وضح أيضاً استخدام defer عن طريق طباعة رسائل التنظيف بالترتيب الصحيح.

    يجب أن تقوم دالة main الخاصة بك بما يلي:

    • طباعة Starting calculation في البداية
    • استخدام defer لجدولة طباعة Cleanup complete
    • استخدام defer آخر لجدولة طباعة Releasing resources
    • استدعاء SafeDivide مع قيم المدخلات
    • طباعة إما Result: [value] أو Error: [error message] بناءً على النتيجة

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

  • السطر 1: العدد الصحيح الأول (المقسوم)
  • السطر 2: العدد الصحيح الثاني (المقسوم عليه)

تذكر أن استدعاءات defer تُنفذ بترتيب عكسي (الأخير يدخل أولاً يخرج أولاً - LIFO)، لذا يجب أن تظهر رسائل التنظيف الخاصة بك بالترتيب المعاكس لكيفية جدولتها.

على سبيل المثال، عند إدخال 20 و 4، يجب أن يكون الناتج:

Starting calculation
Result: 5
Releasing resources
Cleanup complete

وعند إدخال 10 و 0، يجب أن يكون الناتج:

Starting calculation
Error: calculation error: division by zero
Releasing resources
Cleanup complete

ورقة مرجعية

توفر لغة Go كل من panic و defer و recover للتعامل مع الظروف الاستثنائية.

تقوم panic بإيقاف التنفيذ العادي فوراً وهي مخصصة لأخطاء البرمجة غير القابلة للاسترداد:

func MustGetConfig(key string) string {
    value, exists := config[key]
    if !exists {
        panic("missing required config: " + key)
    }
    return value
}

تقوم defer بجدولة وظيفة لتعمل عند انتهاء الوظيفة المحيطة بها. يتم تنفيذ الاستدعاءات المؤجلة بترتيب عكسي (الأخير يدخل أولاً، يخرج أولاً):

func ProcessFile() {
    fmt.Println("Opening file")
    defer fmt.Println("Closing file")
    fmt.Println("Processing...")
    // المخرجات: Opening file, Processing..., Closing file
}

تقوم recover بالتقاط الـ panic واستعادة التنفيذ العادي. وهي تعمل فقط داخل وظيفة مؤجلة (deferred function):

func SafeOperation() (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered from panic: %v", r)
        }
    }()
    
    riskyOperation()
    return nil
}

يقوم هذا النمط بتحويل الـ panic إلى خطأ (error) عادي. يفضل إرجاع الأخطاء للإخفاقات المتوقعة، وتخصيص panic/recover للحالات الاستثنائية حقاً.

جرّب بنفسك

package main

import (
	"fmt"
)

func main() {
	// قراءة المدخلات
	var dividend, divisor int
	fmt.Scanln(&dividend)
	fmt.Scanln(&divisor)

	// طباعة رسالة البدء
	fmt.Println("Starting calculation")

	// TODO: استخدم defer لجدولة رسالة "Cleanup complete"

	// TODO: استخدم defer لجدولة رسالة "Releasing resources"
	// تذكر: الاستدعاءات المؤجلة (deferred calls) تُنفذ بترتيب LIFO (من يدخل آخراً يخرج أولاً)

	// TODO: استدعِ SafeDivide مع قيم المدخلات

	// TODO: اطبع إما "Result: [value]" أو "Error: [error message]"
	// بناءً على ما إذا كان قد تم إرجاع خطأ أم لا
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming