إنشاء استثناءات مخصصة (Custom Exceptions) لتأمين بيئة العمل


إنشاء استثناءات مخصصة (Custom Exceptions) لتأمين بيئة العمل

إنشاء استثناءات مخصصة (Custom Exceptions) لتأمين بيئة العمل

ماذا سنتعلم؟ في هذا الدرس، سنتعلم كيفية إنشاء استثناءات مخصصة (Custom Exceptions) في بايثون لتعزيز أمان تطبيقاتنا وتحسين قابلية صيانتها، وذلك من خلال توفير آليات واضحة للتعامل مع الأخطاء المتعلقة بالتحقق من المدخلات وحالة النظام.

الخطوة 1: تعريف الاستثناءات المخصصة الأساسية

تُعد الاستثناءات المخصصة طريقة فعالة لتمثيل الأخطاء الخاصة بمنطق عمل تطبيقك. بدلاً من الاعتماد على استثناءات بايثون العامة، يمكنك إنشاء استثناءات ذات معنى توفر سياقاً أوضح للمشكلة، مما يسهل عملية تصحيح الأخطاء وتحسين تجربة المطور. سنبدأ بتعريف فئة استثناء أساسية للأمان ثم استثناء محدد لمدخلات المستخدم غير الصالحة.

ملاحظة تقنية: يُنصح دائماً بجعل الاستثناءات المخصصة ترث من فئة Exception مباشرةً، أو من فئة استثناء مخصصة أخرى ترث من Exception، بدلاً من BaseException. هذا يضمن إمكانية التقاطها بواسطة عبارة except Exception: العامة.

class SecurityError(Exception):
    """
    فئة أساسية للاستثناءات المتعلقة بالأمان في التطبيق.
    جميع استثناءات الأمان المخصصة سترث منها.
    """
    pass

class InvalidUserInputError(SecurityError):
    """
    استثناء مخصص يُطلق عند إدخال بيانات غير صالحة من المستخدم.
    يوفر تفاصيل إضافية مثل اسم الحقل والقيمة التي تسببت في الخطأ.
    """
    def __init__(self, message="بيانات المستخدم المدخلة غير صالحة.", field_name=None, value=None):
        # استدعاء مُنشئ الفئة الأصلية (Exception أو SecurityError)
        super().__init__(message)
        # تخزين تفاصيل إضافية للمساعدة في تصحيح الأخطاء
        self.field_name = field_name
        self.value = value

    def __str__(self):
        # تخصيص تمثيل السلسلة النصية للاستثناء ليكون أكثر إفادة
        if self.field_name and self.value is not None:
            return f"{super().__str__()} [الحقل: '{self.field_name}', القيمة: '{self.value}']"
        return super().__str__()

الخطوة 2: تطبيق الاستثناءات في وظائف التحقق

الآن بعد أن عرفنا استثناءاتنا المخصصة، يمكننا دمجها في وظائف التحقق الخاصة بنا. هذا يجعل رمز التحقق أكثر وضوحاً، حيث يحدد نوع الخطأ بدقة بدلاً من إرجاع قيم منطقية أو استخدام استثناءات عامة. سنقوم بإنشاء دالة للتحقق من اسم المستخدم تطلق InvalidUserInputError عند فشل التحقق.

def validate_username(username):
    """
    تتحقق من صحة اسم المستخدم وفقاً لقواعد محددة.
    - يجب أن يكون اسم المستخدم نصاً.
    - يجب أن يتراوح طوله بين 4 و 14 حرفاً.
    يطلق InvalidUserInputError إذا كانت البيانات غير صالحة.
    """
    # التحقق من نوع البيانات
    if not isinstance(username, str):
        raise InvalidUserInputError(
            "اسم المستخدم يجب أن يكون نصاً (string).",
            field_name="username",
            value=username
        )

    # التحقق من طول اسم المستخدم
    if len(username) < 4 or len(username) > 14:
        raise InvalidUserInputError(
            "طول اسم المستخدم يجب أن يتراوح بين 4 و 14 حرفاً.",
            field_name="username",
            value=username
        )

    # يمكن إضافة المزيد من قواعد التحقق هنا (مثل الأحرف المسموح بها، عدم وجود مسافات)
    # مثال:
    # if not username.isalnum():
    #     raise InvalidUserInputError(
    #         "اسم المستخدم يجب أن يحتوي على أحرف وأرقام فقط.",
    #         field_name="username",
    #         value=username
    #     )

    print(f"اسم المستخدم '{username}' صالح للتحقق الأولي.")
    return True

الخطوة 3: معالجة الاستثناءات المخصصة

الخطوة الأخيرة هي كيفية التقاط هذه الاستثناءات المخصصة والتعامل معها في الأجزاء العليا من تطبيقك. باستخدام كتل try-except، يمكنك توفير منطق معالجة خطأ محدد لكل نوع استثناء، مما يسمح لك بالاستجابة بشكل مناسب (مثل عرض رسالة خطأ للمستخدم أو تسجيل المشكلة).

ملاحظة تقنية: عند معالجة الاستثناءات، يمكنك التقاط الاستثناءات الأكثر تحديداً أولاً، ثم الاستثناءات الأكثر عمومية. هذا يسمح لك بالتعامل مع حالات الخطأ الخاصة بشكل دقيق قبل الوقوع في معالجة عامة.

def process_user_registration(username):
    """
    محاكاة لعملية تسجيل مستخدم مع معالجة الاستثناءات المخصصة.
    """
    print(f"\nمحاولة تسجيل المستخدم: '{username}'")
    try:
        # محاولة التحقق من اسم المستخدم
        validate_username(username)
        # إذا نجح التحقق، نفترض أن التسجيل قد اكتمل
        print(f"✅ تم تسجيل المستخدم '{username}' بنجاح.")
    except InvalidUserInputError as e:
        # التقاط الاستثناء المحدد لمدخلات المستخدم غير الصالحة
        print(f"❌ خطأ في تسجيل المستخدم: {e}")
        if e.field_name:
            print(f"  ↔️ تفاصيل الخطأ: الحقل '{e.field_name}' بقيمة '{e.value}' تسبب في المشكلة.")
    except SecurityError as e:
        # التقاط أي استثناءات أخرى ترث من SecurityError
        print(f"⚠️ خطأ أمان عام أثناء تسجيل المستخدم: {e}")
    except Exception as e:
        # التقاط أي استثناءات غير متوقعة أخرى
        print(f"🚨 خطأ غير متوقع أثناء تسجيل المستخدم: {e}")

# أمثلة للاستخدام:
# حالة صحيحة
process_user_registration("john_doe123")
# حالة اسم مستخدم قصير جداً
process_user_registration("abc")
# حالة اسم مستخدم طويل جداً
process_user_registration("thisisareallylongusername")
# حالة اسم مستخدم ليس نصاً
process_user_registration(12345)

الكود النهائي الكامل

إليك السكربت كاملاً، والذي يجمع جميع الأجزاء التي قمنا ببنائها خطوة بخطوة:

# -*- coding: utf-8 -*-
#
# الدرس: إنشاء استثناءات مخصصة (Custom Exceptions) لتأمين بيئة العمل

class SecurityError(Exception):
    """
    فئة أساسية للاستثناءات المتعلقة بالأمان في التطبيق.
    جميع استثناءات الأمان المخصصة سترث منها.
    """
    pass

class InvalidUserInputError(SecurityError):
    """
    استثناء مخصص يُطلق عند إدخال بيانات غير صالحة من المستخدم.
    يوفر تفاصيل إضافية مثل اسم الحقل والقيمة التي تسببت في الخطأ.
    """
    def __init__(self, message="بيانات المستخدم المدخلة غير صالحة.", field_name=None, value=None):
        # استدعاء مُنشئ الفئة الأصلية (Exception أو SecurityError)
        super().__init__(message)
        # تخزين تفاصيل إضافية للمساعدة في تصحيح الأخطاء
        self.field_name = field_name
        self.value = value

    def __str__(self):
        # تخصيص تمثيل السلسلة النصية للاستثناء ليكون أكثر إفادة
        if self.field_name and self.value is not None:
            return f"{super().__str__()} [الحقل: '{self.field_name}', القيمة: '{self.value}']"
        return super().__str__()

def validate_username(username):
    """
    تتحقق من صحة اسم المستخدم وفقاً لقواعد محددة.
    - يجب أن يكون اسم المستخدم نصاً.
    - يجب أن يتراوح طوله بين 4 و 14 حرفاً.
    يطلق InvalidUserInputError إذا كانت البيانات غير صالحة.
    """
    # التحقق من نوع البيانات
    if not isinstance(username, str):
        raise InvalidUserInputError(
            "اسم المستخدم يجب أن يكون نصاً (string).",
            field_name="username",
            value=username
        )

    # التحقق من طول اسم المستخدم
    if len(username) < 4 or len(username) > 14:
        raise InvalidUserInputError(
            "طول اسم المستخدم يجب أن يتراوح بين 4 و 14 حرفاً.",
            field_name="username",
            value=username
        )

    # يمكن إضافة المزيد من قواعد التحقق هنا (مثل الأحرف المسموح بها، عدم وجود مسافات)
    # مثال:
    # if not username.isalnum():
    #     raise InvalidUserInputError(
    #         "اسم المستخدم يجب أن يحتوي على أحرف وأرقام فقط.",
    #         field_name="username",
    #         value=username
    #     )

    print(f"اسم المستخدم '{username}' صالح للتحقق الأولي.")
    return True

def process_user_registration(username):
    """
    محاكاة لعملية تسجيل مستخدم مع معالجة الاستثناءات المخصصة.
    """
    print(f"\nمحاولة تسجيل المستخدم: '{username}'")
    try:
        # محاولة التحقق من اسم المستخدم
        validate_username(username)
        # إذا نجح التحقق، نفترض أن التسجيل قد اكتمل
        print(f"✅ تم تسجيل المستخدم '{username}' بنجاح.")
    except InvalidUserInputError as e:
        # التقاط الاستثناء المحدد لمدخلات المستخدم غير الصالحة
        print(f"❌ خطأ في تسجيل المستخدم: {e}")
        if e.field_name:
            print(f"  ↔️ تفاصيل الخطأ: الحقل '{e.field_name}' بقيمة '{e.value}' تسبب في المشكلة.")
    except SecurityError as e:
        # التقاط أي استثناءات أخرى ترث من SecurityError
        print(f"⚠️ خطأ أمان عام أثناء تسجيل المستخدم: {e}")
    except Exception as e:
        # التقاط أي استثناءات غير متوقعة أخرى
        print(f"🚨 خطأ غير متوقع أثناء تسجيل المستخدم: {e}")

# أمثلة للاستخدام لتوضيح سلوك الاستثناءات
process_user_registration("john_doe123") # اسم مستخدم صالح
process_user_registration("abc") # اسم مستخدم قصير جداً
process_user_registration("thisisareallylongusername") # اسم مستخدم طويل جداً
process_user_registration(12345) # اسم مستخدم ليس نصاً
process_user_registration("admin!") # مثال لاسم مستخدم صالح حالياً (لو لم يتم تفعيل التحقق من الأحرف)

النتيجة المتوقعة

عند تشغيل السكربت أعلاه، ستحصل على مخرجات توضح كيفية معالجة كل حالة من حالات تسجيل المستخدم، مع رسائل خطأ واضحة ومحددة للاستثناءات المخصصة التي تم إطلاقها والتقاطها:

محاولة تسجيل المستخدم: 'john_doe123'
اسم المستخدم 'john_doe123' صالح للتحقق الأولي.
✅ تم تسجيل المستخدم 'john_doe123' بنجاح.

محاولة تسجيل المستخدم: 'abc'
❌ خطأ في تسجيل المستخدم: طول اسم المستخدم يجب أن يتراوح بين 4 و 14 حرفاً. [الحقل: 'username', القيمة: 'abc']
  ↔️ تفاصيل الخطأ: الحقل 'username' بقيمة 'abc' تسبب في المشكلة.

محاولة تسجيل المستخدم: 'thisisareallylongusername'
❌ خطأ في تسجيل المستخدم: طول اسم المستخدم يجب أن يتراوح بين 4 و 14 حرفاً. [الحقل: 'username', القيمة: 'thisisareallylongusername']
  ↔️ تفاصيل الخطأ: الحقل 'username' بقيمة 'thisisareallylongusername' تسبب في المشكلة.

محاولة تسجيل المستخدم: '12345'
❌ خطأ في تسجيل المستخدم: اسم المستخدم يجب أن يكون نصاً (string). [الحقل: 'username', القيمة: '12345']
  ↔️ تفاصيل الخطأ: الحقل 'username' بقيمة '12345' تسبب في المشكلة.

محاولة تسجيل المستخدم: 'admin!'
اسم المستخدم 'admin!' صالح للتحقق الأولي.
✅ تم تسجيل المستخدم 'admin!' بنجاح.
    

توضح هذه النتيجة كيف أن الاستثناءات المخصصة توفر رسائل خطأ مفصلة وذات سياق، مما يسهل على المطورين تحديد المشكلة وحلها بسرعة، ويعزز أمان التطبيق من خلال التعامل مع المدخلات غير الصالحة بشكل واضح ومنظم.