فلسفة الكود
الاستثناءات بدل مؤشرات الفشل
١٣ أكتوبر ٢٠٢٦ · 5 دقائق قراءة
الفشل الصامت أسوأ من الفشل الصريح
ابدأ بمثال بسيط:
async def find_user(uid):
user = await db.fetch(uid)
if not user:
return None # الفشل "بينجح" هنا
return user
المشكلة مش في None — في إنها صامتة. المتصل اللي بياخد القيمة دي ميعرفش إنها نتيجة «مفيش حد» ولا نتيجة «حصل غلط». فبيلبّس الحالتين في نفس القرار:
user = await find_user(uid)
if not user:
await c.reply("المستخدم محظور") # غلط كمان لما يكون الراجل مجرد غير نشط
كود كهذا «بينجح» بشكل طبيعي في الإنتاج — لأن الـ None وقع حيث نجاح كاذب. ده أخطر من خطأ صريح يوقّظك أول ما يحصل.
القاعدة اللي طبقناها
مصدر واحد للحقيقة في نوع الخطأ، وأخدنا قرار: المتصل لازم يتبلغ بالطبيعة الحقيقية للحالة عبر استثناءات محددة:
class UserNotFoundError(Exception):
pass
async def find_user(uid):
user = await db.fetch(uid)
if not user:
raise UserNotFoundError(uid)
return user
والمعالج المركزي بيترجم كل نوع لرسالة مناسبة قدام المستخدم:
async def error_handler(e, c):
if isinstance(e, UserNotFoundError):
return await c.reply("مش لاقيين المستخدم دا")
return await c.reply("حصل غلط، جرب تاني")
لماذا الاستثناءات المحددة، مش واحدة عامة؟
الاستثناء المحدد بيمرر ليك طبيعة الفشل في سياقه: «المستخدم غير موجود» مختلفة جذريًا عن «الصلاحية ناقصة» — اختلاف في الرد المناسب. لو مسكت بـ except Exception عام، بيروح كل التمييز، ونرجع لنفس الشرط المطاطي اللي كنا بنندهرب منه.
الميزان الوحيد
المستخدم مش بيدي الـ traceback — الـ error handler هو اللي بيحوّله لرسالة. فالموازنة:
- في الطبقة العميقة: ارفع الاستثناء المحدد، ولا تمسكه من غير قرار.
- في الطبقة الحدّية (النقل/الواجهة): ترجمه لرسالة بكل وضوح — مكان واحد للفشل المرئي.
دي الدائرة الكاملة: فشل محدد في الأسفل، فشل مُبلّغ في الأعلى، وصفر فشل صامت في المنتصف.