تعلم كتابة كود سي شارب نظيف بالامثلة الجزء الاول

الكاتب بتاريخ عدد التعليقات : 3

في تلك المقالة سوف اتطرق الي كيفية كتابة كود نظيف وان تكون قادر علي اصلاح اخطاء الكود باعطاء نماذج وامثلة لأكواد سيئة الكتابة.

سوف اشرح المشاكل وأستعرض طرق الحل بأستبدالها بافضل الحلول خطوة بخطوة .


الجزء الاول سوف يكون لكل مطور يعلم الاساسيات في السي شارب - سوف اقوم باستعراض المشاكل الاساسية واجري الخطوات لكيفية جعل الكود قابل للقرائة كما لو كان كتاب مفتوح.الجزء المتقدم سوف يكون للمطورين الاكثير خبرة ومن لديهم قدرة علي استيعاب الانماط المتقدمة.


لكي تفهم تلك المقالة اليك الشروط :

  • فهم للغة السي شارب
  • القدرة علي فهم بعد الرياضيات والانماط البسيطة.


ملاحظات سريعة:

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


سوف اعطي الان مثال عن كود لفئة سئ جداً جداً مع انه قد يكون بالنسبة للمبتدئ كود عادي وسليم

public class Class1
{
  public decimal Calculate(decimal amount, int type, int years)
  {
    decimal result = 0;
    decimal disc = (years > 5) ? (decimal)5/100 : (decimal)years/100; 
    if (type == 1)
    {
      result = amount;
    }
    else if (type == 2)
    {
      result = (amount - (0.1m * amount)) - disc * (amount - (0.1m * amount));
    }
    else if (type == 3)
    {
      result = (0.7m * amount) - disc * (0.7m * amount);
    }
    else if (type == 4)
    {
      result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));
    }
    return result;
  }
}

بالفعل انه كود سئ هل لك تتخيل ما هو دور تلك الاكواد او تلك الفئة؟انه تجري بعض الحسابات الغريبة, لكن تخيل ان تلك الفئة دورها بمثابة دور DiscountManager .بالفعل الكود غير قابل للقرائة او الدعم وتطويره حالياً بشكله.


هيا بنا نشوف المشاكل الي في الكود:


Naming التسمية

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

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

Magic numbers الارقام السحرية ولكن شخصياً اطلق عليها الارقام المجهولة


في المثال الي اعطيتكم اياه نوع المتغيرات مفهوم ومعروف حلو اوي, ولكن حالة العميل الذي يجري له الخصم ماذا عنها.
هل لك ان تخمن ان في كل حالة If-else if statements تقوم باختيار رقم 1 او 2 او 3 او 4 لأجراء الخصم حسب نوع كل عميل.
انت في الحقيقة لن تفهم الغرض من تلك الارقام المجهولة,
مرة اخري وقت ضائع غير ان العميل هيكون سعيد جدا لو اخترت حالة خطأ ليه لأنه سيحصل علي خصم لعميل اكثر تقدماً,
في الحقيقة لن يكون مديرك في العمل سعيداً بذلك.

Not obvious bug مشاكل غير واضحة

اتفضل مشكلة اخري لم يتم عمل حسابها في الكود, دلوقتي بفرض ان السيستم دخل عليه عضوية عميل جديدة مثلاً عميل ذهبي
بالتالي الكود لن يتعرف عليه وبالتالي الدالة هتقوم بأعادة الحساب بصفر يالحظ ذلك العميل كم اريد ان اري صاحب العمل ما هي ردة فعلة :D .



Unreadable غير قابل للقرائة

طبعاً كلنا متفقين ان الكود غير قابل للقرائة حتي انا لا استطيع فهمه وبالتالي تلك الاكواد ماهي الا عبارة عن احجية الويجا وليست بكود برمجي :D .

Magic numbers - again مرة اخري الارقام المجهولة

في اول الكود كانت 1و2و3و4 كانت ارقام غير واضحة الدلالة,
طيب بالنسبة للأرقام 0.1, 0.7, 0.5 حد فاهم ليها معني واضح؟
بفرض اننا سوف نقوم بأجراء تعديل حول ذلك الكود
result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));
هل من منافس ؟

DRY – Don’t repeat yourself  اكيد  من الغير منطقي اننا نكرر نفس الحاجة

طبعاً للوهلة الاولي الكود لا يحتوي علي تكرار ولكن انظر
disc * (amount - (0.1m * amount));

نفس الحاجة
disc * (amount - (0.5m * amount))

Multiple responsibilities per class اكثر من وظيفة في نفس الفئة

من غير المحبب ان نجد اكثر من وظيفة للدالة الواحدةلأن تعريف الدالة في الاصل هي مجموعة الجمل التي تنفذ امراً معيناً وليست اوامر معينة او وظائف معينة.



هيا بنا لأعادة هيكلة الكود


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


الخطوة الاولي | التسمية

طبعاً واضح جداً هنعمل ايه هنجعل كل الدوال والمتغيرات والفئات بأسمائها حسب وظيفتها بالتالي الكود هيبقي


public class DiscountManager
{
  public decimal ApplyDiscount(decimal price, int accountStatus, int timeOfHavingAccountInYears)
  {
    decimal priceAfterDiscount = 0;
    decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100; 
    if (accountStatus == 1)
    {
      priceAfterDiscount = price;
    }
    else if (accountStatus == 2)
    {
      priceAfterDiscount = (price - (0.1m * price)) - (discountForLoyaltyInPercentage * (price - (0.1m * price)));
    }
    else if (accountStatus == 3)
    {
      priceAfterDiscount = (0.7m * price) - (discountForLoyaltyInPercentage * (0.7m * price));
    }
    else if (accountStatus == 4)
    {
      priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price)));
    }
 
    return priceAfterDiscount;
  }
}

طيب اتبقي الارقام المجهولة.

الخطوة الثانية | معالجة الارقام المجهولة


public class DiscountManager
{
public enum AccountStatus
{
  NotRegistered = 1,
  SimpleCustomer = 2,
  ValuableCustomer = 3,
  MostValuableCustomer = 4
}

public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
  {
    decimal priceAfterDiscount = 0;
    decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;
 
    if (accountStatus == AccountStatus.NotRegistered)
    {
      priceAfterDiscount = price;
    }
    else if (accountStatus == AccountStatus.SimpleCustomer)
    {
      priceAfterDiscount = (price - (0.1m * price)) - (discountForLoyaltyInPercentage * (price - (0.1m * price)));
    }
    else if (accountStatus == AccountStatus.ValuableCustomer)
    {
      priceAfterDiscount = (0.7m * price) - (discountForLoyaltyInPercentage * (0.7m * price));
    }
    else if (accountStatus == AccountStatus.MostValuableCustomer)
    {
      priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price)));
    }
    return priceAfterDiscount;
  }
}



الخطوة الثالثة | نجعله اكثر قرائة

هنقوم بأبدال ال الجمل الشرطية ب Switch


public class DiscountManager
{
  public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
  {
    decimal priceAfterDiscount = 0;
    decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;
    switch (accountStatus)
    {
      case AccountStatus.NotRegistered:
        priceAfterDiscount = price;
        break;
      case AccountStatus.SimpleCustomer:
        priceAfterDiscount = (price - (0.1m * price));
        priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
        break;
      case AccountStatus.ValuableCustomer:
        priceAfterDiscount = (0.7m * price);
        priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
        break;
      case AccountStatus.MostValuableCustomer:
        priceAfterDiscount = (price - (0.5m * price));
        priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
        break;
    }
    return priceAfterDiscount;
  }
}


الخطوة الرابعة | معالجة الاخطاء الغير متوقعة

فاكرين مشكلة السيستم لو انضاف فيه العميل الذهبي
او اي عميل غير متعارف عليه بيحصل ايه
الخطوة ديه هنحلها بأيه

هنضيف في ال switch الاختيار default
وهنضيف فيه الدالة NotImplementedException



public class DiscountManager
{
  public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
  {
    decimal priceAfterDiscount = 0;
    decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;
    switch (accountStatus)
    {
      case AccountStatus.NotRegistered:
        priceAfterDiscount = price;
        break;
      case AccountStatus.SimpleCustomer:
        priceAfterDiscount = (price - (0.1m * price));
        priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
        break;
      case AccountStatus.ValuableCustomer:
        priceAfterDiscount = (0.7m * price);
        priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
        break;
      case AccountStatus.MostValuableCustomer:
        priceAfterDiscount = (price - (0.5m * price));
        priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
        break;
      default:
        throw new NotImplementedException();
    }
    return priceAfterDiscount;
  }
}


الخطوة الخامسة | هنقوم بتحليل الاجرائات الحسابية

كل الحالات في الكود بتبقي الاجراء الحسابي بنفس الشكل ده
(discountForLoyaltyInPercentage * priceAfterDiscount)

طيب دلوقتي في الاجراء ده 
0.7m * price

انا عاوز اجعله بنفس صيغة الكود 
price - (0.1m * price)

يبقي هنعمله بالشكل ده وهيعطي نفس النتيجة
price - (0.3m * price)

الان الكود بقي بالشكل ده


public class DiscountManager
{
  public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
  {
    decimal priceAfterDiscount = 0;
    decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;
    switch (accountStatus)
    {
      case AccountStatus.NotRegistered:
        priceAfterDiscount = price;
        break;
      case AccountStatus.SimpleCustomer:
        priceAfterDiscount = (price - (0.1m * price));
        priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
        break;
      case AccountStatus.ValuableCustomer:
        priceAfterDiscount = (price - (0.3m * price));
        priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
        break;
      case AccountStatus.MostValuableCustomer:
        priceAfterDiscount = (price - (0.5m * price));
        priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
        break;
      default:
        throw new NotImplementedException();
    }
    return priceAfterDiscount;
  }
}

دلوقتي ازاي اتخلص من الارقام المجهولة برضو .1 وما الي ذلك
خلاص تعالو نحلها

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

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



public class DiscountManager
{
public static class Constants
{
  public const int MAXIMUM_DISCOUNT_FOR_LOYALTY = 5;
  public const decimal DISCOUNT_FOR_SIMPLE_CUSTOMERS = 0.1m;
  public const decimal DISCOUNT_FOR_VALUABLE_CUSTOMERS = 0.3m;
  public const decimal DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS = 0.5m;
}
public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears) { decimal priceAfterDiscount = 0; decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 : (decimal)timeOfHavingAccountInYears/100; switch (accountStatus) { case AccountStatus.NotRegistered: priceAfterDiscount = price; break; case AccountStatus.SimpleCustomer: priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price)); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); break; case AccountStatus.ValuableCustomer: priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price)); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); break; case AccountStatus.MostValuableCustomer: priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS * price)); priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount); break; default: throw new NotImplementedException(); } return priceAfterDiscount; } }

الخطوة السابعة | ازالة المكرر من الكود


هنقسم الكود بتاعنا ل جزئين في دالتين وبالتالي هنلاحظ ان الدالة فيهم بتكرر وبالتالي الميثود بنقدر نستخدمها اكتر من مرة يبقي انا اختصرت في الكود بتاعي ومش عملت تكرار.


public static class PriceExtensions
{
  public static decimal ApplyDiscountForAccountStatus(this decimal price, decimal discountSize)
  {
    return price - (discountSize * price);
  }
 
  public static decimal ApplyDiscountForTimeOfHavingAccount(this decimal price, int timeOfHavingAccountInYears)
  {
     decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 : (decimal)timeOfHavingAccountInYears/100;
    return price - (discountForLoyaltyInPercentage * price);
  }
}

وبالتالي الكود بتاعي بقي شكله كده

public class DiscountManager
{
  public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
  {
    decimal priceAfterDiscount = 0;
    switch (accountStatus)
    {
      case AccountStatus.NotRegistered:
        priceAfterDiscount = price;
        break;
      case AccountStatus.SimpleCustomer:
        priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS)
          .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
        break;
      case AccountStatus.ValuableCustomer:
        priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS)
          .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
        break;
      case AccountStatus.MostValuableCustomer:
        priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS)
          .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
        break;
      default:
        throw new NotImplementedException();
    }
    return priceAfterDiscount;
  }
}


الخطوة الثامنة | بنزيل اي سطور زيادة من الكود بتاعي

كنوع من انواع النظافة للكود من المحبب انك تتعود علي كده

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


لأستكمال الدرس يرجي المتابعة من هذا الرابط

تكملة كتابة كود سي شارب نظيف بالامثلة الجزء الاول - متقدم


التقيم  :     4.94 (297 votes)
ترجمة : أحمد قرواش

هذا أحدث موضوع

3 تعليقات على موضوع "تعلم كتابة كود سي شارب نظيف بالامثلة الجزء الاول"

لو شمحت انا عايز شرح كيفية عمل اوتوباتش

اسف بس علشان بكتب بسرعة بتلغبط فى الكتابة

يا باشمهندس دلوقتي المدونة ديه للبرمجة وليست الاختراع سمارت حضرتك ممكن تسيب تعليق في الاختراع سمارت وانا براجع عليها كل فترة وبجاوب علي الناس ^_^


الإبتساماتإخفاء