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

panic وrecover في Go: حالات panic وقت التشغيل ومتى تستخدمها

توقف حالة panic التنفيذ العادي وتفكّ المكدّس مع تنفيذ الاستدعاءات المؤجّلة. تعلّم ما يسبّبها، وكيف يوقفها recover داخل دالة مؤجّلة، ورسائل أخطاء وقت التشغيل التي ستراها، ومتى تكون panic القرار الصحيح.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

كيف تبدو حالة panic

توقف حالة panic الدالة الحالية، وتنفّذ استدعاءاتها المؤجّلة، ثم تفعل الشيء نفسه في مستدعيها، وهكذا صعودًا في المكدّس. وإذا وصلت إلى أعلى الـ goroutine ينهار البرنامج.

يخرج هذا البرنامج بالحالة 2. المخرجات:

before
deferred in main: still runs
panic: runtime error: index out of range [5] with length 3

goroutine 1 [running]:
main.main()
	/tmp/main.go:11 +0x...
exit status 2

نُفّذ الاستدعاء المؤجّل قبل تقرير الانهيار. يسمّي الأثر الـ goroutine والدالة والسطر، وهذا يكفي عادة لإيجاد الخطأ.

حالات panic الشائعة وقت التشغيل

الرسالةالسبب
index out of range [5] with length 3فهرس شريحة أو مصفوفة أو نص بعد النهاية
slice bounds out of range [:7] with capacity 5تقطيع بعد السعة
invalid memory address or nil pointer dereferenceقراءة حقل أو استدعاء عبر مؤشر nil
assignment to entry in nil mapالكتابة في خريطة لم تُنشأ قط
interface conversion: interface {} is int, not stringتأكيد نوع بقيمة واحدة إلى النوع الخطأ
integer divide by zeroقسمة صحيحة أو باقي قسمة على 0 (الأعداد العشرية تعطي +Inf أو NaN بدلًا من ذلك)
close of closed channel، send on closed channelسوء استخدام القنوات
all goroutines are asleep - deadlock!كل goroutine متوقّفة (خطأ قاتل، لا panic)

كل واحدة من هذه خطأ في البرنامج، لا حالة تُعالج. الإصلاح فحص حدود، أو فحص nil، أو make، أو تأكيد بصيغة comma-ok، لا recover.

الاستعادة

توقف recover() حالة panic. ولا تعمل إلا عند استدعائها مباشرة داخل دالة مؤجّلة، لأن الدوال المؤجّلة هي الشيفرة الوحيدة التي تُنفَّذ أثناء فكّ المكدّس.

المخرجات:

5 <nil>
0 recovered: runtime error: integer divide by zero
program continues

ما حدث في الاستدعاء الثاني:

  1. سبّبت a / b حالة panic.
  2. نُفّذ الإغلاق المؤجّل، وأعادت recover() قيمة الـ panic (من النوع runtime.Error).
  3. توقّف فكّ المكدّس. عادت safeDivide إلى main بشكل طبيعي، مع ضبط النتيجة المسمّاة err من الإغلاق.

النتيجة المسمّاة هي ما يسمح للدالة المؤجّلة بإعادة خطأ. من دونها تعيد الدالة قيمها الصفرية. تشرح صفحة defer كيف تعدّل الإغلاقات المؤجّلة النتائج.

تعيد recover() القيمة nil عندما لا توجد panic، فيجعل فحص if r != nil الدالة المؤجّلة غير مؤذية في المسار العادي. وإذا استُدعيت خارج دالة مؤجّلة، أو في دالة تستدعيها الدالة المؤجّلة، تعيد recover القيمة nil ولا تفعل شيئًا.

panic بقيمتك الخاصة

تأخذ panic أي قيمة. والمعتاد خطأ أو نص.

استعد ما تتوقّعه وأعد إطلاق panic لكل ما عداه. ابتلاع كل حالات panic يخفي أخطاء حقيقية.

منذ Go 1.21 تتحوّل panic(nil) إلى *runtime.PanicNilError، فصارت إعادة recover() للقيمة nil تعني بثقة "لا panic".

حالات panic في الـ goroutines

لا تلتقط recover إلا حالات panic في الـ goroutine الخاصة بها. حالة panic في أي goroutine دون recover تقتل العملية كلها، بما في ذلك main وكل goroutine أخرى.

يمكن أن يُطبع سطرا العاملين بأي ترتيب؛ وmain finished يأتي دائمًا أخيرًا. لم يكن defer recover() في main لينقذ البرنامج من العامل الثاني. لهذا تستعيد خوادم HTTP لكل طلب: تستعيد net/http حالات panic في goroutine كل معالج، وتسجّلها، وتغلق ذلك الاتصال، فلا يُسقط طلب سيئ واحد الخادم.

بعض الإخفاقات أخطاء قاتلة لا حالات panic، ولا يمكن التعافي منها أبدًا: concurrent map writes، ونفاد الذاكرة، ورسالة كاشف الجمود all goroutines are asleep.

متى تكون panic القرار الصحيح

القاعدة العامة في Go: أعد الأخطاء لكل ما قد يسوء وقت التشغيل، واستخدم panic فقط لأخطاء المبرمج. عمليًا تكون panic مناسبة عندما:

  • ينكسر ثابت. يصل switch على تعدادك الخاص إلى حالة لا يمكن أن تحدث. المتابعة ستفسد البيانات.
  • تتلقّى دالة Must مساعدة مدخلًا ثابتًا خاطئًا. تغلّف regexp.MustCompile وtemplate.Must وuuid.MustParse دالة تعيد خطأ وتسبّب panic عند الفشل. استخدمها للقيم المعروفة وقت الترجمة، وعادة متغيّرات على مستوى الحزمة، حيث يعني الفشل أن الشيفرة المصدرية خاطئة:
var emailRE = regexp.MustCompile(`^[^@\s]+@[^@\s]+$`)
  • لا يمكن متابعة بدء التشغيل. إعدادات مطلوبة مفقودة في main. حتى هنا يكون طبع الخطأ واستدعاء os.Exit(1) غالبًا أنظف من أثر مكدّس.

panic هي الأداة الخاطئة لـ:

  • الإخفاقات المتوقّعة: مدخلات مستخدم غير صالحة، أو ملف مفقود، أو انقضاء مهلة. أعد error؛ راجع معالجة الأخطاء.
  • التحكّم في التدفّق: استخدام panic وrecover كاستثناءات عبر شجرة استدعاءات كبيرة يجعل الشيفرة صعبة المتابعة. تفعل المكتبة القياسية ذلك داخليًا في موضعين تقريبًا (مرمّز encoding/json)، مع الاستعادة دائمًا قبل العودة، فلا تتسرّب panic من الحزمة.
  • الواجهات البرمجية للمكتبات: المكتبة التي تسبّب panic مع المدخلات الخاطئة تجبر كل مستدعٍ على إضافة recover. أعد خطأ.

أخطاء شائعة

  • استدعاء recover خارج دالة مؤجّلة. تعيد nil.
  • الاستعادة في main لحالة panic في goroutine. كل goroutine تحتاج استعادتها الخاصة.
  • ابتلاع كل حالات panic. سجّل مع المكدّس (debug.Stack() من runtime/debug) وأعد إطلاق ما لم تتوقّعه.
  • استخدام recover لمعالجة الكتابة في خريطة nil أو الفهارس خارج النطاق. أصلح الخطأ بدلًا من ذلك.

الأسئلة الشائعة

ما هي حالة panic في Go؟

فشل وقت التشغيل يوقف التدفّق العادي للـ goroutine الحالية. تنفّذ Go الاستدعاءات المؤجّلة لكل دالة في المكدّس، من الأعمق إلى الخارج، وإذا لم يستعدها شيء يطبع البرنامج قيمة الـ panic وأثر المكدّس ويخرج بالحالة 2. تأتي حالات panic من الأخطاء البرمجية (فهرس خارج النطاق، إلغاء مرجعية مؤشر nil، كتابة في خريطة nil) أو من استدعاء صريح لـ panic(v).

كيف تتعافى من panic في Go؟

استدعِ recover() داخل دالة مؤجّلة: defer func() { if r := recover(); r != nil { ... } }(). تعيد القيمة الممرّرة إلى panic وتوقف فكّ المكدّس، فتعود الدالة التي أجّلتها إلى مستدعيها بشكل طبيعي. وإذا استُدعيت في أي مكان آخر تعيد recover القيمة nil ولا تفعل شيئًا.

هل يمكنني استعادة panic من goroutine أخرى؟

لا. لا توقف recover حالة panic إلا في الـ goroutine التي تعمل فيها. حالة panic في goroutine بدأتها، دون recover داخلها، تُسقط البرنامج كله. كل goroutine قد تسبّب panic تحتاج recover مؤجّلًا خاصًا بها.

متى أستخدم panic بدل إعادة خطأ؟

للأخطاء البرمجية والحالات المستحيلة، لا للإخفاقات المتوقّعة. المدخلات الخاطئة والملفات المفقودة وأخطاء الشبكة أخطاء عادية. تكون panic معقولة عندما ينكسر ثابت، أو عندما تتلقّى دالة Must مساعدة ثابتًا يجب أن يكون صالحًا دائمًا (regexp.MustCompile)، أو عندما لا يستطيع البرنامج البدء أصلًا. يجب ألا تسمح المكتبات بتسرّب حالات panic عبر واجهتها العامة.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن