إخفاء الخطأ.
الفشل في الإبلاغ عن تفاصيل الخطأ
في برمجة الحاسوب، إخفاء الخطأ (error hiding) (أو ابتلاع الخطأ error swallowing) هو ممارسة التقاط خطأ أو استثناء، ثم الاستمرار دون تسجيل الخطأ أو معالجته أو الإبلاغ عنه لأجزاء أخرى من البرمجيات. وتعتبر معالجة الأخطاء بهذه الطريقة ممارسة سيئة ونمطاً مضاداً في برمجة الحاسوب. وفي اللغات التي تدعم معالجة الاستثناءات، تُعرف هذه الممارسة باسم ابتلاع الاستثناء (exception swallowing).
وللأخطاء والاستثناءات عدة أغراض:
- مساعدة مسؤولي صيانة البرمجيات على تتبع وفهم المشكلات التي تحدث عندما يقوم المستخدم بتشغيل البرنامج، وذلك عند دمجه مع نظام تسجيل الأخطاء
- توفير معلومات مفيدة لمستخدم البرنامج، عندما تقترن برسائل خطأ ذات معنى، أو رموز أخطاء أو أنواع أخطاء تظهر في واجهة المستخدم، أو كرسائل وحدة التحكم (console)، أو كبيانات يتم إرجاعها من واجهة برمجة تطبيقات (API) (اعتماداً على نوع البرنامج ونوع المستخدم)
- الإشارة إلى أن العملية العادية لا يمكن أن تستمر، بحيث يمكن للبرنامج التراجع إلى طرق بديلة لأداء المهمة المطلوبة أو إيقاف العملية.
وعندما يتم ابتلاع الأخطاء، لا يمكن تحقيق هذه الأغراض. وتُفقد المعلومات المتعلقة بالخطأ، مما يجعل من الصعب جداً تتبع المشكلات. واعتماداً على كيفية تنفيذ البرنامج، يمكن أن يتسبب ذلك في آثار جانبية غير مقصودة تتدفق إلى أخطاء أخرى، مما يزعزع استقرار النظام. وبدون معلومات حول السبب الجذري للمشكلة، يكون من الصعب جداً معرفة الخطأ الذي يحدث أو كيفية إصلاحه.
أمثلة
لغات تدعم معالجة الاستثناءات
في هذا المثال بلغة C#، على الرغم من أن الكود داخل كتلة try يطلق استثناءً، إلا أنه يتم التقاطه بواسطة جملة catch العامة. لقد تم ابتلاع الاستثناء واعتُبر أنه تمت معالجته، ويستمر البرنامج في العمل.
try {
throw new Exception();
} catch {
// لا تفعل شيئاً
}
في هذا المثال بلغة PowerShell، تلتقط جملة trap الاستثناء الذي تم إطلاقه وتبتلعه من خلال مواصلة التنفيذ. وتظهر رسالة "I should not be here" (لا يجب أن أكون هنا) كما لو لم يحدث أي استثناء.
&{
trap { continue }
throw
write-output "I should not be here"
}
ويمكن أن يحدث ابتلاع الاستثناء أيضاً إذا تم معالجة الاستثناء وإعادة إطلاقه كاستثناء مختلف، مما يؤدي إلى التخلص من الاستثناء الأصلي وكل سياقه.
في هذا المثال بلغة C#، يتم التقاط جميع الاستثناءات بغض النظر عن نوعها، ويتم إطلاق استثناء عام جديد، مع الاحتفاظ برسالة الاستثناء الأصلي فقط. ويُفقد تتبع المكدس (stacktrace) الأصلي، إلى جانب نوع الاستثناء الأصلي، وأي استثناء كان الاستثناء الأصلي مغلفاً له، وأي معلومات أخرى تم التقاطها في كائن الاستثناء.
try {
// القيام بشيء ما
} catch (Exception ex) {
// ربما تفعل بعض المعالجة المحلية للاستثناء
throw new Exception(ex.Message);
}
والطريقة الأفضل لإعادة إطلاق الاستثناءات دون فقدان المعلومات هي إطلاق الاستثناء الأصلي من جملة catch:
try {
// القيام بشيء ما
} catch (Exception ex) {
// القيام ببعض المعالجة المحلية للاستثناء
throw;
}
بدلاً من ذلك، يمكن إنشاء استثناء جديد يغلف الاستثناء الأصلي، بحيث يكون لأي معالجات أخرى إمكانية الوصول إلى كليهما:
try {
// القيام بشيء ما
} catch(Exception ex) {
// ربما تفعل بعض المعالجة المحلية للاستثناء
throw new CustomException(ex);
}
لغات أخرى
في لغة Go، يتم نشر الأخطاء عن طريق إرجاع كائن Error مع قيمة إرجاع الدالة العادية. ويمكن تجاهله، كما في هذا المثال.
f, _ := "I should not be here", errors.New("")
fmt.Print(f)
وفي حالة استدعاءات نظام C، يشار إلى الأخطاء من خلال كون قيمة إرجاع الاستدعاء NULL، ويتم تخزين معلومات الخطأ في متغير errno عام. يتأكد هذا الكود من أن الملف صالح قبل الوصول إليه، ولكن إذا فشل fopen، فسيتم ابتلاع الخطأ.
FILE *file = fopen("", "r");
if (file) {
// القيام بشيء ما بالملف
}
الأسباب
السبب الكامن الأكثر شيوعاً لابتلاع الخطأ هو الافتقار إلى أدوات وعمليات تسجيل أخطاء جيدة أثناء قيام المطور بإنناء البرمجيات. وعند مواجهة خطأ لا يمكن معالجته بسهولة، إذا كان لدى المطور أدوات تسجيل جيدة، فإن تسجيل خطأ غير متوقع لا يكلف المطور أي وقت أو جهد. ويجب أن يكون تسجيل الخطأ أمراً بسيطاً (استدعاء دالة واحدة)، وسريعاً (دون أي تأثير على أداء التطبيق)، وآمناً (لا يثير أي أخطاء أو استثناءات)، ويضمن حفظ جميع المعلومات، عن طريق تسجيل نوع الخطأ وأي بيانات ذات صلة مرتبطة به، وتتبع المكدس للخطأ (حتى يتمكن المطور من تحديد مكان حدوث الخطأ بالضبط والتعليمات التي أدت إليه)، والطابع الزمني للخطأ.
معالجات الاستثناءات المؤقتة
في اللغات التي تحتوي على استثناءات مفحوصة (checked exceptions)، يجب إدراج جميع الاستثناءات المثارة في دالة ما في توقيع تلك الدالة. وعند بناء نماذج البرمجيات وتطبيقها، يتغير الكود غالباً، مما يعني أن نوع الاستثناءات التي قد تثار في دالة ما يتغير أيضاً بشكل متكرر. ويؤدي الاضطرار إلى تعديل توقيع الدالة في كل مرة يتغير فيها شيء ما إلى إبطاء التطوير ويمكن أن يكون محبطاً، لذا فإن ابتلاع الاستثناءات كإجراء مؤقت أثناء إجراء تغييرات كبيرة على الكود أمر مغرٍ. وقد ينتهي الأمر بكود معالجة الاستثناءات المؤقت هذا في قاعدة الكود النهائية.
وحتى في اللغات التي لا تحتوي على استثناءات مفحوصة، يمكن أن يحدث إضافة معالجات استثناءات مؤقتة أثناء إجراء تغييرات كبيرة على الكود لتسريع بناء النماذج الأولية، مما قد يؤدي إلى إخفاء الأخطاء.
منع الانهيارات
في الحالات التي يجب ألا ينهار فيها البرنامج لأي سبب، فإن ابتلاع الخطأ هو ممارسة يمكن للمبرمج أن يقع فيها بسهولة. على سبيل المثال، من المتوقع أن تقوم إضافة (plugin) تعمل داخل تطبيق آخر بمعالجة جميع الأخطاء والاستثناءات بطريقة لا تؤدي إلى انهيار التطبيق الذي تم تضمينها فيه. ويعد التقاط الأخطاء والاستثناءات بشكل شامل نمطاً يسهل الوقوع فيه عند محاولة منع الانهيارات بأي ثمن، وعندما تجمع ذلك مع أدوات تسجيل أخطاء ضعيفة، يمكن أن يحدث إخفاء للأخطاء.
إخفاء التعقيد عن المستخدمين
عند عرض الأخطاء للمستخدمين، من المهم تحويل الأخطاء التقنية الغامضة إلى رسائل توضح ما حدث والإجراءات التي يمكن للمستخدم اتخاذها، إن وجدت، لإصلاح المشكلة. وأثناء إجراء هذا التحويل للأخطاء التقنية إلى رسائل مستخدم ذات معنى، غالباً ما يتم تجميع أخطاء معينة في أخطاء أكثر عمومية، ويمكن أن تؤدي هذه العملية إلى أن تصبح رسائل المستخدم عديمة الفائدة لدرجة أن المستخدم لا يعرف الخطأ الذي حدث أو كيفية إصلاحه. وبالنسبة للمستخدم، يعتبر الخطأ قد تم ابتلاعه.