أرسلت بواسطة دينيس تسيبلاكوف ، مهندس حلول ، DataArtعلى مر السنين ، وجدت أن المبرمجين يكررون نفس الأخطاء من وقت لآخر. لسوء الحظ ، لا تساعد الكتب التي تتناول الجوانب النظرية للتنمية على تجنبها: عادةً ما لا تحتوي الكتب على نصائح عملية ملموسة. وأنا أخمن لماذا ...
التوصية الأولى التي تتبادر إلى الذهن عندما يتعلق الأمر ، على سبيل المثال ،
تسجيل أو تصميم فئة بسيطة للغاية: "لا تجعل هراء صريح". لكن التجربة تظهر أنها بالتأكيد ليست كافية. مجرد تصميم الطبقات في هذه الحالة هو مثال جيد - صداع أبدي ينشأ من حقيقة أن الجميع ينظر إلى هذه المسألة بطريقتهم الخاصة. لذلك ، قررت أن أضع النصائح الأساسية في مقال واحد ، وبعد ذلك سوف تتجنب عددًا من المشاكل النموذجية ، والأهم من ذلك ، أن تنقذ زملائك منها. إذا كانت بعض المبادئ تبدو مألوفة بالنسبة لك (لأنها حقا مألوفة!) - حسنًا ، فقد استقرت بالفعل في القشرة الفرعية ، ويمكن تهنئة فريقك.
سوف أبدي تحفظًا ، في الواقع ، سوف نركز على الفصول فقط للبساطة. يمكن قول الشيء نفسه تقريبًا حول الوظائف أو أي لبنات بناء أخرى للتطبيق.
إذا كان التطبيق يعمل وأداء المهمة ، فإن تصميمه جيد. أم لا؟ يعتمد على الوظيفة الموضوعية للتطبيق ؛ ما هو مناسب تمامًا لتطبيق الهاتف المحمول الذي يجب عرضه مرة واحدة في المعرض ، قد لا يكون مناسبًا لمنصة التداول التي ظل أي بنك يطورها لسنوات. إلى حد ما ، يمكن
تسمية مبدأ
SOLID بالإجابة على هذا السؤال ، لكنه عام للغاية - أرغب في الحصول على بعض الإرشادات الأكثر تحديدًا التي يمكن الإشارة إليها في محادثة مع الزملاء.
التطبيق المستهدف
لأنه لا يمكن أن يكون هناك إجابة عالمية ، أقترح تضييق النطاق. لنفترض أننا نكتب تطبيق أعمال قياسي يقبل الطلبات عبر HTTP أو واجهة أخرى ، وينفذ بعض المنطق فوقها ، ثم يقوم إما بتقديم طلب إلى الخدمة التالية في السلسلة أو بتخزين البيانات المستلمة في مكان ما. للبساطة ، دعنا نفترض أننا نستخدم Spring IoC Framework ، لأنه شائع جدًا الآن وبقية الأطر تشبهه إلى حد كبير. ماذا يمكن أن نقول عن مثل هذا الطلب؟
- يعد الوقت الذي يقضيه المعالج في معالجة طلب واحد أمرًا مهمًا ، ولكن ليس حرجًا - لن تكون هناك زيادة بنسبة 0.1٪ في الطقس.
- لا توجد ذاكرة تيرابايت تحت تصرفنا ، ولكن إذا استغرق التطبيق 50-100 كيلوبايت إضافية ، فلن تكون هذه كارثة.
- بالطبع ، كلما كانت مدة البدء أقصر ، كان ذلك أفضل. ولكن لا يوجد فرق جوهري بين 6 ثوانٍ و 5.9 ثانية.
معايير التحسين
ما هو المهم بالنسبة لنا في هذه الحالة؟
من المرجح أن تستخدم الشركة رمز المشروع لعدة مرات ، وربما أكثر من عشر سنوات.
سيتم تعديل الكود في أوقات مختلفة بواسطة عدة مطورين غير مألوفين مع بعضهم البعض.
من المحتمل أنه خلال بضع سنوات ، سيرغب المطورون في استخدام مكتبة LibXYZ الجديدة أو إطار FrABC.
في مرحلة ما ، قد يتم دمج جزء من الكود أو المشروع بأكمله مع قاعدة الكود لمشروع آخر.
في وسط المديرين ، من المقبول عمومًا أن يتم حل هذه المشكلات بمساعدة الوثائق. الوثائق ، بالطبع ، جيدة ومفيدة ، لأنها رائعة للغاية عندما تبدأ العمل في مشروع بحيث يكون لديك خمس تذاكر مفتوحة معلقة عليك ، ويسأل مدير المشروع عن تقدمك ، وتحتاج إلى قراءة (وتذكر) حوالي 150 صفحات من النص مكتوبة إلى حد بعيد عن الكتاب اللامعين. بالطبع ، كان لديك بضعة أيام أو حتى بضعة أسابيع لتدفق إلى المشروع ، ولكن إذا كنت تستخدم حساب بسيط ، فمن ناحية ، 500000 بايت من الكود ، من ناحية أخرى ، 50 ساعة عمل. اتضح أنه في المتوسط كان من الضروري ضخ 100 كيلو بايت من الشفرة في الساعة. وهنا يعتمد كل شيء كثيرًا على جودة الكود. إذا كان نظيفًا: سهل التجميع ، منظم جيدًا ويمكن التنبؤ به ، يبدو أن التدفق في مشروع ما هو عملية أقل إيلامًا بشكل ملحوظ. ليس الدور الأخير في هذا هو لعبت من قبل تصميم الفصول الدراسية. ليس الأخير.
ما نريد من تصميم الطبقة
من كل ما سبق ، يمكن استخلاص الكثير من الاستنتاجات المثيرة للاهتمام حول الهندسة العامة ، ومجموعة التكنولوجيا ، وعملية التطوير ، وما إلى ذلك. ولكن منذ البداية ، قررنا التحدث عن تصميم الفصل ، دعونا نرى الأشياء المفيدة التي يمكن أن نتعلمها مما قيل سابقًا فيما يتعلق به.
- أرغب في أن يكون مطورًا غير مألوف تمامًا برمز التطبيق قادرًا على فهم ما تقوم به هذه الفئة عند النظر إلى الفصل. والعكس بالعكس - بالنظر إلى المتطلبات الوظيفية أو غير الوظيفية ، يمكنني أن أخمن بسرعة أين يوجد التطبيق في الفئات المسؤولة عنه. حسنًا ، من المستحسن أن لا يتم "تنفيذ" تنفيذ المتطلبات في جميع أنحاء التطبيق ، ولكن يتركز في فئة واحدة أو مجموعة مدمجة من الفئات. اسمحوا لي أن أشرح مع مثال أي نوع من antipattern أعني. لنفترض أننا بحاجة إلى التحقق من أن 10 طلبات من نوع معين لا يمكن تنفيذها إلا من قبل المستخدمين الذين لديهم أكثر من 20 نقطة في حسابهم (بغض النظر عن معنى ذلك). من الطرق السيئة لتنفيذ مثل هذا المطلب إدراج علامة في بداية كل طلب. ثم سيتم نشر المنطق على 10 طرق ، في وحدات تحكم مختلفة. هناك طريقة جيدة لإنشاء عامل تصفية أو WebRequestInterceptor والتحقق من كل شيء في مكان واحد.
- أريد ألا تؤثر التغييرات في فصل واحد على عقد الفصل ، أو ، (لنكن واقعيين!) على الأقل لا تؤثر كثيرًا على الفئات الأخرى. وبعبارة أخرى ، أود أن أغلف تنفيذ عقد فئة.
- أرغب في أن يكون ذلك ممكنًا ، عند تغيير عقد الفصل ، من خلال المرور بسلسلة المكالمات وجعل استخدامات ، ابحث عن الفئات التي يؤثر هذا التغيير عليها. وهذا هو ، أريد الطبقات ليس لها التبعيات غير المباشرة.
- إذا كان ذلك ممكنًا ، أود أن أرى أن عمليات معالجة الطلبات التي تتكون من عدة خطوات من مستوى واحد لا يتم تلطيخها بواسطة رمز العديد من الفئات ، ولكن يتم وصفها في نفس المستوى. إنه لأمر جيد للغاية إذا تم وضع الكود الذي يصف عملية المعالجة هذه على شاشة واحدة داخل إحدى الطرق باسم واضح. على سبيل المثال ، نحتاج إلى العثور على جميع الكلمات في سطر ما ، وإجراء مكالمة مع خدمة تابعة لجهة خارجية لكل كلمة ، والحصول على وصف للكلمة ، وتطبيق التنسيق على الوصف وحفظ النتائج في قاعدة البيانات. هذا هو سلسلة واحدة من الإجراءات في 4 خطوات. من المريح للغاية فهم الكود وتغيير منطقه عندما يكون هناك طريقة تنتقل فيها هذه الخطوات واحدة تلو الأخرى.
- أريد حقًا تنفيذ نفس الأشياء في التعليمات البرمجية بالطريقة نفسها. على سبيل المثال ، إذا وصلنا إلى قاعدة البيانات على الفور من وحدة التحكم ، فمن الأفضل القيام بذلك في كل مكان (على الرغم من أنني لن أسمي مثل هذه الممارسة الجيدة للتصميم). وإذا دخلنا بالفعل في مستويات الخدمات والمستودعات ، فمن الأفضل عدم الاتصال بقاعدة البيانات مباشرة من وحدة التحكم.
- أود أن لا يكون عدد الفئات / الواجهات غير المسؤولة مباشرة عن المتطلبات الوظيفية وغير الوظيفية كبيرًا جدًا. من الصعب للغاية العمل مع مشروع يوجد به واجهات اثنين لكل فصل مع المنطق ، والتسلسل الهرمي المعقد للميراث من خمسة فصول ، ومصنع للفصل ومصنع للفصل التجريدي.
توصيات عملية
بعد صياغة رغبات ، يمكننا تحديد خطوات محددة تسمح لنا بتحقيق أهدافنا.
طرق ثابتة
كاحماء ، سأبدأ بقاعدة بسيطة نسبيًا. يجب عدم إنشاء طرق ثابتة إلا إذا كانت ضرورية لتشغيل إحدى المكتبات المستخدمة (على سبيل المثال ، تحتاج إلى إنشاء برنامج تسلسلي لنوع البيانات).
من حيث المبدأ ، لا حرج في استخدام الأساليب الثابتة. إذا كان سلوك الطريقة يعتمد كليا على معاييرها ، فلماذا لا تجعلها ثابتة حقا. ولكن عليك أن تأخذ بعين الاعتبار حقيقة أننا نستخدم Spring IoC ، والذي يعمل على ربط مكونات تطبيقنا. ربيع IoC يتعامل مع مفاهيم الفول ونطاقهم. يمكن خلط هذا النهج مع الأساليب الثابتة المجمعة في فصول ، ولكن فهم مثل هذا التطبيق وحتى تغيير شيء فيه (على سبيل المثال ، إذا كنت بحاجة إلى تمرير بعض المعلمات العمومية إلى طريقة أو فئة) قد يكون من الصعب للغاية.
في الوقت نفسه ، فإن الطرق الثابتة مقارنة بسلالات IoC تعطي ميزة ضئيلة للغاية في سرعة استدعاء الأسلوب. وعلى هذا ، ربما ، تنتهي المزايا.
إذا لم تكن تقوم بإنشاء وظيفة عمل تتطلب عددًا كبيرًا من مكالمات فائق السرعة بين فئات مختلفة ، فمن الأفضل عدم استخدام الأساليب الثابتة.
هنا قد يسأل القارئ: "لكن ماذا عن فصول StringUtils و IOUtils؟" في الواقع ، لقد تطور تقليد في عالم جافا - لوضع وظائف إضافية للعمل مع السلاسل وتدفقات المدخلات والمخرجات في أساليب ثابتة وجمعها تحت مظلة فئات SomethingUtils. ولكن هذا التقليد يبدو لي مطحلب إلى حد ما. إذا تابعت ذلك ، بالطبع ، فمن غير المتوقع حدوث ضرر كبير - يتم استخدام جميع مبرمجي Java عليه. ولكن ليس هناك معنى في مثل هذا العمل الطقسي. من ناحية ، لماذا لا تجعل حبة StringUtils ، من ناحية أخرى ، إذا كنت لا تجعل الحبة وجميع الأساليب المساعدة ثابتة ، فلنجعل بالفعل فئات المظلات الثابتة StockTradingUtils و BlockChainUtils. البدء في وضع المنطق في طرق ثابتة ، من الصعب رسم الحدود والتوقف. أنصحك بعدم البدء.
أخيرًا ، لا تنس أنه من خلال Java 11 ، فإن العديد من الأساليب المساعدة التي كانت تجول المطورين من مشروع إلى آخر لعدة عقود ، إما أصبحت جزءًا من المكتبة القياسية ، أو دمجت في مكتبات ، على سبيل المثال ، في Google Guava.
ذرية ، عقد الطبقة المدمجة
هناك قاعدة بسيطة تنطبق على تطوير أي نظام برمجي. عند النظر إلى أي فصل ، يجب أن تكون قادرًا بسرعة وبشكل مضغوط ، دون اللجوء إلى الحفريات الطويلة ، وشرح ما الذي تفعله هذه الفئة. إذا كان من المستحيل وضع التفسير في فقرة واحدة (ليس من الضروري ، معبراً عنه في جملة واحدة) ، فقد يكون من المفيد التفكير في هذه الفئة وتقسيمها إلى عدة فئات ذرية. على سبيل المثال ، الفصل "يبحث عن ملفات نصية على قرص ويحسب عدد الأحرف Z في كل منها" - مرشح جيد للتحلل "يبحث على قرص" + "يحسب عدد الأحرف".
من ناحية أخرى ، لا تصنع فصولًا صغيرة جدًا ، تم تصميم كل منها لفعل واحد. ولكن ما حجم يجب أن يكون الفصل بعد ذلك؟ القواعد الأساسية هي كما يلي:
- من الناحية المثالية ، عندما يتطابق عقد الفصل مع وصف وظيفة العمل (أو الوظيفة الفرعية ، اعتمادًا على كيفية ترتيب المتطلبات). هذا غير ممكن دائمًا: إذا أدت محاولة الامتثال لهذه القاعدة إلى إنشاء رمز مرهق وغير واضح ، فمن الأفضل تقسيم الفصل إلى أجزاء أصغر.
- المقياس الجيد لتقييم جودة العقد الطبقي هو نسبة تعقيده الجوهري إلى درجة تعقيد العقد. على سبيل المثال ، قد يبدو عقد الفصل الدراسي جيدًا (وإن كان رائعًا) كما يلي: "يحتوي الفصل على طريقة واحدة تستقبل سطرًا مع وصف للموضوع باللغة الروسية عند الإدخال وتكوين قصة جودة أو حتى قصة حول موضوع معين كنتيجة لذلك." هنا ، العقد بسيط ومفهوم بشكل عام. تنفيذه معقد للغاية ، لكن التعقيد مخفي داخل الفصل.
لماذا هذه القاعدة مهمة؟
- أولاً ، إن القدرة على أن توضح لنفسك بوضوح ما يفعله كل فصل دراسي مفيد دائمًا. لسوء الحظ ، لا يمكن لمطوري المشروع القيام بذلك. غالبًا ما تسمع شيئًا مثل: "حسنًا ، هذه مجموعة غلاف فوق فئة المسار ، والتي صنعناها بطريقة ما وأحيانًا نستخدمها بدلاً من المسار. لديها أيضًا طريقة يمكنها مضاعفة جميع مسارات File.separator - نحتاج إلى هذه الطريقة عند حفظ التقارير في السحابة ، ولسبب ما انتهى بها الأمر في فئة المسار. "
- الدماغ البشري قادر على العمل في وقت واحد مع ما لا يزيد عن خمسة إلى عشرة أشياء. معظم الناس ليس لديهم أكثر من سبعة. وفقًا لذلك ، إذا كان المطور بحاجة إلى العمل مع أكثر من سبعة كائنات من أجل حل مشكلة ، فسيفتقد إما شيئًا ما أو سيضطر إلى حزم عدة كائنات تحت "مظلة" منطقية واحدة. وإذا كنت لا تزال بحاجة إلى حزمها ، فلماذا لا تفعل ذلك على الفور ، وبوعي ، وإعطاء هذه المظلة اسمًا ذا مغزى وعقدًا واضحًا.
كيفية التحقق من أن كل شيء محبب بما فيه الكفاية؟ اطلب من زميلك أن يمنحك 5 (خمس) دقائق. خذ جزء التطبيق الذي تعمل حاليًا على إنشائه. بالنسبة لكل فصل ، اشرح لزميل ما الذي تقوم به هذه الفئة بالضبط. إذا لم تكن لائقًا في غضون 5 دقائق ، أو لم يستطع أحد الزملاء فهم سبب الحاجة إلى هذه الفئة أو تلك الفئة ، فربما يجب عليك تغيير شيء ما. حسنًا ، أو عدم تغيير وإجراء التجربة مرة أخرى ، مع زميل آخر.
تبعيات الطبقة
افترض أننا بحاجة إلى تحديد أقسام نصية مرتبطة أطول من 100 بايت لملف PDF معبأة في أرشيف ZIP وحفظها في قاعدة البيانات. يشبه المضاد الشهير في مثل هذه الحالات:
- هناك فئة تفتح أرشيف ZIP ، وتبحث عن ملف PDF فيه وتعيده كـ InputStream.
- هذه الفئة لها رابط لفصل يبحث في فقرات PDF من النص.
- للفصل الذي يعمل مع PDF ، بدوره ، رابط إلى الفصل الذي يخزن البيانات في قاعدة البيانات.
من ناحية ، يبدو كل شيء منطقيًا: البيانات المستلمة ، والتي يطلق عليها مباشرة الفئة التالية في السلسلة. ولكن في الوقت نفسه ، تخلط عقود الفئة في الجزء العلوي من السلسلة في العقود والتبعيات لجميع الفئات التي تدخل في السلسلة التي تقف وراءها. من الأصح بكثير جعل هذه الفئات ذرية ومستقلة عن بعضها البعض ، وإنشاء فئة أخرى تنفذ بالفعل منطق المعالجة عن طريق ربط هذه الفئات الثلاثة مع بعضها البعض.
كيف لا تفعل ذلك:
ما هو الخطأ هنا؟ يقوم الفصل الذي يعمل مع ملفات ZIP بتمرير البيانات إلى الفصل الذي يقوم بمعالجة PDF ، وهذا بدوره يقوم بتمرير البيانات إلى الفصل الذي يعمل مع قاعدة البيانات. هذا يعني أن الفئة التي تعمل مع ZIP ، نتيجة لذلك ، لسبب ما تعتمد على الفئات التي تعمل مع قاعدة البيانات. بالإضافة إلى ذلك ، فإن منطق المعالجة ينتشر على ثلاثة فصول ، ولكي نفهم ذلك ، يجب أن نتجاوز كل الفئات الثلاثة. ماذا لو كنت بحاجة إلى فقرات من النص تم الحصول عليها من PDF لتمريرها إلى خدمة طرف ثالث عبر مكالمة REST؟ ستحتاج إلى تغيير الفئة التي تعمل مع PDF ، وسحبها أيضًا مع REST.
كيف تفعل ذلك:
هنا لدينا أربعة فصول:
- فئة تعمل فقط مع أرشيف ZIP وتُرجع قائمة بملفات PDF (يمكن للمرء أن يجادل - إعادة الملفات سيئة - إنها كبيرة وستقطع التطبيق. ولكن دعنا نقرأ كلمة "إرجاع" في هذه الحالة بمعنى واسع. على سبيل المثال ، تقوم بإرجاع Stream من InputStream. )
- الفئة الثانية هي المسؤولة عن العمل مع قوات الدفاع الشعبي.
- لا تعرف الفئة الثالثة ولا يمكنها فعل أي شيء باستثناء حفظ الفقرات في قاعدة البيانات.
- أما الفئة الرابعة ، والتي تتكون من عدة سطور من الكود حرفيًا ، فتحتوي على كل منطق الأعمال الذي يتلاءم مع شاشة واحدة.
وأؤكد مرة أخرى أنه في عام 2019 في جاوة هناك نوعان من الأقل جيدًا (وأقل إلى حد ما
جيد) طرق عدم نقل الملفات وقائمة كاملة بجميع الفقرات ككائنات في الذاكرة. هذا هو:
- جافا ستريم API
- الاسترجاعات أي أن الفصل الذي يحتوي على وظيفة عمل لا ينقل البيانات مباشرة ، لكنه يقول ZIP Extractor: إليك رد اتصال لك ، وابحث عن ملفات PDF في ملف ZIP ، وأنشئ InputStream لكل ملف واتصل باستدعاء رد الاتصال المنقول معه.
السلوك الضمني
عندما لا نحاول حل مشكلة جديدة تمامًا لم يحلها أحد من قبل ، بل نفعل شيئًا فعله مطورو آخرون بالفعل عدة مئات (أو مئات الآلاف) مرات من قبل ، فإن جميع أعضاء الفريق لديهم بعض التوقعات فيما يتعلق بالتعقيد السيكلوي وكثافة الموارد في الحل. . على سبيل المثال ، إذا كنا بحاجة إلى العثور في الملف على كل الكلمات التي تبدأ بالحرف z ، فهذه قراءة تسلسلية لمرة واحدة للملف في كتل من القرص. أي أنك إذا ركزت على
https://gist.github.com/jboner/2841832 - ستحتاج هذه العملية إلى عدة مايكروثانية لكل 1 ميجابايت ، وربما ، اعتمادًا على بيئة البرمجة وتحميل النظام ، أو عدة عشرات أو حتى مائة ثانية ، لكن ليس ثانية على الإطلاق. سوف يستغرق الأمر عدة عشرات من الكيلوبايت من الذاكرة (نترك السؤال الذي نفعله بالنتائج ، وهذا هو مصدر قلق فئة أخرى) ، وعلى الأرجح سيشغل الكود شاشة واحدة. في الوقت نفسه ، نتوقع عدم استخدام موارد النظام الأخرى. وهذا يعني أن الكود لن ينشئ سلاسل رسائل ، وكتابة البيانات على القرص ، وإرسال الحزم عبر الشبكة ، وحفظ البيانات في قاعدة البيانات.
هذا هو التوقع المعتاد لاستدعاء الطريقة:
zWordFinder.findZWords(inputStream). ...
إذا كانت شفرة صفك لا تلبي هذه المتطلبات لسبب معقول ، على سبيل المثال ، لتصنيف كلمة إلى z وليس z ، فأنت بحاجة إلى استدعاء طريقة REST في كل مرة (لا أعرف لماذا قد يكون ذلك ضروريًا ، ولكن دعنا نتخيل هذا) من الضروري أن تكتب بعناية فائقة في عقد الفصل ، وأنه من الجيد جدًا أن يشير اسم الأسلوب إلى أن الطريقة تعمل في مكان ما للتشاور.
إذا لم يكن لديك سبب معقول للسلوك الضمني ، فقم بإعادة كتابة الفصل الدراسي.
كيف نفهم توقعات التعقيد وكثافة الموارد في الطريقة؟ تحتاج إلى اللجوء إلى إحدى هذه الطرق البسيطة:
- مع الخبرة ، واكتساب آفاق واسعة إلى حد ما.
- اسأل زميلًا - يمكن القيام بذلك دائمًا.
- قبل البدء في التطوير ، تحدث مع أعضاء الفريق حول خطة التنفيذ.
- لطرح السؤال التالي: "لكنني لا أستخدم الكثير من الموارد الزائدة في هذه الطريقة؟" هذا عادة ما يكون كافيا.
لا تحتاج إلى المشاركة في عملية التحسين أيضًا - حيث إن توفير 100 بايت عند استخدامها في الفئة 100،000 لا يكون له معنى كبير بالنسبة لمعظم التطبيقات.
تفتح هذه القاعدة نافذة على العالم الغني من الهندسة الزائدة ، حيث تخفي إجابات لأسئلة مثل "لماذا لا تقضي شهرًا لحفظ 10 بايت من الذاكرة في تطبيق يحتاج إلى 10 غيغابايت للعمل". لكنني لن أطور هذا الموضوع هنا. إنها تستحق مقالة منفصلة.
أسماء الطرق الضمنية
في برمجة Java ، يوجد حاليًا العديد من الاتفاقيات الضمنية المتعلقة بأسماء الفئات وسلوكهم. لا يوجد الكثير منهم ، لكن من الأفضل عدم كسرها. سأحاول سرد تلك التي تتبادر إلى ذهني:
- المُنشئ - يُنشئ مثيلًا للفصل الدراسي ، ويمكنه إنشاء بعض هياكل البيانات المتفرعة إلى حد ما ، لكنه لا يعمل مع قاعدة البيانات ، ولا يكتب إلى القرص ، ولا يرسل البيانات عبر الشبكة (أقول ، يمكن للمسجل المدمج أن يفعل كل ذلك ، لكن هذه قصة مختلفة في في أي حال ، فإنه يقع على ضمير مكون التسجيل).
- Getter - getSomething () - بإرجاع نوع من بنية الذاكرة من أعماق الكائن. مرة أخرى ، لا تكتب إلى القرص ، ولا تقوم بإجراء عمليات حسابية معقدة ، ولا ترسل بيانات عبر الشبكة ، ولا تعمل مع قاعدة البيانات (باستثناء عندما يكون هذا حقل ORM كسولًا ، وهذا مجرد أحد الأسباب التي يجب أن تستخدم بها الحقول البطيئة بعناية فائقة) .
- Setter - setSomething (شيء ما) - يحدد قيمة بنية البيانات ، لا يقوم بحسابات معقدة ، لا يرسل بيانات عبر الشبكة ، لا يعمل مع قاعدة البيانات. عادةً ، لا يُتوقع من الضابط السلوك أو الاستهلاك الضمني لأي من موارد الحوسبة المهمة.
- يساوي () و hashcode () - لا شيء متوقع على الإطلاق ، باستثناء العمليات الحسابية البسيطة والمقارنات في كمية تعتمد خطيًا على حجم بنية البيانات. بمعنى أنه إذا استدعينا hashcode لكائن من ثلاثة حقول بدائية ، فمن المتوقع أن يتم تنفيذ N * 3 تعليمات حسابية بسيطة.
- toSomething () - من المتوقع أيضًا أن تقوم هذه الطريقة بتحويل نوع بيانات واحد إلى آخر ، ولا يحتاج هذا التحويل إلا إلى مقدار ذاكرة مماثل لحجم الهياكل ، ووقت المعالج ، والذي يعتمد خطيًا على حجم الهياكل. تجدر الإشارة هنا إلى أن تحويل الكتابة لا يمكن أن يتم دائمًا بشكل خطي ، على سبيل المثال ، يمكن أن يكون تحويل صورة بكسل إلى تنسيق SVG إجراءً غير تافه للغاية ، ولكن في هذه الحالة ، من الأفضل تسمية الطريقة بشكل مختلف. على سبيل المثال ، يبدو اسم computeAndConvertToSVG () غريبًا إلى حد ما ، لكنه يشير على الفور إلى أن بعض الحسابات المهمة تجري في الداخل.
سأقدم مثالا. فعلت مؤخرا مراجعة التطبيق. حسب منطق العمل ، أعلم أن التطبيق في مكان ما في الكود يشترك في قائمة انتظار RabbitMQ. أنا أسير الشفرة - لا يمكنني العثور على هذا المكان. أنا أبحث مباشرة عن نداء للأرنب ، لقد بدأت في الصعود ، وأنا في طريقي إلى تدفق العمل حيث يتم الاشتراك بالفعل - لقد بدأت أقسم. كيف يبدو في الكود:
- يتم استدعاء أسلوب service.getQueueListener (tickerName) - يتم تجاهل النتيجة التي تم إرجاعها. قد ينبهك هذا ، لكن مثل هذا الرمز الذي يتم فيه تجاهل نتائج الطريقة ليس هو الوحيد في التطبيق.
- من الداخل ، يتم التحقق من tickerName لإلغاء الصلاحية ويتم استدعاء أسلوب getQueueListenerByName (tickerName) آخر.
- بداخلها ، يتم أخذ مثيل فئة QueueListener من التجزئة باسم المؤشر (إذا لم يكن كذلك ، يتم إنشاؤه) ، ويتم استدعاء أسلوب getSubscription () عليه.
- والآن ، داخل طريقة getSubscription () ، يتم الاشتراك بالفعل. ويحدث ذلك في مكان ما في منتصف طريقة بحجم ثلاث شاشات.
سأخبرك بصراحة - دون المرور عبر السلسلة بأكملها وبدون قراءة عشرات الشاشات اليقظة ، كان من غير الواقعي تخمين مكان الاشتراك. إذا كانت الطريقة تسمى subscribeToQueueByTicker (tickerName) ، فستوفر لي الكثير من الوقت.
فصول فائدة
هناك كتاب رائع لأنماط التصميم: عناصر من البرامج الموجهة للكائنات القابلة لإعادة الاستخدام (1994) ، وغالبًا ما يطلق عليه GOF (عصابة الأربعة ، من خلال عدد المؤلفين). تكمن فائدة هذا الكتاب في أنه قدم للمطورين من مختلف البلدان لغة واحدة لوصف أنماط تصميم الفصل. الآن بدلاً من "الفصل مضمون في الوجود في حالة واحدة فقط ولديها نقطة وصول ثابتة" يمكننا أن نقول "مفردة". نفس الكتاب تسبب في أضرار ملحوظة للعقول الهشة. يتم وصف هذا الضرر جيدًا من خلال اقتباس من أحد المنتديات "الزملاء ، أحتاج إلى إنشاء متجر على الويب ، أخبرني ما هي القوالب التي أحتاج إليها للبدء". بمعنى آخر ، يميل بعض المبرمجين إلى إساءة استخدام أنماط التصميم ، وفي أي مكان يمكنك إدارته باستخدام فصل واحد ، في بعض الأحيان ينشئون خمسة أو ستة في وقت واحد - فقط في حالة "لمزيد من المرونة".
كيف تقرر ما إذا كنت بحاجة إلى مصنع فئة مجردة (أو نمط آخر أكثر تعقيدًا من واجهة) أم لا؟ هناك بعض الاعتبارات البسيطة:
- إذا كنت تكتب طلبًا في Spring ، فلن تكون هناك حاجة إلى 99٪ من الوقت. الربيع يقدم لك لبنات بناء على مستوى أعلى ، واستخدامها. الحد الأقصى الذي قد تجده مفيدًا هو فئة مجردة.
- إذا كانت النقطة 1 لا تزال لا تعطيك إجابة واضحة - تذكر أن كل قالب هو +1000 نقطة لتعقيد التطبيق. تحليل بعناية ما إذا كانت فوائد استخدام القالب تفوق الضرر من ذلك. بالانتقال إلى الاستعارة ، تذكر أن كل دواء لا يشفي فحسب ، بل يضر أيضًا. لا تشرب كل الحبوب مرة واحدة.
مثال جيد على كيف لا تحتاج إلى ذلك ، يمكنك أن ترى
هنا .
استنتاج
لتلخيص ، أود أن أشير إلى أنني أدرجت معظم التوصيات الأساسية. حتى أنني لن أجعلها في شكل مقال - فهي واضحة للغاية. لكن على مدار العام الماضي ، صادفت طلبات في كثير من الأحيان تم فيها انتهاك الكثير من هذه التوصيات. دعنا نكتب رمزًا بسيطًا سهل القراءة وسهل الصيانة.