CodeGym /مدونة جافا /Random-AR /ما هي الأنماط المضادة؟ دعونا نلقي نظرة على بعض الأمثلة (ا...
John Squirrels
مستوى
San Francisco

ما هي الأنماط المضادة؟ دعونا نلقي نظرة على بعض الأمثلة (الجزء الثاني)

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

8. المطرقة الذهبية

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

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

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

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

9. التحسين المبكر

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

10. كود السباغيتي

كود السباغيتي هو نمط مضاد يتم تحديده بواسطة كود سيئ التنظيم ومربك ويصعب فهمه، ويحتوي على جميع أنواع التفرع، مثل استثناءات الالتفاف والشروط والحلقات. في السابق، كان عامل goto هو الحليف الرئيسي لهذا النمط المضاد. لم تعد عبارات Goto تُستخدم حقًا بعد الآن، مما يزيل عددًا من الصعوبات والمشاكل المرتبطة بها.
public boolean someDifficultMethod(List<String> XMLAttrList) {
           ...
   int prefix = stringPool.getPrefixForQName(elementType);
   int elementURI;
   try {
       if (prefix == -1) {
        ...
           if (elementURI != -1) {
               stringPool.setURIForQName(...);
           }
       } else {
        ...
           if (elementURI == -1) {
           ...
           }
       }
   } catch (Exception e) {
       return false;
   }
   if (attrIndex != -1) {
       int index = attrList.getFirstAttr(attrIndex);
       while (index != -1) {
           int attName = attrList.getAttrName(index);
           if (!stringPool.equalNames(...)){
           ...
               if (attPrefix != namespacesPrefix) {
                   if (attPrefix == -1) {
                    ...
                   } else {
                       if (uri == -1) {
                       ...
                       }
                       stringPool.setURIForQName(attName, uri);
                   ...
                   }
                   if (elementDepth >= 0) {
                   ...
                   }
                   elementDepth++;
                   if (elementDepth == fElementTypeStack.length) {
                   ...
                   }
               ...
                   return contentSpecType == fCHILDRENSymbol;
               }
           }
       }
   }
}
يبدو فظيعا، أليس كذلك؟ لسوء الحظ، هذا هو النمط المضاد الأكثر شيوعًا :( حتى الشخص الذي يكتب مثل هذا الرمز لن يتمكن من فهمه في المستقبل. سيفكر المطورون الآخرون الذين يرون الكود، "حسنًا، إذا كان يعمل، فلا بأس — من الأفضل عدم لمسها". في كثير من الأحيان، تكون الطريقة في البداية بسيطة وشفافة جدًا، ولكن مع إضافة متطلبات جديدة، تصبح الطريقة مثقلة تدريجيًا بالمزيد والمزيد من العبارات الشرطية، مما يحولها إلى وحشية مثل هذه. إذا كانت هذه الطريقة يبدو أنك بحاجة إلى إعادة هيكلته إما بشكل كامل أو على الأقل الأجزاء الأكثر إرباكًا. عادةً، عند جدولة مشروع، يتم تخصيص وقت لإعادة البناء، على سبيل المثال، 30% من وقت السبرنت مخصص لإعادة البناء والاختبارات. بالطبع، هذا يفترض أنه لا يوجد أي استعجال (ولكن متى يحدث ذلك). يمكنك العثور على مثال جيد لرمز السباغيتي وإعادة هيكلته هنا .

11. الأرقام السحرية

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

12. برمجة النسخ واللصق

برمجة النسخ واللصق هي نمط مضاد حيث يتم نسخ ولصق التعليمات البرمجية الخاصة بشخص آخر دون تفكير، مما قد يؤدي إلى آثار جانبية غير متوقعة. على سبيل المثال، طرق النسخ واللصق ذات الحسابات الرياضية أو الخوارزميات المعقدة التي لا نفهمها بشكل كامل. قد ينجح هذا في حالتنا الخاصة، ولكن في بعض الظروف الأخرى قد يؤدي إلى مشاكل. لنفترض أنني بحاجة إلى طريقة لتحديد الحد الأقصى للعدد في المصفوفة. ومن خلال البحث في الانترنت وجدت هذا الحل:
public static int max(int[] array) {
   int max = 0;
   for(int i = 0; i < array.length; i++) {
       if (Math.abs(array[i]) > max){
           max = array[i];
       }
   }
   return max;
}
نحصل على مصفوفة بالأرقام 3 و6 و1 و4 و2، وترجع الطريقة 6. رائع، لنحتفظ بها! لكن لاحقًا حصلنا على مصفوفة تتكون من 2.5، -7، 2، و3، والنتيجة هي -7. وهذه النتيجة ليست جيدة. المشكلة هنا هي أن Math.abs() تُرجع القيمة المطلقة. الجهل بهذا يؤدي إلى الكارثة، ولكن في حالات معينة فقط. بدون فهم متعمق للحل، هناك العديد من الحالات التي لن تتمكن من التحقق منها. قد تتجاوز التعليمات البرمجية المنسوخة أيضًا البنية الداخلية للتطبيق، سواء من الناحية الأسلوبية أو على المستوى المعماري الأساسي. سيكون مثل هذا الرمز أكثر صعوبة في القراءة والصيانة. وبالطبع، يجب ألا ننسى أن نسخ كود شخص آخر بشكل مباشر هو نوع خاص من السرقة الأدبية. في تلك الحالات عندما لا يفهم المبرمج تمامًا ما يفعله ويقرر استخدام الحل الذي يفترض أنه يعمل من قبل شخص آخر، فإن هذا لا يدل على نقص المثابرة فحسب، بل إن هذه الإجراءات تضر أيضًا بالفريق والمشروع. وأحيانًا الشركة بأكملها (لذا انسخ والصق بعناية).

13. إعادة اختراع العجلة

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

14. مشكلة اليويو

مشكلة اليويو هي نمط مضاد تكون فيه بنية التطبيق معقدة للغاية بسبب التجزئة المفرطة (على سبيل المثال، سلسلة الميراث المقسمة بشكل مفرط). تنشأ "مشكلة اليويو" عندما تحتاج إلى فهم برنامج يكون تسلسله الهرمي للوراثة طويلًا ومعقدًا، مما يؤدي إلى إنشاء استدعاءات أسلوب متداخلة بعمق. ونتيجة لذلك، يحتاج المبرمجون إلى التنقل بين العديد من الفئات والأساليب المختلفة من أجل فحص سلوك البرنامج. يأتي اسم هذا النمط المضاد من اسم اللعبة. على سبيل المثال، دعونا نلقي نظرة على سلسلة الميراث التالية: لدينا واجهة تقنية:
public interface Technology {
   void turnOn();
}
ترثها واجهة النقل:
public interface Transport extends Technology {
   boolean fillUp();
}
ثم لدينا واجهة أخرى، GroundTransport:
public interface GroundTransportation extends Transport {
   void startMove();
   void brake();
}
ومن هناك، نستمد فئة السيارة المجردة:
public abstract class Car implements GroundTransportation {
   @Override
   public boolean fillUp() {
       /* some implementation */
       return true;
   }
   @Override
   public void turnOn() {
       /* some implementation */
   }
   public boolean openTheDoor() {
       /* some implementation */
       return true;
   }
   public abstract void fixCar();
}
التالي هو فئة فولكس فاجن المجردة:
public abstract class Volkswagen extends Car {
   @Override
   public void startMove() {
       /* some implementation */
   }
   @Override
   public void brake() {
       /* some implementation */
   }
}
وأخيرًا نموذج محدد:
public class VolkswagenAmarok extends Volkswagen {
   @Override
   public void fixCar(){
       /* some implementation */
   }
}
تجبرنا هذه السلسلة على البحث عن إجابات لأسئلة مثل:
  1. كم عدد الأساليب لديها VolkswagenAmarok؟

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

    ? someObj = new VolkswagenAmarok();
           someObj.brake();
من الصعب الإجابة على مثل هذه الأسئلة بسرعة، فهي تتطلب منا إلقاء نظرة والتحقيق، ومن السهل أن نشعر بالارتباك. وماذا لو كان التسلسل الهرمي أكبر وأطول وأكثر تعقيدًا، مع كل أنواع التجاوزات والحمل الزائد؟ سيتم حجب الهيكل الذي سنحصل عليه بسبب التجزئة المفرطة. الحل الأفضل هو تقليل الانقسامات غير الضرورية. في حالتنا، سنترك التكنولوجيا → السيارة → فولكس فاجن أماروك.

15. التعقيد العرضي

التعقيد غير الضروري هو نمط مضاد يتم فيه إدخال تعقيدات غير ضرورية إلى الحل.
"يمكن لأي أحمق أن يكتب تعليمات برمجية يمكن للكمبيوتر أن يفهمها. أما المبرمجون الجيدون فيكتبون تعليمات برمجية يمكن للبشر فهمها." - مارتن فاولر
إذن ما هو التعقيد؟ ويمكن تعريفها بأنها درجة الصعوبة التي يتم بها تنفيذ كل عملية في البرنامج. وكقاعدة عامة، يمكن تقسيم التعقيد إلى نوعين. النوع الأول من التعقيد هو عدد الوظائف التي يمتلكها النظام. ويمكن تقليله بطريقة واحدة فقط — عن طريق إزالة بعض الوظائف. الأساليب الحالية تحتاج إلى مراقبة. يجب إزالة الطريقة إذا لم تعد مستخدمة أو لا تزال مستخدمة ولكن دون تحقيق أي قيمة. علاوة على ذلك، تحتاج إلى تقييم كيفية استخدام جميع الأساليب الموجودة في التطبيق، من أجل فهم المجالات التي قد تكون فيها الاستثمارات جديرة بالاهتمام (الكثير من إعادة استخدام التعليمات البرمجية) وما الذي يمكنك رفضه. النوع الثاني من التعقيد هو التعقيد غير الضروري. ولا يمكن علاجه إلا من خلال نهج احترافي. بدلا من القيام بشيء "رائع" (المطورون الشباب ليسوا الوحيدين المعرضين لهذا المرض)، عليك أن تفكر في كيفية القيام بذلك ببساطة قدر الإمكان، لأن الحل الأفضل هو دائما بسيط. على سبيل المثال، لنفترض أن لدينا جداول صغيرة مرتبطة بأوصاف بعض الكيانات، مثل المستخدم: ما هي الأنماط المضادة؟  دعونا نلقي نظرة على بعض الأمثلة (الجزء 2) - 3إذن، لدينا معرف المستخدم، ومعرف اللغة التي يتم بها الوصف، والوصف نفسه. وبالمثل، لدينا واصفات مساعدة للسيارات والملفات والخطط وجداول العملاء. إذًا كيف سيبدو إدراج قيم جديدة في مثل هذه الجداول؟
public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description)throws Exception {
   switch (type){
       case CAR:
           jdbcTemplate.update(CREATE_RELATION_WITH_CAR, languageId, serviceId, description);
       case USER:
           jdbcTemplate.update(CREATE_RELATION_WITH_USER, languageId, serviceId, description);
       case FILE:
           jdbcTemplate.update(CREATE_RELATION_WITH_FILE, languageId, serviceId, description);
       case PLAN:
           jdbcTemplate.update(CREATE_RELATION_WITH_PLAN, languageId, serviceId, description);
       case CUSTOMER:
           jdbcTemplate.update(CREATE_RELATION_WITH_CUSTOMER, languageId, serviceId, description);
       default:
           throw new Exception();
   }
}
وبناء على ذلك، لدينا هذا التعداد:
public enum ServiceType {
   CAR(),
   USER(),
   FILE(),
   PLAN(),
   CUSTOMER()
}
يبدو أن كل شيء بسيط وجيد... ولكن ماذا عن الطرق الأخرى؟ في الواقع، سيكون لديهم أيضًا مجموعة من switchالبيانات ومجموعة من استعلامات قاعدة البيانات المتطابقة تقريبًا، والتي بدورها ستؤدي إلى تعقيد فصلنا وتضخمه بشكل كبير. كيف يمكن جعل كل هذا أسهل؟ دعونا نقوم بترقية التعداد الخاص بنا قليلاً:
@Getter
@AllArgsConstructor
public enum ServiceType {
   CAR("cars_descriptions", "car_id"),
   USER("users_descriptions", "user_id"),
   FILE("files_descriptions", "file_id"),
   PLAN("plans_descriptions", "plan_id"),
   CUSTOMER("customers_descriptions", "customer_id");
   private String tableName;
   private String columnName;
}
الآن أصبح لكل نوع أسماء الحقول الأصلية لجدوله. ونتيجة لذلك، تصبح طريقة إنشاء الوصف:
private static final String CREATE_RELATION_WITH_SERVICE = "INSERT INTO %s(language_id, %s, description) VALUES (?, ?, ?)";
public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description) {
   jdbcTemplate.update(String.format(CREATE_RELATION_WITH_SERVICE, type.getTableName(), type.getColumnName()), languageId, serviceId, description);
   }
مريحة وبسيطة وصغيرة الحجم، ألا تعتقد ذلك؟ إن الإشارة إلى المطور الجيد ليست حتى في عدد المرات التي يستخدم فيها الأنماط، بل في عدد المرات التي يتجنب فيها الأنماط المضادة. الجهل هو أسوأ عدو، لأنك تحتاج إلى معرفة أعداءك عن طريق البصر. حسنا، هذا كل ما لدي لهذا اليوم. شكرا لكل شخص! :)
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION