فحص التقنيات المستخدمة لتبسيط الشرطيات المعقدة

مكتمل

تعني إعادة بناء الشروط المعقدة تطبيق تقنيات منظمة لجعل الكود أبسط وأكثر انبساطا دون تغيير سلوكه. هناك العديد من الأساليب التي تم اختبارها على مدار الوقت لتبسيط الشروط المعقدة.

استخدام جمل الحماية (الإرجاع المبكر) لتسطيح التداخل

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

فحص فوائد بنود الحماية

تقلل بنود الحماية من مستويات المسافة البادئة بشكل كبير.

على سبيل المثال ، ضع في اعتبارك كتلة التعليمات البرمجية التالية:

// Nested conditional example (arrowhead pattern)
if (X) {
    if (Y) {
        if (Z) {
            // do something
        }
    }
}

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

فيما يلي نموذج التعليمات البرمجية قبل وبعد يستخدم عبارات الحماية لتسطيح الشرطيات المتداخلة:

// BEFORE: Nested conditions (arrowhead pattern)
void ProcessOrder(Order order) {
    if (order != null) {
        if (order.IsValid) {
            if (!order.HasExpired) {
                Execute(order);
            } else {
                Console.WriteLine("Order expired.");
            }
        } else {
            Console.WriteLine("Order is invalid.");
        }
    } else {
        Console.WriteLine("Order is null.");
    }
}

// AFTER: Using guard clauses to flatten logic
void ProcessOrder(Order order) {
    if (order == null) {
        Console.WriteLine("Order is null.");
        return;
    }
    if (!order.IsValid) {
        Console.WriteLine("Order is invalid.");
        return;
    }
    if (order.HasExpired) {
        Console.WriteLine("Order expired.");
        return;
    }
    Execute(order);
}

في الإصدار المعاد تركيله، يعالج كل منهما if سيناريو "الحالة السيئة" ويعود مبكرا. الآن "المسار السعيد" (حيث يكون الأمر غير فارغ وصالح وغير منتهي الصلاحية) في الأسفل ، مع الحد الأدنى من المسافة البادئة. يتم فحص كل شرط بالتتابع وبشكل مستقل ، وتكون نتيجة كل فحص فاشل واضحة على الفور. قضى هذا النهج على مستويات متعددة من الأقواس وجعل الغرض من الوظيفة أكثر وضوحا.

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

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

التبسيط باستخدام عبارات التبديل أو مطابقة الأنماط

تقدم switch العديد من اللغات ، بما في ذلك C # ، عبارات (وإمكانيات مطابقة الأنماط الأحدث) التي يمكن أن تحل محل سلاسل معينة ببنية if/else تعريفية أنظف. غالبا ما يكون قراءة مفتاح / حالة من السهل عند التحقق من متغير أو تعبير واحد مقابل العديد من القيم المحتملة. تمكن مطابقة الأنماط المطورين من التعامل مع الظروف المعقدة باستخدام تعبير يشبه المحول.

فحص مزايا عبارات التبديل

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

ضع في اعتبارك قصاصة البرمجة التالية التي تستخدم مطابقة النمط في تعبير التبديل:

string result = (user.Role, user.HasAccess) switch
{
    ("Admin", true)  => "Access granted",
    ("Admin", false) => "Access denied: no access flag",
    ("Guest", _)     => "Access denied: guests not allowed",
    _                => "Access denied: role not recognized"
};
Console.WriteLine(result);

يعالج تعبير المحول هذا أربعة سيناريوهات في شكل مضغوط. إنه أكثر إيجازا من سلم مكافئ if/else if ومن الواضح أنه شامل. كل حالة منفصلة ، لذلك من السهل إضافة واحدة أو تعديلها دون المخاطرة بالآخرين.

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

تحلل وتغليف الظروف المعقدة

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

افحص فوائد التحلل

يحسن التحلل قابلية القراءة وإعادة الاستخدام. عندما تقوم بنقل علامة اختيار منطقية إلى دالة ذات اسم واضح، تصبح العبارة if لا تحتاج إلى شرح.

ضع في اعتبارك نموذج التعليمات البرمجية التالي الذي يوضح التحلل:

// BEFORE: Moderately complex conditional that's hard to parse
public class DocumentService 
{
    public bool CanAccessDocument(User user, Document document)
    {
        if (user != null && user.IsActive && document != null && 
            !document.IsDeleted && 
            (document.IsPublic || 
             (document.OwnerId == user.Id) || 
             (user.Role == "Admin") || 
             (user.Role == "Manager" && document.Department == user.Department) ||
             (document.SharedUsers != null && document.SharedUsers.Contains(user.Id) && 
              document.ShareExpiry > DateTime.Now)))
        {
            return true;
        }
        return false;
    }
}

// AFTER: Decomposed with clear, meaningful method names
public class DocumentService 
{
    public bool CanAccessDocument(User user, Document document)
    {
        if (!IsValidRequest(user, document))
            return false;

        return HasDocumentAccess(user, document);
    }

    private bool IsValidRequest(User user, Document document)
    {
        return user != null && 
               user.IsActive && 
               document != null && 
               !document.IsDeleted;
    }

    private bool HasDocumentAccess(User user, Document document)
    {
        return document.IsPublic || 
               IsDocumentOwner(user, document) || 
               HasAdminAccess(user) || 
               HasDepartmentAccess(user, document) || 
               HasSharedAccess(user, document);
    }

    private bool IsDocumentOwner(User user, Document document)
    {
        return document.OwnerId == user.Id;
    }

    private bool HasAdminAccess(User user)
    {
        return user.Role == "Admin";
    }

    private bool HasDepartmentAccess(User user, Document document)
    {
        return user.Role == "Manager" && 
               document.Department == user.Department;
    }

    private bool HasSharedAccess(User user, Document document)
    {
        return document.SharedUsers != null && 
               document.SharedUsers.Contains(user.Id) && 
               document.ShareExpiry > DateTime.Now;
    }
}

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

تؤدي إعادة بناء نموذج التعليمات البرمجية إلى الفوائد التالية:

  • النية الواضحة: يشرح كل اسم طريقة بالضبط ما يتحقق منه (IsDocumentOwner، HasAdminAccess، وما إلى ذلك)

  • سهل التعديل: هل تحتاج إلى تغيير منطق المسؤول؟ فقط قم بتعديل HasAdminAccess. هل تريد إضافة قاعدة مشاركة جديدة؟ أضفه إلى HasSharedAccess.

  • قابل للاختبار: يمكنك اختبار كل قاعدة وصول بشكل مستقل دون إعداد سيناريوهات معقدة.

  • التدفق المقروء: تقرأ الطريقة الرئيسية الآن مثل اللغة الإنجليزية: "هل الطلب صالح؟ إذا كان الأمر كذلك ، فهل يمكن للمستخدم الوصول إلى المستندات؟

  • قابل للصيانة: تعد إضافة قواعد وصول جديدة (مثل دور "المحرر") أمرا سهلا دون لمس المنطق الحالي.

تلميح

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

التحلل هو نهج "فرق تسد" يقسم المنطق المعقد إلى أجزاء أصغر يمكن التحكم فيها. والنتيجة هي رمز يسهل قراءته وصيانته واختباره.

دمج المنطق المتكرر وإزالة متغيرات "علامة التحكم"

يستخدم الدمج لتنظيف أي ازدواجية أو حالة غير ضرورية في منطقك الشرطي.

تشمل تقنيات التوحيد ما يلي:

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

دراسة فرص الدمج

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

غالبا ما يكون متغير علامة التحكم علامة على أن التعليمات البرمجية قد تم تنظيمها بطريقة أقل من مثالية. ضع في اعتبارك نموذج التعليمات البرمجية التالي:

bool processed = false;
if (condition1) {
    DoTask();
    processed = true;
}
if (!processed && condition2) {
    DoTask();
    processed = true;
}
if (!processed) {
    DoDefaultTask();
}

يمكن إعادة بناء نموذج التعليمات البرمجية هذا إلى سلسلة أكثر if - else if - else وضوحا ، أو عوائد منفصلة محمية. على سبيل المثال:

if (condition1) {
    DoTask();
} else if (condition2) {
    DoTask();
} else {
    DoDefaultTask();
}

فيما يلي مثال آخر على التوحيد:

// Before consolidation
if (x > 0) {
    result = Math.Log(x);
} else {
    result = Math.Log(x);
    Logger.Warn("x was non-positive");
}

// After consolidation
if (x <= 0) {
    Logger.Warn("x was non-positive");
}
result = Math.Log(x);

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

تطبيق تعدد الأشكال للمنطق المعقد متعدد الفروع

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

إشعار

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

فحص فوائد تعدد الأشكال

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

ضع في اعتبارك نموذج التعليمات البرمجية التالي الذي يستخدم تعدد الأشكال:

INotificationSender sender = SenderFactory.GetSender(notification.Type);
sender.Send(notification);

ليست if - else هناك حاجة إلى سلسلة. إذا كان نوع إعلام جديد مطلوبا، فيمكنك إضافة فئة وتحديث المصنع، ولكنك لا تقوم بتعديل المنطق الأساسي.

نمط الإستراتيجية مشابه ولكنه يشير عادة إلى تبديل الخوارزميات لمهمة معينة.

فحص وقت استخدام تعدد الأشكال

يعد تنفيذ تعدد الأشكال (أو نمط الإستراتيجية) أكثر فائدة عندما يتعامل منطقك الشرطي مع فئات مميزة من السلوك أو الأنواع. إنه لا يقلل من التعقيد الفوري فحسب ، بل يجعل الكود أكثر قابلية للتوسيع للمتطلبات المستقبلية.

ضع في اعتبارك الأساليب المستندة إلى البيانات (المستندة إلى الجدول)

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

فحص فوائد التصميم المستند إلى البيانات

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

ضع في اعتبارك نموذج التعليمات البرمجية التالي الذي يستخدم قاموسا لعمليات البحث:

var fees = new Dictionary<string, decimal> {
    {"US", 5}, {"EU", 7}, {"ASIA", 10}, {"OTHER", 15}
};
fee = fees.ContainsKey(region) ? fees[region] : defaultFee;

يؤدي استخدام قاموس لتعيين المناطق إلى الرسوم إلى التخلص من سلسلة طويلة من if/else if العبارات. يكون الكود الناتج أقصر ويمكن إضافة منطقة جديدة عن طريق إضافة إدخال إلى القاموس.

ملخص تقنيات التبسيط

فيما يلي ملخص للتقنيات الرئيسية لتبسيط الشرطيات المعقدة:

  • بنود الحماية / الإرجاع المبكر
  • مطابقة التبديل / النمط
  • استخراج الوظائف / المتغيرات
  • دمج التكرارات وإزالة العلامات
  • التعدد
  • الجداول المستندة إلى البيانات

في كثير من الأحيان ، يعمل مزيج من الأساليب بشكل أفضل. المكافأة متعددة الجوانب: تتحسن قابلية القراءة ، وتتحسن قابلية الصيانة ، وتحسن قابلية الاختبار.

ملخص

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