كيف تقيّم مستوى المبرمج عبر أحجية واحدة فقط: فزبز FizzBuzz

شاء الله أن أُجريَ عدداً من المقابلات التقنيّة لبعض المتقدمين للوظائف، عادةً ما أنهي المقابلة بسؤال ثابت “كم تحتاج من الوقت لتُنهي قراءة كتابٍ تقني مكون من مئتي صفحة؟

سأخبرك عن سبب هذا السؤال لاحقاً..

لا أدري عن الآخرين، ولكن أعلمُ تماماً أن إجراء المقابلات مسؤليةٌ كبيرة يجبُ فيها قبل كُل شيء استحضارُ العدل بين المتقدمين، وأن يكونَ التقييمُ عادلاً بين الجميع.

ولكن الحقيقة أن معرفة وسبر أغوار الناس لا يمكن أن يتم خلال دقائق معدودة، ولا حتى ساعات! وهنا تكونُ مسؤليّتُك أنت!

على المتقدم أن يكون مستعداً جيداً للمقابلة، نعم. ولكن يجبُ عليك أنت أن تكونَ مستعداً أكثرَ منه!

حقيقة: المقابلة الوظيفية للمبرمجين بها كثيرٌ من الظلم

قد يكونُ أحدُ المتقدمين للوظائفِ متحدثٌ حصيف ومُقنعٌ يبيعك الماء في الهواء والسمك في البحر ولكنه بلا قدرات تقنية، بينما أحدٌ آخر عبقري عملي ولكنه ضعيفُ الحديث، وآخرون مختلفون.

فيجبُ على المتقدم أن يختار الأنسب ويحاول أن يصل لحقيقة المتقدم ويقيمه كما يجب، ولكن…

ما هي الأدوات المساعدة على سبر أغوار الناس خلال هذه الدقائق القليلة؟!!

خلال قرائتي لبعض المواضيع البرمجية مررتُ على مشكلة برمجية تُسمى فزبز FizzBuzz.

في الأصل هذه المشكلة هي لعبة من الممكن أن يلعبها الأطفال بعد حفظ جداول الضرب التي لا يحبونها 🙂

فكرةُ لعبة فزبز FizzBuzz كالآتي:

  1. نفترض أن اللُّعبة تلعب من قبل شخصين.
  2. كل شخص يذكر رقماً إبتداءً من الرقم واحد 1، 2، 3، 4، …إلخ.
  3. إذا كان الرقم من مضاعفات الرقم 3 يقول اللاعب فز Fizz بدلاً عن الرقم.
  4. إذا كان الرقم من مضاعفات الرقم 5 يقول اللاعب بز Buzz بدلاً عن الرقم.
  5. إذا كان الرقم من مضاعفات الرقمين 3 و 5 في نفس الوقت يقول اللاعب فزبز FizzBuzz.

هذه اللعبة بسيطة، ومن الطبيعي أن المبرمجين بمختلف مستوياتهم يستطيعون برمجة هذه اللعبة بكللللل بساطة.

ولكن…

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

هيا فلنبرمج هذه اللعبة ونرى أنواع المبرمجين…

جميع الشفرات البرمجية التالية مكتوبة بلغة جافا

المبرمج المبتدئ: المرحلة الأولى

بمجرّد سماع المشكلة ومحاولة تحويل المشكلة إلى منطق برمجي تكون النتيجة كما يلي

        for(int i = 1; i < 100; i++)
        {
            if( i % 3 == 0){
                System.out.println("Fizz");
            }
            
            if( i % 5 == 0){
                System.out.println("Buzz");
            }
            
            if( i % 3 != 0 && i % 5 != 0 ){
                System.out.println(i);
            }
        }

في الحقيقة هذا نوع مميز جداً من المبرمجين، وهو المبرمج المبتدئ.

برغم أن المنطق صحيح تماماً ولكن توجد مشكلةٌ في تنفيذ هذه الشفرة المصدرية!

تنفيذ المحاولة الأولى - فزبز fizzbuzz
تنفيذ المحاولة الأولى

فوفقاً للقاعدة الخامسة المذكورة أعلاه في فكرة اللعبة يجب طباعة FizzBuzz وليس Fizz في سطر و Buzz في سطر!

نتيجة تنفيذ المحاولة الأولى
نتيجة تنفيذ المحاولة الأولى

جميل!

المبرمج المبتدئ: المرحلة الثانية

بعد أن نخبر المبرمج المبتدئ بهذا الخطأ سيبدأ في اصلاحه فوراً بطباعة جملة جديدة كاملة لـ FizzBuzz و تعديل الشفرة البرمجية كما يلي

        for(int i = 1; i < 100; i++)
        {
            if( (i % 3 == 0) && (i % 5 != 0) ){
                System.out.println("Fizz");
            }
            
            if( (i % 5 == 0) && (i % 5 != 0)){
                System.out.println("Buzz");
            }
            
            if( (i % 3 == 0) && (i % 5 == 0)){
                System.out.println("FizzBuzz");
            }
            
            if( (i % 3 != 0) && (i % 5 != 0) ){
                System.out.println(i);
            }
        }

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

لماذا؟!

انظر إلى عدد المقارنات في الهذه الشفرة المصدرية

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

المبرمج المتقدّم: المرحلة الأولى

كان بالإمكان تقليل عدد المقارنات إل حد كبير “نسبياً” بإستخدام عبارة else بعد إعادة ترتيب المقارنات منطقياً، فتصبح الشفرة المصدرية  كما ترى

        for(int i = 1; i < 100; i++)
        {
            if( (i % 3 == 0) && (i % 5 == 0)){
                System.out.println("FizzBuzz");
            }
            else if( i % 3 == 0 ){
                System.out.println("Fizz");
            }
            
            else if( i % 5 == 0 ){
                System.out.println("Buzz");
            }
            
            else {
                System.out.println(i);
            }
        }

أصبحت الشفرة المصدرية الآن أكثر تقدماً مع قدرتها على أداء المطلوب بكفاءة، أليس كذلك؟

ما زال بالإمكان أفضل مما كان. فالتفكير في قابلية التوسع Scalability من سمات المبرمج المتقدّم. ماذا سيحدث إذا توسعت المشكلة بإضافة حالات أخرى، أي أرقام مثل:

  • إذا كان الرقم من مضاعفات 4 اطبع Guzz.
  • إذا كان الرقم من مضاعفات 4 و 3 إطبع FizzGuzz.
  • إذا كان الرقم من مضاعفات 4 و 5 إطبع BuzzGuzz.
  • إذا كان الرقم من مضاعفات 3 و 5 و 4 اطبع FizzBuzzGuzz.

وفقاً للشفرة المصدرية السابقة ستصبح الشفرة المصدرية أكثر تعقيداً و ضعيفة المقروئية.

المبرمج المتقدّم: المرحلة الثانية

هنا يوجد نوعٌ آخر من المبرمجين الذين يكتبون شفراتهم المصدرية بالطريقة التالية

        for(int i = 1; i < 100; i++)
        {
           String print = "";
           if( i % 3 == 0){
               print += "Fizz";
           }
           if( i % 5 == 0){
               print += "Buzz";
           }
           if( i % 4 == 0){
               print += "Guzz";
           }
           
           if( print.equals("") ){
               print = String.valueOf(i);
           }
           
            System.out.println(print);
        }

هنا عرّف المبرمج متغيراً نصياً في البداية وطبعهُ في النهاية، ما بين البداية والنهاية أجرى عملياتٍ تحدد محتوى النص الذي سيُطبع.

بهذه الطريقة أصبحت الشفرة المصدرية أكثرْ رُقياً وتحضراً، كما غدا توسيعُ الحل كُلّما توسّعت المشكلة أسهل.

ولكن يا صديقي..

ما زال هناك نوعٌ آخر من المبرمجين، يستطيعُ أن يفعل المزيد بهذه الشفرة البرمجية.

لاحظ إلى هذه الأجزاء:

أجزاء متشابهة من الشفرة البرمجية
أجزاء متشابهة من الشفرة البرمجية

المبرمج المتقدم: المرحلة الثالثة

هنا يأتي آخرُ نوعٍ من المبرمجين من المبرمجين المتقدمين. وهو الذي يفكر في أخذ جميع الأجزاء المتشابهة ووضعها في دالة منفصلة

    public static void main(String args[]){
        for(int i = 1; i < 100; i++)
        {
           String print = "";
           print += problemLogic(i,3,"Fizz");
           print += problemLogic(i,5,"Buzz");
           print += problemLogic(i,4,"Guzz");
           
           if( print.equals("") ){
               print = String.valueOf(i);
           }
           
            System.out.println(print);
        }
    }
    
    static String problemLogic(int i,int number, String text){
        if( i % number == 0){
               return text;
        }else{
            return "";
        }
    }

وما زال بالإمكان تحسين الشفرة المصدرية أكثر!

 

خاتمة

ألم ترَ كيف أن هذه المشكلة البرمجية البسيطة بإمكانها أن تفرق بين المبرمجين ومستوياتهم!

أجد أن أحجية FizzBuzz مثالٌ ممتاز فعلاً لتقييم المبرمج إبتداءً، والأهم من ذلك أنها مفتاحٌ جيّد لمواصلة الحديث حول تحليل هذه الأحجية ومعرفة مدى قدرة المبرمج على استيعاب الخوارزميات الأكثر تعقيداً وقدرته على التبنؤ بالحلول.

أثناء كتابة الشفرات البرمجية المتعلقة بهذه التدوينة طرأت في ذهني حلولٌ أخرى بطُرُقٍ مختلفة، جيمعُها تظل حُلول وخوارزميات للحل تحتاجُ إلى تحسين وتركيز، فليس ما ذُكر في التدوينة هو الحل الأمثل أو الطريقةُ الوحيدة للحل، بل ربما تستطيع إيجاد العشرات من الحلول كلاً وفقاً لتفكيره.

عندما كُنت أسأل المتقدمين عن الوقتِ اللازم لقراءة كتاب من 200 صفحة كنت أرغب في معرفة رغبتهم في التعلم التقني، وكانت الإجابات مساعدةً جداً لفتح بابٍ لمناقشة هذا الأمر!

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

مصطفى الطيب

صديقٌ لنُظمِ المعلُومات و عُلومِ الحَاسِب و مُختصٌ بهما، مُحبٌ للعِلمِ و نَشرِه.
أُشاركُ معارفي و تَجاربي و خِبراتي في تَدويناتٍ و دوراتٍ من خلال مُدونةِ عُلوم.

مقالات ذات صلة

8 آراء على “كيف تقيّم مستوى المبرمج عبر أحجية واحدة فقط: فزبز FizzBuzz”

  1. السلام عليكم يا مصطفى فرحت كثيرا عندما رايت فى هذا الموقع فكيف حالك وكيف امورك واجارك الله على الفائدة التى تنشرها للناس وجعلها الله فى ميزان حسناتك
    عزت العشري

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *