إن C # الخاص بك "فعال" بالفعل ، فقط دعه.

مرحبا يا هابر! أقدم لكم ترجمة المقال الأصلي "C # الخاص بك يعمل بالفعل ، ولكن فقط إذا تركته " بقلم إيجال تاباتشنيك.

قبل بضعة أيام، وأنا بالتغريد على C # البرمجية المتكررة التي تنفذ FizzBuzz ، وذلك باستخدام بعض جديدة الميزات في C # 8.0 . تغردت "أصبحت فيروسية" ، أعجب العديد من الناس بإيجازها ووظيفتها ، بينما سألني البعض لماذا لم أكتبها في F #؟

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

أولاً ، دعنا نلقي نظرة على ما يفعله هذا الرمز:

public static void Main(string[] args)
{
      string FizzBuzz(int x) => 
            (x % 3 == 0, x % 5 == 0) switch
            {  
                  (true, true) => "FizzBuzz", 
                  (true, _) => "Fizz", 
                  (_, true) => "Buzz", 
                  _ => x.ToString()
            };
    
      Enumerable.Range(1, 100 ) 
            .Select(FizzBuzz).ToList() 
            .ForEach(Console.WriteLine); 
}

هنا ننشئ طريقة محلية ، ممثلة بتعبير لامدا ، يتم حساب نتيجته باستخدام tuple.

الحداثة هنا هي استخدام tuple (زوج) للعمل مع نتيجة حساب تعبيرين معًا (x٪ 3 = = 0 و x٪ 5 = = 0). هذا يسمح لك باستخدام مطابقة النمط لتحديد النتيجة النهائية. إذا لم يتطابق أي من الخيارات ، فسيتم إرجاع تمثيل السلسلة للرقم افتراضيًا.

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

ببساطة ، التعبير هو سؤال له إجابة دائمًا. فيما يتعلق بالبرمجة ، فإن التعبير هو مزيج من الثوابت والمتغيرات والعوامل والوظائف المحسوبة بوقت التشغيل لحساب ("الإرجاع") قيمة. لتوضيح الاختلاف بالعبارات ، دعنا نكتب نسخة أكثر شيوعًا من FizzBuzz لمبرمجي C #:

public static void Main(string[] args) 
{
      foreach( int x in Enumerable.Range(1, 100)) 
      {
             FizzBuzz(x);
      }
} 

public static void FizzBuzz( int x) 
{
      if (x % 3 == 0  && x % 5 == 0) 
             Console.WriteLine("FizzBuzz"); 
      else if (x % 3 == 0 ) 
             Console.WriteLine("Fizz"); 
      else if (x % 5 == 0 ) 
             Console.WriteLine("Buzz"); 
      else
             Console.WriteLine(x);
}

بطبيعة الحال ، هذا أبعد ما يكون عن رمز "نظيف" ، ويمكن تحسينه ، ولكن لا يمكن للمرء إلا أن يوافق على أن هذا رمز C # عادي. بعد الفحص الدقيق لطريقة FizzBuzz ، حتى بالنظر إلى بساطتها ، فإنه يظهر بوضوح العديد من مشاكل التصميم.

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

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

public static string FizzBuzz(int x)
{
      if (x % 3 == 0 && x % 5 == 0)
            return "FizzBuzz";
      else if (x % 3 == 0)
            return "Fizz";
      else if (x % 5 == 0)
            return "Buzz";
      else
            return x.ToString();
}

بالطبع ، هذه ليست تغييرات جذرية ، ولكن هذا يكفي بالفعل:

  1. أصبحت طريقة FizzBuzz الآن تعبيرًا يأخذ قيمة رقمية ويعيد سلسلة.
  2. ليس لديها مسؤوليات أخرى وتأثيرات خفية ، مما يحولها إلى وظيفة نقية.
  3. يمكن استخدامه واختباره بشكل مستقل ، دون أي تبعيات وإعدادات إضافية.
  4. الشفرة التي تستدعي هذه الوظيفة مجانية للقيام بأي شيء مع النتيجة - الآن هذه ليست مسؤوليتنا.

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

يتم استخدام التعبيرات بدلاً من العبارات إلى حد ما بواسطة جميع لغات البرمجة. تطورت C # بمرور الوقت لتقديم وظائف تسهل العمل مع التعبيرات: LINQ ، والأساليب المستندة إلى التعبير ، ومطابقة الأنماط ، والمزيد. غالبًا ما تسمى هذه "الميزات" "وظيفية" لأنها موجودة - بلغات مثل F # أو Haskell ، حيث يكاد يكون من المستحيل وجود أي شيء بخلاف التعبيرات.

في الواقع ، تم تشجيع أسلوب البرمجة هذا الآن من قبل فريق C #. في خطاب أخير في NDC لندن ، حث بيل واجنر المطورين على تغيير عاداتهم (الحتمية) واعتماد الأساليب الحديثة:


يمكن استخدام C # (واللغات الحتمية الأخرى مثل Java) بشكل وظيفي ، ولكنها تتطلب الكثير من الجهد. تجعل هذه اللغات النمط الوظيفي استثناءً ، وليس القاعدة. أشجعك على تعلم لغات البرمجة الوظيفية لتصبح متخصصًا من الدرجة الأولى.

All Articles