panic وrecover وdefer
معرفة متى تستخدم panic وكيفية التعافي بسلاسة
panic وrecover وdefer درس مجاني في Go Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Go Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Go Academy 4 دروس في المجموع.
ما هو panic؟
يوقف panic التنفيذ العادي لـ goroutine الحالية، ويفك مكدس الاستدعاءات مع تشغيل الدوال المؤجلة، ويتسبب في انهيار البرنامج إذا لم تتم استعادته:
package main
import "fmt"
func mustPositive(n int) int {
if n <= 0 {
panic(fmt.Sprintf("mustPositive: got %d", n))
}
return n
}
func main() {
fmt.Println(mustPositive(5)) // 5
// fmt.Println(mustPositive(-1)) // panics!
}متى تستخدم panic
استخدم panic فقط في الحالات التالية:
- أخطاء المبرمج غير القابلة للاسترداد فعلًا (انتهاك الثوابت البرمجية)
- حالات مستحيلة تشير إلى وجود خطأ برمجي
- فشل بدء التشغيل (إعدادات مفقودة أو فشل الاتصالات أثناء التهيئة)
لا تستخدم panic مطلقًا لحالات الفشل المتوقعة — استخدم error بدلًا منه.
أساسيات defer
تُجدول defer استدعاء دالة ليُنفّذ عند عودة الدالة المحيطة، بصرف النظر عن طريقة عودتها (طبيعية أو بسبب خطأ أو panic):
package main
import "fmt"
func main() {
defer fmt.Println("third")
defer fmt.Println("second") // LIFO order
defer fmt.Println("first")
fmt.Println("running...")
// Output:
// running...
// first
// second
// third
}استخدام defer للتنظيف
الاستخدام الأكثر شيوعًا لـ defer هو ضمان تنظيف الموارد:
package main
import ("fmt"; "os")
func readFile(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", fmt.Errorf("open: %w", err)
}
defer f.Close() // always runs, even if we return early
buf := make([]byte, 1024)
n, _ := f.Read(buf)
return string(buf[:n]), nil
}recover — اعتراض حالات panic
يوقف recover حالة panic ويُرجع قيمة panic. ويجب استدعاؤه مباشرةً داخل دالة defer:
package main
import "fmt"
func safeDiv(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
return a / b, nil // panics if b==0
}
func main() {
r, err := safeDiv(10, 0)
fmt.Println(r, err)
// 0 recovered from panic: runtime error: integer divide by zero
}يجب أن يكون recover داخل دالة مؤجلة
لا يعمل recover إلا إذا استُدعي مباشرةً داخل دالة مؤجلة. ويؤدي استدعاؤه في مكان آخر إلى إرجاع nil:
package main
import "fmt"
func wrong() {
// recover() here does nothing — not in defer
}
func correct() {
defer func() {
if r := recover(); r != nil {
fmt.Println("caught:", r)
}
}()
panic("oops")
}
func main() { correct() }panic في شفرات المكتبات
ينبغي لشفرات المكتبات ألا تسمح تقريبًا مطلقًا بتسرّب حالات panic إلى المتصلين. حوّل حالات panic إلى أخطاء عند الحد الفاصل:
package main
import ("fmt"; "runtime/debug")
func safeCall(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v\n%s", r, debug.Stack())
}
}()
fn()
return nil
}
func main() {
err := safeCall(func() { panic("boom") })
fmt.Println(err != nil) // true
}ترتيب defer بنمط LIFO ومشكلة الحلقات
تُنفّذ الاستدعاءات المؤجلة بترتيب الوارد أخيرًا يخرج أولًا (LIFO). انتبه عند استخدام defer داخل الحلقات — استخدم دالة مغلقة أو دالة مساعدة:
package main
import "fmt"
func processFiles(names []string) {
for _, name := range names {
name := name // capture loop variable
defer func() { fmt.Println("close:", name) }()
}
}
func main() {
processFiles([]string{"a.txt", "b.txt", "c.txt"})
// Output (LIFO): close: c.txt, close: b.txt, close: a.txt
}defer مع قيم الإرجاع المُسمّاة
يمكن للدوال المؤجلة قراءة قيم الإرجاع المُسمّاة وتعديلها — وهذا مفيد لإضافة تعليقات إلى الأخطاء:
package main
import "fmt"
func doWork() (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("doWork: %w", err)
}
}()
return fmt.Errorf("something failed")
}
func main() {
err := doWork()
fmt.Println(err) // doWork: something failed
}panic مقابل error: دليل اتخاذ القرار
اختيار استخدام panic أو error:
- استخدم error: حالات الفشل المتوقعة (ملف غير موجود، انتهاء مهلة الشبكة، إدخال غير صالح)
- استخدم panic: الأخطاء البرمجية (الوصول إلى map بقيمة nil أو الخروج عن الحدود)، والحالات المستحيلة، وفشل التهيئة
- لا تستخدم panic مطلقًا عبر حدود واجهة برمجة التطبيقات — فلا يستطيع المتصلون التعامل معه
- استعد من حالات panic عند المستوى الأعلى لـ goroutine في الخوادم لتجنب الانهيارات
تحقق سريع
أين يجب استدعاء recover() لاعتراض حالة panic؟
مراجعة: panic وrecover وdefer
الخلاصة:
panic: يفكّ المكدس، وينفّذ التأجيلات، ويتسبب في انهيار البرنامج إذا لم تتم معالجتهdefer: يُنفَّذ عند خروج الدالة، سواءً بشكل طبيعي أو بسبب خطأ أوpanic؛ وبترتيب LIFOrecover: يوقف حالةpanicويعيد قيمتها؛ ويجب أن يُستدعى داخلdefer- استخدم
panicلأخطاء المبرمج، وليس لحالات الفشل المتوقعة - حوّل حالات
panicإلى أخطاء عند حدود API
الأسئلة الشائعة
هل درس «panic وrecover وdefer» مجاني؟
نعم — نص درس «panic وrecover وdefer» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Go Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Go Academy 4 دروس في المجموع.
ماذا ستتعلم في «panic وrecover وdefer»؟
معرفة متى تستخدم panic وكيفية التعافي بسلاسة تتمرن على Go Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Go Academy؟
لا تُشترط خبرة سابقة. Go Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «panic وrecover وdefer»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Go Academy هذا؟
نعم. كل درس في Go Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- واجهة error
- إنشاء أخطاء مخصصة
- تغليف الأخطاء وإلغاء تغليفها
- panic وrecover وdefer