نصائح لكتابة الأكواد البرمجية بطريقة واضحة ومفهومه Tips for Coding Standards for writing Clean Code

مقالات تقنية - مواضيع تقنية

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



من الامثلة على الاخطاء الشائعة عند كتابة الاكواد:
1-  كتابة الكود بطريقة شخصية بحتية وكأن لا أحد سيقرأ او يستخدم الكود مرة أخرى.
2- تعريف الطرق method بشكل كبير جدًا دون الحاجة لذلك.
3- طرق محملة overloaded methods بشكل زائد مع تكرار المحتوى بالكامل.
4- تكرار نفس الكود باكثر من مكان.
وغيره الكثير  

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

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

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

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

فيما يلي مجموعة من النصائح المفيدة في هذا الشأن 


مراعاة طريقة تعريف المسميات

من اهم الأشياء التي يجب الانتباه لها عند كتابة الكود البرمجي هي الأسماء: والحكي عن الأسماء بشمل كل شيء في الكود بما في ذلك: أسماء المتغيرات Parameters أسماء function أسماء قواعد البيانات Data Bases، أسماء الملفات او الصفحات File Name... وغيرها.

مثال:

string fileName
string fn

لاحظ الفرق بين أسماء المتغيرات 

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

الطريقة الثانية اكيد خطأ لانو الاسم fn غير واضح وغير مفهوم.

بشكل عام عند كتابة الأسماء في الكود البرمجي يجب مراعاة النقاط التالية:

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


مثال 2 

protected void Button2_Click(object sender, EventArgs e)
{
}

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

على سبيل المثال اذا كانت وظيفة Button2 هي تسجيل ملف جديد يمكن تسميته ب 

protected void BtnAddNewCustomerFile_Click(object sender, EventArgs e)
{
}

مثال 3: 

// method 1
String customerAddress, vendorAddress;
int NUMBER_OF_DAYS_IN_WEEK = 7;
if(numberOfDays == NUMBER_OF_DAYS_IN_WEEK) {
  // do something
}
// method 2
String cAddress, vAddress;
if(numberOfDays == 7) {
  // do something
}

في هذا المثال قد تكون method 2 أفضل بسبب قلة عدد السطور المكتوبة. ولكن قلة عدد سطور لا تعني ان الكود افضل، في طريقة الكتابة هنا زادت فرصة أن يكون الكود في حالة من الفوضى.

بالرجوع الى الطريقة الأولى اكيد الكود يبدو نظيفًا بشكل اكثر والسبب:

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


مثال 4:

public static String state;

في هذا المثال ماذا يعني state. بهذه الطريقة التعريف غير واضح.

لك حرية اختيار الاسم. لكن الأهم ان يكون له معني يعكس الدور الذي سيؤديه. كما يفضل اختيار الأسماء التي تكون مألوفة وواضحة.


تجميع المتغيرات المتشابه في مكان واحد common class

لنفرض أنك بحاجة الى معلومات عن عنوان العميل مثل 

  private String name;
  private String addressLine1;
  private String city;
  private String state;
  private String Phone;
  private String POBox;

في هذه الحالة يفضل تجميع هذه البيانات تحت class واحد مثل:

public class Address
        {
            private static String name;
            private static String addressLine1;
            private static String city;
            private static String state;
            private static int Phone;
            private static String POBox;
        }

لاحظ استخدمنا هنا تعريف باسم state وهو نفس المثال رقم 4 السبق 

public static String state;

لكن الفرق هنا ان المتغير state داخل class باسم Address وبالتالي واضح ومفهوم شو هو.


تطبيق مبدأ المسؤولية الفردية. SOLID principles (Single Responsibility Principle)

شو يعني هذا المبدأ: 

مبدأ المسؤولية الفردية حسب Wikipedia هو مبدأ برمجة الكمبيوتر الذي ينص على أن كل module أو class أو function في برنامج كمبيوتر يجب أن تتحمل المسؤولية عن جزء واحد من وظائف هذا البرنامج، ويجب أن تغلف encapsulate هذا الجزء.

لتطبيق هذا الحكي يجب تطبيق من الأشياء :

  •  يجب أن تقوم method دائمًا بشيء واحد فقط لا أكثر ولا أقل. ويجب ألا تظهر method أبدًا لمحة عن temporal coupling. يجب أن يوضح اسم الطريقة وجسمها دائمًا ما تفعله الطريقة بوضوح.
  • من الأفضل ان لا يتجاوز عدد arguments في method أكثر من 3.  كلما كان عدد arguments اقل كانت الطريقة أفضل وأنظف، والسبب في ذلك :
    • في حال وجود عدد arguments كبير هذه يجعل method تقوم بالعديد من الوظائف. لذا يجب تقسيمها إلى عدة وظائف functions أصغر، لكل منها مجموعة parameter set أصغر.
    • يجعل الكود البرمجي أسهل للقراءة. 
    • أسهل للاختبار والتجربة بحيث يتم تقسيم المشكلة إلى مهام أصغر حيث تكون كل على مهمه بسيطة للغاية. 
  • تجنب تمرير القيم المنطقية (booleans (or flag values)) إلى arguments. والسبب :

يجب أن تؤدي method او function شيئًا واحدًا فقط حسب مبدأ المسؤولية الفردية للمحافظة على تماسك قوي. في حين ان boolean او flag يشير إلى العكس تمامًا، حيث تُستخدم لتغيير سلوك function بناءً على السياق الذي تُستخدم فيه. إنها إشارة الى أن الوظيفة تقوم بأكثر من شيء وليست متماسكة. يمكن أن تتسبب القيم المنطقية في حدوث تغيير طفيف فقط في التدفق المنطقي logical flow للدالة. ولكن في معظم الأحيان ينتج عنه أكثر من عبارة شرطية واحدة أو تعديل في العمليات الحسابية. قد يتفرع logical flow في اتجاهات أخرى ويؤدي إلى أخطاء مختلفة وآثار جانبية.

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

يمكن إنشاء Object أو بنية بيانات تتضمن هذه arguments في حال الحاحه الى استخدام اكثر من 2 او 3 arguments

لاحظ المثال التالي :


الطريقة 1 : تمرير 3 arguments

	Circle createCircle(double x, double y, double radius) {...}

الطريقة 2 : تمرير 2 arguments

	class Point {
  double x;
  double y;
 }

Circle createCircle(Point center, double radius) {...}

في الطريقة 2 عملنا class يحتوي على 2 متغير باسم x,y ثم بعثنا هذا class كامل الى الطريقة function .

  • النقطة الأخيرة التي يجب مراعاتها هي أن method يجب أن تفعل شيئًا ما أو تجيب عن شيء ما ، ولكن ليس كلاهما في نفس الوقت. يوضح المثال التالي طريقة القيام بهذين الأمرين والتي تظهر علامات تدل على سوء كتابة التعليمات البرمجية.
public boolean set(String FirstName, String SecondName) {
  this.FullName = FirstName+ SecondName;
  return true;
}

في هذا المثال عملنا function لإرجاع الاسم الأول والثاني (عملية دمج)، أيضا رجعنا قيمة true. يعني هذا function بعمل مهمه وبرجع قيمة 

مثال 2: 


طريقة 1 : لا بنصح بها 

private Boolean IsEligible(int[] marks, int condition)
        {
            int sum = 0;
            for (int i = 0; i < marks.Length; i++)
            {
                sum = sum + marks[i];
            }
            return sum>= condition;
        }

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


طريقة 2: ينصح بها 

وهي تقسيم الكود الى 2 function الأول لإجراء عمليات الحساب 

        private int sum(int[] marks)
        {
            int sum = 0;
            for (int i = 0; i < marks.Length; i++)
            {
                sum = sum + marks[i];
            }
            return sum ;
        }

والثاني لإرجاع النتيجة 

        private Boolean IsEligible(int[] marks, int condition)
        {
            int Total = sum(marks);
            return Total >= condition;
        }


مراعاة طول function 

في حال وجود function يحتوي على عدد كبير من الأكواد يفضل تقسيمها الى أكثر من function 


كتابة الشروحات التوضيحية comments

مثال :

final Object value = (new JSONTokener(jsonString)).nextValue(); 
// Note that JSONTokener.nextValue() may return 
// a value equals() to null. 
if (value == null || value.equals(null)) { 
return null; 
}

حسب لغة البرمجة تكتب الملاحظات، في المثال السابق الكود المكتوب بعد العلامة //  يعني كود لن يتم تنفيذه 


استخدام interfaces لتجنب الاقتران coupling

ما هو الاقتران coupling: يشير إلى مدى قوة ارتباط عناصر البرنامج elements بالعناصر الأخرى. يمكن أن يكون عنصر element البرنامج: class أو package أو component أو subsystem أو system. وأثناء تصميم الأنظمة، يوصى بالحصول على عناصر برمجية ذات تماسك عالٍ High cohesion وتدعم اقتران منخفض Low coupling.

مصطلح التماسك Cohesion: هو نوع ترتيبي من القياس وعادة ما يوصف بأنه "تماسك عالي high cohesion " أو "تماسك منخفض low cohesion ". تميل Modules ذات التماسك العالي إلى أن تكون مفضلة، لأن التماسك العالي يرتبط بالعديد من السمات المرغوبة للبرنامج بما في ذلك المتانة robustness والموثوقية reliability وقابلية إعادة الاستخدام re-usability والفهم understand-ability. في المقابل ، يرتبط التماسك المنخفض low cohesion بسمات غير مرغوب فيها مثل صعوبة الحفاظ عليها أو اختبارها أو إعادة استخدامها أو حتى فهمها. 

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


مثال 

طريقة 1:

        public int GetAvrage(List<int> Marks)
        {
            int Avrage = 0;
            return Avrage;
        }

في هذا المثال يتم ارسال List ك arguments 

طريقة 2

        public int GetAvrage(ArrayList<int> Marks)
        {
            int Avrage = 0;
            return Avrage;
        }

في هذا المثال يتم ارسال نموذج instance باسم ArrayList ك Arguments