ما وراء الـ Try-Catch: هندسة معماريات عالمية مرنة باستخدام Effect و Expo
توقف عن الاعتماد على كتل catch الهشة وابدأ في بناء تطبيقات عالمية (Universal Applications) مرنة حقاً. سأغوص في كيفية دمج معالجة الأخطاء الآمنة نوعياً (Type-safe) من Effect مع انتشار Expo الشامل للقضاء على مفاجآت وقت التشغيل (Runtime).

ما وراء الـ Try-Catch: هندسة معماريات عالمية مرنة
لقد قضيت الجزء الأكبر من عقد من الزمان في بناء تطبيقات الجوال والويب، وإذا كان هناك شيء واحد يؤرقني، فهو كتلة الـ try-catch. إنها بمثابة أسلوب "تمنّي الأفضل" في البرمجة. تقوم بتغليف بعض الكود، ثم تلتقط خطأ unknown غير معروف، وتدعو الله أن تغطي منطق الاحتياط (Fallback logic) الخاص بك كل حالة حافة، بدءاً من اتصال 5G متقطع في نفق وصولاً إلى استجابة JSON مشوهة من API قديم.
في عملي الأخير مع Expo، أدركت أن الطبيعة العالمية لتطبيقاتنا — التي تعمل على iOS و Android و Web — لا تضاعف انتشارنا فحسب، بل تضاعف مساحة سطح الفشل ثلاث مرات. عندها توقفت عن الاعتماد على الـ Promises القياسية وبدأت في التوجه نحو Effect.
المشكلة في معالجة الأخطاء "الاعتيادية"
في تطبيق Expo القياسي، عادةً ما يبدو جلب البيانات (Data fetching) هكذا:
هذا ما نسميه فشلاً "غير محدد النوع" (Untyped failure). المترجم (Compiler) لا يمكنه مساعدتك هنا. إذا نسيت معالجة نوع معين من الأخطاء، سينهار التطبيق، أو والأسوأ من ذلك، سيبقى في حالة تحميل (Loading state) دائمة. عندما تبني تطبيقات عابرة للمنصات (Cross-platform)، تظهر هذه الإخفاقات بشكل مختلف باختلاف البيئات.
نقطة التحول: Effect
Effect هي مكتبة برمجة وظيفية (Functional programming) للغة TypeScript تتعامل مع الأخطاء كـ "مواطنين من الدرجة الأولى". بدلاً من رمي الاستثناءات (Exceptions) التي تفجر الـ Stack، يقوم Effect بإعادة الأخطاء ضمن توقيع النوع (Type signature).
عندما قرنت هذا مع Expo، تحولت بنية تطبيقاتي العالمية من "دفاعية" إلى "حتمية" (Deterministic).
الخطوة 1: نمذجة النجاح والفشل
بدلاً من دالة async قد ترمي خطأً، أقوم بتعريف برنامج. إليك كيف أعالج التفاعل مع Native module (مثل Expo SecureStore) باستخدام Effect:
الآن، نوع getAuthToken ليس مجرد Promise<string>. بل هو Effect<string | null, StorageError, never>. مترجم TypeScript الآن يجبرني على معالجة StorageError قبل أن أتمكن من استخدام النتيجة. هذا يغير قواعد اللعبة للمهندسين الخبراء المسؤولين عن استقرار التطبيق.
الخطوة 2: الطبقة العالمية (The Universal Layer)
أحد أكبر الاكتشافات التي حققتها كان استخدام Layers في Effect لمعالجة المنطق الخاص بكل بيئة في Expo. تخيل أن لديك خدمة تسجيل (Logging service) تستخدم مسجل نظام أصلي (Native logger) على iOS/Android ولكنها تستخدم Console logging القياسي على الويب.
باستخدام امتدادات الملفات الخاصة بالمنصة في Expo (مثل .web.ts مقابل .ts)، يمكنني توفير الـ Layer الصحيح عند جذور التطبيق. يبقى منطق العمل (Business logic) الخاص بي نقياً تماماً ومستقلاً عن المنصة.
لماذا يهم هذا مطوري Expo
يتيح لنا Expo التطور والنمو بسرعة مذهلة. لكن السرعة غالباً ما تؤدي إلى الهشاشة. من خلال دمج Effect:
- القضاء على الـ "Zombies": لا مزيد من المكونات العالقة في حالة التحميل لأن Promise رُفض بصمت.
- سياسات إعادة المحاولة (Retry Policies): يحتوي Effect على أدوات مدمجة لإعادة محاولة طلبات الشبكة الفاشلة مع Exponential backoff. القيام بذلك يدوياً في
useEffectهو كابوس؛ في Effect، الأمر بسيط مثل.pipe(Effect.retry(Schedule.exponential(1000))). - التتبع (Traceability): عندما يواجه مستخدم على جهاز Android 11 في منطقة ذات نطاق ترددي منخفض خطأً ما، فإن التتبع المدمج في Effect يخبرني بالضبط بأي جزء من المسار (Pipeline) فشل، وليس فقط "Unexpected token < in JSON".
أفكار ختامية
إن تجاوز الـ try-catch لا يقتصر فقط على استخدام مكتبة جديدة؛ بل يتعلق بتغيير العقلية. يتعلق الأمر بالاعتراف بأن الفشل هو جزء متوقع من دورة حياة تطبيق الجوال.
إذا كنت تبني تطبيقات عالمية معقدة باستخدام Expo، فإنني أوصي بشدة بالاطلاع على Effect. إنه يحول "فوضى" التطوير العابر للمنصات إلى محرك آمن نوعياً (Type-safe) وقابل للتنبؤ. سيشكرك مستخدموك (وحصتك في Sentry).
استمر في البناء، واستمر في تجربة الأشياء، ولكن ابدأ في التقاط الأخطاء بشكل صحيح.