Panic و Defer و Recover
جزء من قسم البرمجة كائنية التوجه في رحلة 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:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}يحوّل هذا النمط حالة panic إلى error عادية، مما يسمح لبرنامجك بالمتابعة. استخدمه باعتدال: يُفضَّل إرجاع error في حالات الفشل المتوقعة، واحتفظ بـ panic/recover للمواقف الاستثنائية حقًا، مثل تلف الحالة أو الظروف التي يتعذر التعافي منها.
التحدي
سهللنبنِ حاسبة قسمة آمنة توضّح كيفية استخدام panic وdefer وrecover للتعامل مع الحالات الاستثنائية بسلاسة. ستحاول الحاسبة تنفيذ عمليات محفوفة بالمخاطر وتحويل حالات panic إلى أخطاء عادية يمكن التعامل معها بشكل طبيعي.
ستنظّم الشيفرة عبر ملفين:
calculator.go: أنشئ منطق القسمة مع استرداد حالاتpanic.نفّذ دالة
Divideتأخذ عددين صحيحين وتُحدِث حالةpanicبالرسالة"division by zero"إذا كان المقسوم عليه يساوي صفراً. وإلا، فإنها تُرجع النتيجة الصحيحة للقسمة.نفّذ دالة
SafeDivideتأخذ عددين صحيحين وتُرجع كلاً من نتيجة صحيحة وخطأ. يجب أن تقوم هذه الدالة بما يلي:- استخدام
deferمع دالةanonymousلاسترداد أي حالةpanic - إذا تم استرداد حالة
panic، فحوّلها إلى خطأ بالتنسيق"calculation error: [panic message]" - استدعاء الدالة
Divideداخلياً - إرجاع النتيجة و
nilعند النجاح، أو صفراً والخطأ إذا تم استرداد حالةpanic
- استخدام
main.go: اقرأ عددين صحيحين من الإدخال واستخدمSafeDivideلتنفيذ الحساب بأمان. اعرض أيضاً كيفية استخدامdeferمن خلال طباعة رسائل التنظيف بالترتيب الصحيح.يجب أن تقوم دالتك الرئيسية بما يلي:
- طباعة
Starting calculationفي البداية - استخدام
deferلجدولة طباعةCleanup complete - استخدام
deferآخر لجدولة طباعةReleasing resources - استدعاء
SafeDivideباستخدام قيم الإدخال - طباعة
Result: [value]أوError: [error message]استناداً إلى النتيجة
- طباعة
سيتم توفير المدخلات التالية:
- السطر 1: العدد الصحيح الأول (المقسوم)
- السطر 2: العدد الصحيح الثاني (المقسوم عليه)
تذكّر أن الاستدعاءات المؤجلة تُنفَّذ بترتيب عكسي (الأخير دخولاً، الأول خروجاً)، لذا يجب أن تظهر رسائل التنظيف بترتيب معاكس للترتيب الذي تمت جدولة الاستدعاءات به.
على سبيل المثال، عند إعطاء 20 و4، يجب أن يكون الناتج:
Starting calculation
Result: 5
Releasing resources
Cleanup completeوعند إعطاء 10 و0، يجب أن يكون الناتج:
Starting calculation
Error: calculation error: division by zero
Releasing resources
Cleanup completeجرّب بنفسك
package main
import (
"fmt"
)
func main() {
// اقرأ المدخلات
var dividend, divisor int
fmt.Scanln(÷nd)
fmt.Scanln(&divisor)
// اطبع رسالة البدء
fmt.Println("Starting calculation")
// TODO: Use defer to schedule "Cleanup complete" message
// TODO: Use defer to schedule "Releasing resources" message
// تذكر: استدعاءات defer تُنفَّذ بترتيب LIFO (الأخير دخولاً، الأول خروجاً)
// TODO: استدعِ SafeDivide مع قيم الإدخال
// TODO: اطبع إما "Result: [value]" أو "Error: [error message]"
// بناءً على ما إذا كان قد أُرجع خطأ
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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تدرّب بنفسك: مترجم Go عبر الإنترنت