إنشاء رمز معياري حقًا بدون تبعيات

نشرت: 2022-03-11

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

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

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

99 بق صغير في الكود. 99 البق الصغير. خذ واحدة لأسفل ، وقم بتثبيتها حولها ،

... 127 أخطاءً صغيرةً في الكود.

ما هو شعورك حيال العمل في هذا المشروع الآن؟ إذا كنت مثلي ، فمن المحتمل أن تبدأ في فقدان حافزك. إنه مجرد ألم لتطوير هذا التطبيق ، لأن كل تغيير في الكود الحالي يمكن أن يكون له عواقب غير متوقعة.

هذه التجربة شائعة في عالم البرمجيات ويمكن أن تفسر سبب رغبة العديد من المبرمجين في التخلص من شفرة المصدر الخاصة بهم وإعادة كتابة كل شيء.

أسباب تباطؤ تطوير البرامج بمرور الوقت

إذن ما سبب هذه المشكلة؟

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

لذلك يوجد لديك. السبب الحقيقي لمشاكلنا هو زيادة التعقيد من جميع التبعيات التي يمتلكها كودنا.

كرة كبيرة من الطين وكيفية تقليلها

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

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

تصور لـ "كرة الطين الكبيرة" الخاصة بـ Apache Hadoop ، مع بضع عشرات من العقد ومئات الخطوط التي تربطها ببعضها البعض.

"كرة الطين الكبيرة" لأباتشي هادوب

حل برمز معياري

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

كيف تحل الصناعات الأخرى هذه المشكلة

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

رسم تخطيطي تقني لآلية فيزيائية وكيف تتلاءم قطعها معًا. يتم ترقيم القطع بالترتيب الذي سيتم إرفاقها بعد ذلك ، لكن هذا الترتيب من اليسار إلى اليمين يذهب 5 ، 3 ، 4 ، 1 ، 2.

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

هل يمكننا تكرار ذلك في صناعة البرمجيات؟

طبعا نحن نستطيع! باستخدام واجهات وعكس مبدأ التحكم ؛ أفضل جزء هو حقيقة أنه يمكن استخدام هذا الأسلوب في أي لغة موجهة للكائنات: Java و C # و Swift و TypeScript و JavaScript و PHP— والقائمة تطول وتطول. لا تحتاج إلى أي إطار عمل خيالي لتطبيق هذه الطريقة. تحتاج فقط إلى الالتزام ببعض القواعد البسيطة والبقاء منضبطًا.

قلب السيطرة هو صديقك

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

دعونا نرى في مثال بسيط كيف يمكننا فصل نظامنا لإنشاء كود معياري. تم تنفيذ المخططات أدناه كتطبيقات Java بسيطة. يمكنك العثور عليها في مستودع GitHub هذا.

مشكلة

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

يستخدم Main الخدمات A و B و C ، والتي يستخدم كل منها Util. تستخدم الخدمة C أيضًا الخدمة A.

لماذا حصل حقن التبعية على خطأ

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

العمارة السابقة ولكن مع حقن التبعية. تستخدم Main الآن خدمة الواجهة A و B و C ، والتي يتم تنفيذها بواسطة الخدمات المقابلة لها. تستخدم الخدمتان A و C كلاهما Interface Service B و Interface Util ، والتي يتم تنفيذها بواسطة Util. تستخدم الخدمة C أيضًا Interface Service A. تعتبر كل خدمة مع واجهتها عنصرًا.

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

حل الكود المعياري

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

تم تعديل رسم تخطيطي للتطبيق لاستخدام بنية العنصر. الاستخدامات الرئيسية Util وجميع الخدمات الثلاث. يقوم Main أيضًا بتنفيذ مستمع لكل خدمة ، والذي تستخدمه تلك الخدمة. يعتبر المستمع والخدمة معًا عنصرًا.

يرجى ملاحظة أنه في هذه البنية ، فإن الفئة Main فقط لها تبعيات متعددة. تقوم بتوصيل جميع العناصر معًا وتغليف منطق الأعمال للتطبيق.

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

أخيرًا ، يمكنك البدء في كتابة التعليمات البرمجية المستقلة مرة أخرى ، تمامًا كما في بداية مشروعك الأخير.

نمط العنصر

دعنا نحدد نمط العنصر الهيكلي حتى نتمكن من إنشائه بطريقة قابلة للتكرار.

يتكون أبسط نسخة من العنصر من شيئين: فئة العنصر الرئيسي والمستمع. إذا كنت تريد استخدام عنصر ما ، فأنت بحاجة إلى تنفيذ المستمع وإجراء مكالمات إلى الفصل الرئيسي. فيما يلي رسم تخطيطي لأبسط تكوين:

رسم تخطيطي لعنصر واحد ومستمعه داخل التطبيق. كما في السابق ، يستخدم التطبيق العنصر الذي يستخدم المستمع الخاص به والذي يتم تنفيذه بواسطة التطبيق.

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

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

رسم تخطيطي بسيط لعنصر أكثر تعقيدًا. هنا ، يتكون المعنى الأكبر لكلمة "عنصر" من ستة أجزاء: View؛ المنطق A و B و C ؛ جزء؛ ومستمع العنصر. العلاقات بين الأخيرين والتطبيق هي نفسها كما كانت من قبل ، لكن العنصر الداخلي يستخدم أيضًا المنطق A و C. يستخدم المنطق C المنطق A و B. يستخدم المنطق A المنطق B والعرض.

دعونا أيضًا نلقي نظرة على مثال "Hello World" البسيط الذي تم إنشاؤه في Java.

 public class Main { interface ElementListener { void printOutput(String message); } static class Element { private ElementListener listener; public Element(ElementListener listener) { this.listener = listener; } public void sayHello() { String message = "Hello World of Elements!"; this.listener.printOutput(message); } } static class App { public App() { } public void start() { // Build listener ElementListener elementListener = message -> System.out.println(message); // Assemble element Element element = new Element(elementListener); element.sayHello(); } } public static void main(String[] args) { App app = new App(); app.start(); } }

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

الآن ألق نظرة على فئة App الرئيسية. ينفذ المستمع ويجمع العنصر مع التنفيذ الملموس. الآن يمكننا البدء في استخدامه.

يمكنك أيضًا تشغيل هذا المثال في JavaScript هنا

هندسة العنصر

دعونا نلقي نظرة على استخدام نمط العنصر في التطبيقات واسعة النطاق. إظهاره في مشروع صغير شيء - تطبيقه على العالم الحقيقي شيء آخر.

يبدو هيكل تطبيق الويب الكامل المكدس الذي أحب استخدامه على النحو التالي:

 src ├── client │ ├── app │ └── elements │ └── server ├── app └── elements

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

ثم نقوم بتقسيم الكود في كل طبقة إلى مجلدات تسمى التطبيقات والعناصر. تتكون العناصر من مجلدات بمكونات مستقلة ، بينما يقوم مجلد التطبيق بتوصيل جميع العناصر معًا ويخزن كل منطق الأعمال.

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

مثال عملي

اعتقادًا بأن الممارسة تتفوق دائمًا على النظرية ، فلنلقِ نظرة على مثال واقعي تم إنشاؤه في Node.js و TypeScript.

مثال من الحياة الواقعية

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

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

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

تطوير أسرع ، وإعادة الاستخدام أكثر في كثير من الأحيان!

آمل أنه باستخدام هذه المجموعة الجديدة من الأدوات ، ستكون قادرًا على تطوير رمز أكثر قابلية للصيانة بسهولة أكبر. قبل الانتقال إلى استخدام نمط العنصر عمليًا ، دعنا نلخص جميع النقاط الرئيسية بسرعة:

  • تحدث الكثير من المشكلات في البرامج بسبب التبعيات بين المكونات المتعددة.

  • من خلال إجراء تغيير في مكان واحد ، يمكنك إدخال سلوك غير متوقع في مكان آخر.

ثلاثة مناهج معمارية شائعة هي:

  • كرة الطين الكبيرة. إنه رائع للتطور السريع ، ولكنه ليس رائعًا لأغراض الإنتاج المستقر.

  • حقن التبعية. إنه حل نصف مخبوز يجب تجنبه.

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

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

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

يمكنك الآن البدء في إنشاء العناصر الخاصة بك ومشاركتها. على المدى الطويل ، ستساعدك على إنشاء منتجات يمكن صيانتها بسهولة. حظا سعيدا واسمحوا لي أن أعرف ماذا صنعت!

أيضًا ، إذا وجدت نفسك تقوم بتحسين الكود الخاص بك قبل الأوان ، فاقرأ كيف تتجنب لعنة التحسين المبكر من قبل زميلك Toptaler كيفن بلوخ.

الموضوعات ذات الصلة: JS Best Practices: إنشاء روبوت للخلاف باستخدام TypeScript وحقن التبعية