كان كودنا بيتاً من ورق: كيف أنقذتنا ‘برمجة السكك الحديدية’ من جحيم الـ try-catch؟

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

الدنيا مقلوبة فوق راسنا، والعميل على الخط كل خمس دقائق. بعد ساعات من التنبيش في الكود، وجدنا المصيبة. كانت هناك كتلة try-catch مدفونة في عمق سبع دوال متداخلة، تقوم بـ “ابتلاع” الخطأ (Swallowing the exception) دون تسجيله، وتُرجع قيمة null. هذه القيمة الفارغة كانت تتسبب في انهيار المنظومة في مرحلة لاحقة، مما جعل تتبع المشكلة أشبه بالبحث عن إبرة في كومة قش. وقتها، نظرت إلى الكود الذي كتبناه وقلت لزملائي: “يا جماعة، كودنا هذا مثل بيت من ورق، أي نسمة هواء قوية بتهدمه”.

هذه الحادثة كانت نقطة تحول في طريقة تفكيرنا في معالجة الأخطاء. ومن هنا بدأت رحلتنا مع ما يسمى بـ “برمجة السكك الحديدية” (Railway Oriented Programming).

ما هي مشكلة كتل try-catch المتداخلة؟ (هرم الهلاك)

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

  1. التحقق من صحة المدخلات (Validation).
  2. التحقق من أن البريد الإلكتروني غير مستخدم.
  3. إنشاء المستخدم في قاعدة البيانات.
  4. إرسال بريد إلكتروني ترحيبي.

بالطريقة التقليدية، قد يبدو الكود هكذا (الكود التالي مجرد مثال توضيحي بلغة تشبه C#):


public string RegisterUser(UserData data)
{
    try
    {
        // Step 1: Validate Input
        if (!Validate(data))
        {
            return "Error: Invalid data";
        }

        try
        {
            // Step 2: Check if email exists
            if (EmailExists(data.Email))
            {
                return "Error: Email already exists";
            }

            try
            {
                // Step 3: Save user to DB
                var user = CreateUserInDatabase(data);
                if (user == null)
                {
                    return "Error: Could not save user";
                }

                try
                {
                    // Step 4: Send welcome email
                    SendEmail(user.Email);
                    return "Success: User registered";
                }
                catch (Exception ex)
                {
                    // مشكلة! قد نفشل في إرسال الإيميل لكن المستخدم تم تسجيله!
                    Log.Error("Failed to send email", ex);
                    return "Warning: User registered but failed to send email";
                }
            }
            catch (Exception ex)
            {
                Log.Error("Failed to save user", ex);
                return "Error: Database failure";
            }
        }
        catch (Exception ex)
        {
            Log.Error("Failed to check email", ex);
            return "Error: System failure";
        }
    }
    catch (Exception ex)
    {
        Log.Error("An unexpected error occurred", ex);
        return "Error: Unexpected error";
    }
}

انظر إلى هذه الفوضى! هذا ما نسميه “هرم الهلاك” (Pyramid of Doom). الكود صعب القراءة، منطق العمل (Business Logic) مختلط تماماً مع منطق معالجة الأخطاء، وكل كتلة try-catch تزيد من التعقيد وتفتح باباً جديداً لابتلاع الأخطاء عن طريق الخطأ.

الحل: أهلاً بكم في “برمجة السكك الحديدية” 🚂

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

  • مسار النجاح (Success Track): تسير عليه البيانات بسلاسة من محطة (دالة) إلى أخرى طالما أن كل شيء على ما يرام.
  • مسار الفشل (Failure Track): بمجرد حدوث أي خطأ في أي محطة، يتم تحويل القطار (النتيجة) إلى مسار الفشل، ويتجاوز كل المحطات التالية، ويذهب مباشرة إلى الوجهة النهائية للفشل.

الخطوة الأولى: النوع `Result`

لتحقيق ذلك، نتوقف عن إرجاع القيم مباشرة (مثل string أو User) أو إلقاء استثناءات (Exceptions). بدلاً من ذلك، كل دالة لدينا تقوم بإرجاع نوع خاص يغلف النتيجة، دعنا نسميه Result. هذا النوع يمكن أن يكون في إحدى حالتين:

  1. Success: ويحمل القيمة الناجحة.
  2. Failure: ويحمل معلومات الخطأ.

مثال بسيط لتعريف هذا النوع في C#:


public class Result<TSuccess, TError>
{
    public bool IsSuccess { get; }
    public TSuccess Value { get; }
    public TError Error { get; }

    private Result(TSuccess value)
    {
        IsSuccess = true;
        Value = value;
        Error = default;
    }

    private Result(TError error)
    {
        IsSuccess = false;
        Value = default;
        Error = error;
    }

    public static Result<TSuccess, TError> Success(TSuccess value) => new Result<TSuccess, TError>(value);
    public static Result<TSuccess, TError> Failure(TError error) => new Result<TSuccess, TError>(error);
}

الخطوة الثانية: الربط (Binding)

السحر الحقيقي يكمن في كيفية ربط هذه الدوال معاً. نحن بحاجة إلى دالة وسيطة (Higher-Order Function) تأخذ نتيجة ودالة أخرى. إذا كانت النتيجة “نجاح”، فإنها تطبق الدالة على القيمة الموجودة بداخلها. أما إذا كانت “فشل”، فإنها تتجاهل الدالة الجديدة وتعيد الفشل كما هو.

هذه الدالة غالباً ما تسمى Bind أو Then. لنضفها إلى كلاس `Result` الخاص بنا:


// نضيف هذه الدالة داخل كلاس Result
public Result<TNewSuccess, TError> Then<TNewSuccess>(Func<TSuccess, Result<TNewSuccess, TError>> func)
{
    if (!IsSuccess)
    {
        // إذا كنا في مسار الفشل، نمرر الخطأ كما هو
        return Result<TNewSuccess, TError>.Failure(Error);
    }

    // إذا كنا في مسار النجاح، ننفذ الدالة التالية
    return func(Value);
}

الكود بعد استخدام برمجة السكك الحديدية

الآن، دعونا نعيد كتابة مثال تسجيل المستخدم باستخدام هذا النمط. أولاً، نعيد كتابة كل خطوة لتكون دالة منفصلة تعيد `Result`:


// كل دالة الآن تتبع نمط السكك الحديدية
private Result<UserData, string> ValidateInput(UserData data) { ... }
private Result<UserData, string> CheckEmail(UserData data) { ... }
private Result<User, string> CreateUserInDatabase(UserData data) { ... }
private Result<User, string> SendWelcomeEmail(User user) { ... }

والآن، انظر إلى جمال وبساطة الدالة الرئيسية:


public Result<User, string> RegisterUser(UserData data)
{
    return ValidateInput(data)
        .Then(CheckEmail) // إذا نجحت الأولى، نفذ الثانية
        .Then(CreateUserInDatabase) // إذا نجحت الثانية، نفذ الثالثة
        .Then(SendWelcomeEmail); // وهكذا...
}

يا سلام! الكود أصبح خطياً، واضحاً، وسهل القراءة. منطق العمل (Business Logic) هو ما تراه أمامك في سلسلة الدوال، ومنطق معالجة الأخطاء تم تغليفه بالكامل داخل دالة `Then`. إذا فشلت أي خطوة، ستتوقف السلسلة فوراً وتعود النتيجة النهائية كـ `Failure` مع تفاصيل الخطأ من تلك الخطوة الفاشلة.

الكود الآن لا يقرأ كتعليمات برمجية فقط، بل يقرأ كقصة أو كإجراءات عمل واضحة. وهذا هو هدفنا كمهندسي برمجيات.

نصائح من خبرة أبو عمر

بعد سنوات من استخدام هذا النمط في مشاريع مختلفة، إليكم بعض النصائح العملية:

1. لا تستبدل كل شيء مرة واحدة

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

2. أنشئ مكتبة مشتركة للـ `Result`

قم بإنشاء كلاس `Result` ودواله المساعدة (مثل `Then`, `Map`, `Tap`) في مكتبة مشتركة (Shared Library) يمكن إعادة استخدامها في كل مشاريعك. هذا يوفر الوقت ويضمن التناسق.

3. تعامل مع تسجيل الأخطاء (Logging) بذكاء

يمكنك إضافة دالة مساعدة مثل `Tap` أو `Inspect` لتسجيل الأخطاء أو النجاحات دون التأثير على مسار السكة. مثال:


// دالة Tap لا تغير النتيجة، فقط "تتلصص" عليها
public Result<TSuccess, TError> Tap(Action<TSuccess> action)
{
    if (IsSuccess)
    {
        action(Value);
    }
    return this;
}

// يمكنك استخدامها هكذا
.Then(CreateUserInDatabase)
.Tap(user => Log.Info($"User {user.Id} created successfully")) // تسجيل النجاح
.Then(SendWelcomeEmail)

4. اعرف متى لا تستخدمها

هذا النمط رائع للأخطاء المتوقعة والمنطقية (مثل “بريد إلكتروني مستخدم” أو “رصيد غير كافٍ”). أما للأخطاء الكارثية وغير المتوقعة (مثل `OutOfMemoryException` أو فشل في الاتصال بقاعدة البيانات بشكل كامل)، فلا يزال من المنطقي وجود معالج أخطاء عام (Global Exception Handler) أو كتلة try-catch في أعلى مستوى من التطبيق لالتقاط هذه الحالات ومنع انهيار التطبيق بالكامل.

الخلاصة: من بيت الورق إلى قلعة حصينة 🏰

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

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

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

برمجة وقواعد بيانات

تحديثات قاعدة البيانات بدون توقف: كيف أنقذنا نمط التوسيع والتعاقد (Expand/Contract) من جحيم التوقفات المجدولة؟

هل سئمت من إيقاف الخدمة مع كل تحديث لهيكلة قاعدة البيانات؟ أشارككم قصة حقيقية وكيف أنقذنا نمط التوسيع والتعاقد (Expand/Contract) من ليالي النشر الطويلة والمُجهدة،...

4 يونيو، 2026 قراءة المزيد
الشبكات والـ APIs

كانت إعادة المحاولة كارثة: كيف أنقذتنا مفاتيح عدم تكرار العمليات (Idempotency Keys) من جحيم الفواتير المزدوجة؟

أشارككم قصة حقيقية من الخنادق البرمجية، يوم كاد خطأ بسيط في إعادة محاولة طلبات الدفع أن يكلفنا سمعتنا وأموال عملائنا. اكتشفوا معنا كيف كانت مفاتيح...

4 يونيو، 2026 قراءة المزيد
الحوسبة السحابية

من التوقف التام إلى النجاة: كيف أنقذتنا استراتيجية “الضوء المرشد” (Pilot Light) يوم انقطعت السحابة؟

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

4 يونيو، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

كانت مهمتي البرمجية للاختبار مجرد كود: كيف أنقذني توثيق القرارات من جحيم الصمت بعد المقابلة؟

أشارككم قصة حقيقية من بداياتي، وكيف تعلمت بالطريقة الصعبة أن المهمة البرمجية ليست مجرد كتابة كود، بل هي فرصة لإظهار طريقة تفكيرك. اكتشف كيف يمكن...

4 يونيو، 2026 قراءة المزيد
التكنلوجيا المالية Fintech

من الانتظار لأيام إلى الدفع في ثوانٍ: كيف أنقذتنا شبكات الدفع الفوري من جحيم التحويلات البنكية؟

أسرد لكم من واقع تجربتي كـ "أبو عمر"، كيف عانينا من بطء وتكلفة التحويلات البنكية الدولية، وكيف جاءت شبكات الدفع الفوري ومعيار ISO 20022 لتكون...

4 يونيو، 2026 قراءة المزيد
البنية التحتية وإدارة السيرفرات

كان كل خادم لدينا ‘ندفة ثلج’ فريدة: كيف أنقذنا ‘الكود كبنية تحتية’ (IaC) من جحيم الانجراف اليدوي؟

في هذه المقالة، أشارككم قصة حقيقية من قلب المعركة التقنية مع "خوادم ندفات الثلج" الفوضوية. سنغوص في مفهوم "الكود كبنية تحتية" (IaC) وكيف أن أدوات...

4 يونيو، 2026 قراءة المزيد
اختبارات الاداء والجودة

كانت تغطية الاختبارات 100% لكن الأخطاء تتسرب: كيف أنقذنا “الاختبار الطفري” من جحيم الثقة الزائفة؟

كنا نظن أن تغطية الاختبار بنسبة 100% هي درعنا الواقي، لكن الأخطاء كانت تتسلل إلى الإنتاج كاللصوص في ليل بهيم. اكتشف كيف أنقذنا "الاختبار الطفري"...

4 يونيو، 2026 قراءة المزيد
البودكاست