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 للحالات الاستثنائية حقاً مثل الحالة التالفة أو الظروف التي لا يمكن التعافي منها.
التحدي
سهللنقم ببناء آلة حاسبة للقسمة الآمنة توضح كيفية استخدام 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(÷nd)
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]"
// بناءً على ما إذا كان قد تم إرجاع خطأ أم لا
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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