معالجة الأخطاء في C#: من try/catch إلى التقنيات الاحترافية
في عالم التطوير الصناعي باستخدام C# و.NET، فإن معالجة الأخطاء ليست مجرد بناء try/catch. إنها فلسفة كاملة تحدد موثوقية وقابلية القراءة وتحمل الأخطاء في الكود الخاص بك. على المستوى المتقدم، لا يجب على المطور مجرد التقاط الاستثناءات، بل يجب أيضًا تصميمها وتصفيتها وتسجيلها واستعادة حالة التطبيق بشكل صحيح. في هذه المقالة، سنقوم بتحليل الجوانب الرئيسية لمعالجة الأخطاء في C# والتي ستساعدك على كتابة كود أكثر جودة واحترافية.
1. أعمق في try/catch/finally: إدارة الموارد والسياق
البناء الأساسي try-catch-finally مألوف للجميع، لكن دعنا ننظر إليه من زاوية مختلفة. الهدف الرئيسي من كتلة finally هو التحرير المضمون للموارد، حتى في حالة حدوث استثناء. في الإصدارات الحديثة من C#، يتم استخدام using في كثير من الأحيان لهذا الغرض، لكن finally يظل لا غنى عنه عند العمل مع الموارد غير المُدارة أو منطق الإنهاء المعقد.
public void ProcessFile(string path){ FileStream? fileStream = null; try { fileStream = File.OpenRead(path); // منطق معالجة الملف } catch (FileNotFoundException ex) { // معالجة محددة: الملف غير موجود Log.Error($"الملف {path} غير موجود: {ex.Message}"); throw; // إعادة إلقاء الاستثناء مع الحفاظ على مكدس الاستدعاءات } catch (UnauthorizedAccessException) { Log.Warn($"لا يوجد وصول إلى الملف: {path}"); // يمكن تنفيذ إجراء بديل دون إعادة إلقاء الاستثناء return; } finally { // التحرير المضمون للمورد fileStream?.Dispose(); }}لاحظ استخدام throw; (بدون وسيط) - هذه نقطة رئيسية. على عكس throw ex;، فإن البناء throw; يحافظ على مكدس الاستدعاءات الأصلي، وهو أمر بالغ الأهمية لتصحيح الأخطاء. لا تستخدم أبدًا throw ex; في المشاريع الحقيقية، لأنه يدمر معلومات مكان حدوث الخطأ الأصلي.
2. الاستثناءات المخصصة: تصميم نموذج مجال الأخطاء
في التطبيقات الكبيرة، غالبًا ما تكون الاستثناءات القياسية في .NET غير كافية. إنشاء فئات استثناء خاصة بك يسمح بنقل سياق الخطأ بدقة ويبسط المعالجة في المستويات العليا. يجب أن يكون الاستثناء المخصص الجيد قابلاً للتسلسل، وأن يحتوي على رسالة ذات معنى، وأن يحمل معلومات إضافية (مثل معرف الكيان).
[Serializable]public class OrderProcessingException : Exception{ public int OrderId { get; } public string FaultyStep { get; }
public OrderProcessingException(int orderId, string faultyStep, string message) : base(message) { OrderId = orderId; FaultyStep = faultyStep; }
public OrderProcessingException(int orderId, string faultyStep, string message, Exception inner) : base(message, inner) { OrderId = orderId; FaultyStep = faultyStep; }
// مُنشئ لإلغاء التسلسل protected OrderProcessingException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { OrderId = info.GetInt32(nameof(OrderId)); FaultyStep = info.GetString(nameof(FaultyStep)) ?? string.Empty; }
public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { base.GetObjectData(info, context); info.AddValue(nameof(OrderId), OrderId); info.AddValue(nameof(FaultyStep), FaultyStep); }}
// الاستخدامpublic void ValidateOrder(int orderId){ try {