تعتمد Go على تصور مختلف نسبياً للأخطاء مقارنة بلغات مثل Java وC++ وJavaScript وPython: الخطأ في Go قيمة من النوع المدمج error، وتعيده الدالة عادة إلى المستدعي إلى جانب قيم الإرجاع الأخرى. وبذلك يصبح فحص الخطأ والتعامل معه جزءاً ظاهراً من مسار البرنامج، بدلاً من نقله إلى آلية استثناءات منفصلة.
يقدم دليل JetBrains، الذي كتب أصلاً بواسطة المساهم المجتمعي Christoph Berger ثم نُقل إلى مدونة JetBrains Go، مجموعة من التقنيات والممارسات للتعامل مع أخطاء الإدخال والإخراج والشبكات والتحقق من البيانات وغيرها. وقد جرى تحديثه في أغسطس 2026 لمواكبة أحدث تغييرات لغة Go.
الإرجاع هو نقطة البداية
عندما لا تملك الدالة سياقاً كافياً لمعالجة المشكلة، فعليها إعادة الخطأ إلى الدالة المستدعية. وتضع Go قيمة الخطأ عادة في آخر قائمة الإرجاع؛ إذ تعيد دالة مثل ReadFile() محتوى الملف وخطأً تكون قيمته nil عند النجاح أو غير ذلك عند الفشل.
يوضح الدليل أن على المستدعي اختبار الخطأ فوراً بدلاً من تجاهله. وإذا كانت الدالة قد فتحت مورداً مثل ملف أو اتصالاً شبكياً، فينبغي استخدام defer لتنظيفه عند الخروج، لكن بعد التأكد من نجاح عملية الفتح أولاً، لأن محاولة إغلاق مورد غير صالح قد تؤدي إلى مشكلة إضافية.
إضافة السياق دون إتلاف بنية الخطأ
قد ينتقل الخطأ عبر سلسلة من الدوال قبل أن تتم معالجته أو تسجيله. وخلال هذا الانتقال، يمكن لكل دالة إضافة معلومات مفيدة مثل العملية التي فشلت أو المسار الذي كانت تعالجه. لكن تحويل الخطأ إلى نص عبر دمج err.Error() يفقد السلسلة والبنية النوعية للخطأ.
الأسلوب الصحيح هو استخدام fmt.Errorf() مع المعامل %w لتغليف الخطأ مع الاحتفاظ بالخطأ الأصلي. ويتيح ذلك لاحقاً استخدام errors.Unwrap() للوصول إلى طبقة واحدة، أو استخدام errors.Is() وerrors.As() لاختبار أخطاء محددة داخل سلسلة التغليف. وتضيف Go 1.26 الدالة العامة errors.AsType() كبديل آمن نوعياً لـ As()؛ فهي تعيد الخطأ المطابق وقيمة منطقية، وتسمح للمترجم باكتشاف أخطاء النوع التي قد تظهر وقت التشغيل مع الأسلوب الأقدم.
الأخطاء المتعددة والسياق الملغى
لا تكون الأخطاء دائماً سلسلة خطية. فعند معالجة مجموعة ملفات مثلاً، قد تنجح بعض العمليات وتفشل أخرى. تقدم الحزمة القياسية errors.Join()، المتاحة منذ Go 1.20، لجمع عدة أخطاء في قيمة واحدة مع الاحتفاظ بالمحتوى الذي تمت معالجته بنجاح.
ينبه الدليل إلى أن errors.Unwrap() يعيد قيمة واحدة، ولذلك يعيد nil عند التعامل مع خطأ مدمج. وللوصول إلى الأخطاء المجمعة، يجب التحقق من أن القيمة تنفذ الواجهة التي توفر Unwrap() []error. كما يمكن منذ Go 1.20 استخدام context.WithCancelCause() لربط إلغاء السياق بسبب مخصص، ثم استرداد ذلك السبب عبر context.Cause(ctx) بدلاً من الاكتفاء بالقيمة العامة context.Canceled.
متى يكون panic مناسباً؟
لا ينبغي أن تكون panic وrecover بديلاً اعتيادياً عن فحص الأخطاء. فالأخطاء المتوقعة، مثل مدخلات المستخدم غير الصالحة أو الملفات المفقودة أو مهلات الشبكة، يجب التعامل معها وإعادتها عبر مسار الإرجاع المعتاد.
يصبح الذعر مناسباً عندما تكون المشكلة غير متوقعة ولا توجد معالجة ذات معنى لها، مثل فشل تجميع تعبير نمطي ثابت كان ينبغي التحقق من صحته مسبقاً. وفي حالات خوادم HTTP، قد يستخدم التطبيق recover() داخل دالة مؤجلة لمنع انهيار معالجة الطلب الحالي من التأثير في الطلبات الأخرى، حيثما كان ذلك ممكناً. أما حالات مثل نفاد الذاكرة فقد لا تترك للتطبيق خياراً عملياً للاستمرار.
ما الذي يهم عملياً للمطورين؟
- لا تتجاهل الأخطاء: إسناد قيمة الخطأ إلى المعرف الفارغ أو إسقاط قيمة الإرجاع الوحيدة قد يؤخر اكتشاف المشكلة ويجعل آثارها اللاحقة أصعب في التشخيص.
- سجّل في المكان المناسب: على الدالة أن تعالج الخطأ أو تعيده إلى المستدعي. ويوصي الدليل بأن تتجنب المكتبات التسجيل من تلقاء نفسها، لأن مستخدميها قد يفضلون سجلات أو وجهات إخراج مختلفة.
- استخدم أنواعاً مناسبة: يمكن لأنواع الأخطاء المخصصة حمل معلومات إضافية، كما تفعل fs.PathError التي توفر العملية والمسار والخطأ الداخلي.
- ميّز أخطاء الشبكة: يتيح net.OpError فحص طبيعة فشل الاتصال، وقد يسمح الخطأ المؤقت بإعادة المحاولة أو استخدام استراتيجية مثل التراجع الأسي.
- استفد من عدد البايتات في عمليات الإدخال والإخراج: تعيد واجهة io.Reader عدد البايتات التي عولجت مع الخطأ، ما قد يساعد على استئناف العملية بدلاً من إعادة كل البيانات.
- انتبه إلى io.EOF: تشير هذه القيمة إلى نهاية ناجحة لتيار القراءة وفق دلالات الحزمة، وليست بالضرورة فشلاً يحتاج إلى تسجيل كخطأ عادي.
- لا تستخدم log.Fatal() بلا حساب: فهي تستدعي os.Exit()، ما يؤدي إلى تجاوز الدوال المؤجلة، ولذلك يقترح الدليل حصر استخدامها أو استخدام os.Exit() في الدالة main().
الخلاصة التحريرية من certi.news هي أن وضوح مسار الخطأ جزء من تصميم البرنامج، وليس مجرد أسلوب كتابة مطول. فالتغليف المنظم والأنواع المناسبة والتسجيل المسؤول تجعل التشخيص قابلاً للتنفيذ، بينما يؤدي الاعتماد على panic أو الرسائل العامة أو إسقاط الأخطاء إلى إخفاء المعلومات التي يحتاجها المطور لاحقاً. وتظل المفاضلة العملية مرتبطة بسياق التطبيق: فخادم الطلبات قد يحتاج إلى استرداد محدود، في حين أن خطأ لا يمكن التعافي منه قد يستدعي الإنهاء وإعادة التشغيل.
كيف أعددنا هذا الخبر؟
اعتمد الخبر على المصدر الأصلي الموضح أعلاه. قد نستخدم أدوات آلية للمساعدة في الاستخراج والتصنيف والصياغة،
لكن النشر يخضع لقواعد تمنع المحتوى المكرر والقصير أو الروتيني منخفض القيمة.
اقرأ سياستنا التحريرية.