CodeGym /مدونة جافا /Random-AR /قواعد الترميز: من إنشاء النظام إلى العمل مع الكائنات
John Squirrels
مستوى
San Francisco

قواعد الترميز: من إنشاء النظام إلى العمل مع الكائنات

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

الأنظمة

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

مصدر الصورة

مراحل تصميم النظام

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

المبادئ والمفاهيم العامة لتصميم النظام

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

AOP

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

صلب

عند تصميم النظام، فإن مبادئ SOLID المعروفة تستحق النظر فيها:

S (مسؤولية فردية)، O (مفتوح ومغلق)، L (استبدال Liskov)، I (فصل الواجهة)، D (انعكاس التبعية).

لن نتناول كل مبدأ على حدة. قد يكون ذلك خارج نطاق هذه المقالة قليلاً، ولكن يمكنك قراءة المزيد هنا .

واجهه المستخدم

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

فصل

قواعد الترميز: من إنشاء النظام إلى العمل مع الكائنات - 3

مصدر الصورة

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

حجم الصف

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

أشياء

التغليف

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

قانون ديميتر

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

بنية البيانات

بنية البيانات عبارة عن مجموعة من العناصر ذات الصلة. عند اعتبار كائن ما بمثابة بنية بيانات، هناك مجموعة من عناصر البيانات التي تعمل عليها الأساليب. ويفترض ضمنا وجود هذه الأساليب. أي أن بنية البيانات هي كائن غرضه تخزين البيانات المخزنة والعمل معها (معالجتها). الفرق الرئيسي بينه وبين الكائن العادي هو أن الكائن العادي عبارة عن مجموعة من الأساليب التي تعمل على عناصر البيانات التي يُفترض ضمنيًا وجودها. هل تفهم؟ الجانب الرئيسي للكائن العادي هو الأساليب. المتغيرات الداخلية تسهل عملها الصحيح. ولكن في بنية البيانات، توجد طرق لدعم عملك باستخدام عناصر البيانات المخزنة، والتي تعتبر ذات أهمية قصوى هنا. أحد أنواع بنية البيانات هو كائن نقل البيانات (DTO). هذه فئة تحتوي على متغيرات عامة ولا توجد طرق (أو طرق للقراءة/الكتابة فقط) تُستخدم لنقل البيانات عند العمل مع قواعد البيانات، وتحليل الرسائل من المقابس، وما إلى ذلك. ولا يتم عادةً تخزين البيانات في مثل هذه الكائنات لفترة طويلة. يتم تحويله على الفور تقريبًا إلى نوع الكيان الذي يعمل به تطبيقنا. والكيان بدوره هو أيضًا بنية بيانات، ولكن الغرض منه هو المشاركة في منطق الأعمال على مستويات مختلفة من التطبيق. الغرض من DTO هو نقل البيانات من/إلى التطبيق. مثال على DTO:
@Setter
@Getter
@NoArgsConstructor
public class UserDto {
    private long id;
    private String firstName;
    private String lastName;
    private String email;
    private String password;
}
يبدو كل شيء واضحا بما فيه الكفاية، ولكن هنا نتعرف على وجود الهجينة. الهجينة هي كائنات تحتوي على طرق للتعامل مع المنطق المهم، وتخزين العناصر الداخلية، وتتضمن أيضًا طرق الوصول (الحصول على/المجموعة). مثل هذه الكائنات فوضوية وتجعل من الصعب إضافة أساليب جديدة. يجب عليك تجنبها، لأنه ليس من الواضح ما الغرض منها - تخزين العناصر أو تنفيذ المنطق؟

مبادئ خلق المتغيرات

دعونا نفكر قليلا في المتغيرات. وبشكل أكثر تحديدًا، دعونا نفكر في المبادئ التي تنطبق عند إنشائها:
  1. من الناحية المثالية، يجب عليك الإعلان عن متغير وتهيئته قبل استخدامه مباشرة (لا تقم بإنشاء متغير ونسيانه).
  2. كلما أمكن، قم بإعلان المتغيرات كنهائية لمنع قيمتها من التغيير بعد التهيئة.
  3. لا تنس متغيرات العداد، والتي نستخدمها عادة في نوع ما من حلقات for . وهذا يعني، لا تنسى التخلص منها. وإلا فإن كل منطقنا قد ينكسر.
  4. يجب أن تحاول تهيئة المتغيرات في المُنشئ.
  5. إذا كان هناك خيار بين استخدام كائن بمرجع أو بدونه ( new SomeObject() ) فاختر بدونه، لأنه بعد استخدام الكائن سيتم حذفه أثناء دورة جمع البيانات المهملة التالية ولن يتم إهدار موارده.
  6. حافظ على عمر المتغير (المسافة بين إنشاء المتغير وآخر مرة تمت الإشارة إليه) قصيرًا قدر الإمكان.
  7. تهيئة المتغيرات المستخدمة في الحلقة قبل الحلقة مباشرة، وليس في بداية الطريقة التي تحتوي على الحلقة.
  8. ابدأ دائمًا بالنطاق الأكثر محدودية وقم بالتوسيع فقط عند الضرورة (يجب أن تحاول جعل المتغير محليًا قدر الإمكان).
  9. استخدم كل متغير لغرض واحد فقط.
  10. تجنب المتغيرات ذات الأغراض الخفية، مثل تقسيم المتغير بين مهمتين – وهذا يعني أن نوعه غير مناسب لحل إحداهما.

طُرق

قواعد الترميز: من إنشاء النظام إلى العمل مع الكائنات - 4

من فيلم "حرب النجوم: الحلقة الثالثة - انتقام السيث" (2005)

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

  2. القاعدة رقم 2 — if و else و while وغيرها من العبارات لا يجب أن تحتوي على كتل متداخلة بشكل كبير: الكثير من التداخل يقلل بشكل كبير من سهولة قراءة التعليمات البرمجية. من الناحية المثالية، يجب ألا يكون لديك أكثر من كتلتين متداخلتين {} .

    ومن المستحسن أيضًا الاحتفاظ بالكود الموجود في هذه الكتل مضغوطًا وبسيطًا.

  3. القاعدة رقم 3 - يجب أن تؤدي الطريقة عملية واحدة فقط. أي أنه إذا كانت إحدى الطرق تؤدي جميع أنواع المنطق المعقد، فإننا نقوم بتقسيمها إلى طرق فرعية. ونتيجة لذلك، فإن الطريقة نفسها ستكون واجهة هدفها استدعاء كافة العمليات الأخرى بالترتيب الصحيح.

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

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

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

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

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

  7. إذا كانت الطريقة تحتوي على عدد كبير من معلمات الإدخال (الحد الأقصى هو 7، ولكن يجب أن تبدأ في التفكير بعد 2-3)، فيجب تجميع بعض الوسائط في كائن منفصل.

  8. إذا كان هناك عدة طرق متشابهة (محملة بشكل زائد)، فيجب تمرير معلمات مماثلة بنفس الترتيب: وهذا يحسن سهولة القراءة وسهولة الاستخدام.

  9. عندما تقوم بتمرير معلمات إلى طريقة ما، يجب عليك التأكد من أنها كلها مستخدمة، وإلا لماذا تحتاج إليها؟ قم بقطع أي معلمات غير مستخدمة من الواجهة وانتهي منها.

  10. لا تبدو المحاولة/التقاط جميلة جدًا بطبيعتها، لذا سيكون من الجيد نقلها إلى طريقة وسيطة منفصلة (طريقة للتعامل مع الاستثناءات):

    public void exceptionHandling(SomeObject obj) {
        try {
            someMethod(obj);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

لقد تحدثت عن التعليمات البرمجية المكررة أعلاه، ولكن اسمحوا لي أن أكرر مرة أخرى: إذا كانت لدينا طريقتان برمز متكرر، فسنحتاج إلى نقلهما إلى طريقة منفصلة. وهذا سيجعل كلاً من الطريقة والفئة أكثر إحكاما. لا تنس القواعد التي تحكم الأسماء: ستتم مناقشة التفاصيل حول كيفية تسمية الفئات والواجهات والأساليب والمتغيرات بشكل صحيح في الجزء التالي من المقالة. ولكن هذا كل ما لدي لك اليوم.
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION