اذهب إلى المحتوى

Wael Amin

الأعضاء
  • المساهمات

    2
  • تاريخ الانضمام

  • تاريخ آخر زيارة

آخر الزوار

لوحة آخر الزوار معطلة ولن تظهر للأعضاء

إنجازات Wael Amin

عضو مبتدئ

عضو مبتدئ (1/3)

7

السمعة بالموقع

  1. تعد ريآكت React أحد أشهر مكتبات لغة جافا سكريبت المستخدمة في تطوير الواجهات الأمامية front-end development، إلا أن تطوير التطبيقات بهذه المكتبة يمكن أن يتسبب لك ببعض المشكلات مع محركات البحث. نستعرض في هذه المقالة أهم التحديات التي تواجه المطورين في تحسين محركات البحث SEO للتطبيقات المصممة باستخدام مكتبة ريآكت React، ونوضح أهم الخطوات التي يمكنك اتباعها لتجاوز هذه المشكلات بفعالية، وتحسين سيو التطبيقات، ورفع رتبتها في صفحة نتائج محرك البحث. لمحة عن ريآكت React وآلية عملها ريآكت React هي مكتبة جافا سكريبت مخصصة لإنشاء واجهات مستخدم تفاعلية متعددة المنصات وهي تعتمد على تقسيم التطبيق لعدة مكونات أو أجزاء صغيرة قابلة لإعادة الاستخدام لتساعد في إنشاء تطبيقات واجهات أمامية فعالة وعالية الأداء. طُورت مكتبة ريآكت في الأساس من أجل إنشاء تطبيقات الصفحة الواحدة Single Page Applications أو اختصارًا SPAs -وهي تطبيقات مبنية بالاعتماد على صفحة واحدة ويمكنك أن تنتقل بين صفحاتها دون الحاجة إلى تحميل الصفحات الجديدة بالكامل- ولكنها اليوم تصلح لإنشاء مختلف أنواع مواقع الويب وتطبيقات الجوال. إلا أن المواقع والتطبيقات المصممة بهذه المكتبة قد تواجه مجموعة من التحديات والمشكلات المتعلقة بترتيبها في محركات البحث، وهذا ما ستلاحظه بنفسك إذا كنت قد طورت في البداية تطبيقات ويب بالطريقة التقليدية وكتبت بنفسك كافة التعليمات الأساسية اللازمة لواجهات هذه التطبيقات دون الاستعانة بمكتبات أوأطر عمل جاهزة، ثم انتقلت بعد ذلك لاستخدام مكتبة ريآكت إذ ستلاحظ أن جزءَا كبيرًا من شيفرات HTML و CSS الخاصة بتطبيقك قد أصبحت جزءًا من شيفرات جافا سكريبت JavaScript. السبب في ذلك هو أن مكتبة ريآكت React لا توصي بإنشاء عناصر واجهة المستخدم أو تحديثها بشكل مباشر، بل تعتمد طريقة أخرى بدلاً وهي وصف (حالة state) عناصر واجهة المستخدم أي تصف كيف يجب أن تظهر واجهة المستخدم في أي وقت، ثم تُحدِّث بعد ذلك نموذج تمثيل المستند ككائن DOM ليتوافق مع هذه الحالة بطريقة فعالة، كما توفر ريآكت مكونات قابلة لإعادة الاستخدام يمكننا استخدامها لوصف حالة واجهة المستخدم. ونتيجة لذلك، سيتوجب عليك إجراء جميع التغييرات على واجهة المستخدم أو على مستند DOM من خلال محرك ريآكت، وبالرغم من أن هذا الأمر مريح للمطورين، إلا أنه من ناحية أخرى يجعل وقت تحميل الصفحات أطول بالنسبة للمستخدمين، كما أنه يتطلب المزيد من العمل في محركات البحث للعثور على المحتوى وفهرسته، الأمر الذي ينجم عنه مشكلات محتملة في ترتيب الصفحات المبنية باستخدام React في نتائج محركات البحث. سنشرح في الفقرات التالية من المقالة المزيد حول المشكلات التي قد تواجهها في سيو التطبيقات والمواقع التي أنشأتها باستخدام رياكت، ونوفر لك عدة حلول واستراتيجيات للتعامل معها والتخلص منها. طريقة زحف محرك البحث جوجل إلى صفحات الويب وفهرستها قبل الغوص في حلول تحسين سيو تطبيقات رياكت من الضروري أن تفهم كيف يزحف محرك البحث جوجل Google إلى تطبيقاتك ومواقعك ويفهرسها، فمحرك البحث جوجل هو أشهر محرك بحث ويتلقى ما يزيد على 90% من مجمل عمليات البحث عبر الإنترنت ويمكنك من خلال فهم طريقة عمله معرفة العوامل التي تؤثر على فهرسة موقعك. للنظر للصورة التالية المقتبسة من وثائق Google وتجدر الملاحظة بأن هذا المخطط ليس سوى رسم توضيحي مبسط لعملية فهرسة موقع الويب، فالفهرسة الفعلية التي يقوم بها زاحف جوجل Googlebot أعقد بكثير. خطوات فهرسة جوجل يتبع زاحف جوجل Googlebot عملية من عدة خطوات لفهرسة مواقع الويب وهي كالتالي: ينشئ زاحف جوجل رتل انتظار للزحف crawl queue يتضمن كافة عناوين URL التي اكتشفها والتي يحتاج للزحف إليها وفهرستها لاحقًا. عندما يكون الزاحف جاهزًا، فإنه يأخذ عنوان URL التالي في رتل الانتظار، ويرسل طلبًا لجلب محتوى HTML للصفحة. بعدها يقوم الزاحف Googlebot بتحليل محتوى HTML وتحديد فيما إذا كان يحتاج كذلك إلى جلب سكربتات جافا سكريبت وتنفيذها لعرض المحتوى. إذا كانت الإجابة نعم سيضاف عنوان URL إلى رتل انتظار التصيير render queue. لاحقًا، يقوم المصيّر renderer الخاص بالزاحف بجلب شيفرات جافا سكريبت وتنفيذها من أجل تصيير أو عرض render الصفحة ويرسل كود HTML الناتج مرة أخرى إلى وحدة لمعالجة الكود processing unit. تستخرج وحدة المعالجة جميع وسوم الروابط <a> لعناوين URL المكتشفة في صفحة الويب وتضيفها من جديد إلى رتل انتظار الزحف crawl queue. أخيرًا يضاف المحتوى إلى فهرس جوجل. لاحظ أن هناك اختلافات واضحة بين مرحلة المعالجة التي تحلل كود HTML ومرحلة التصيير التي تنفذ أكواد جافا سكريبت لاحقًا، والسبب في ذلك هو أن تنفيذ أكواد جافا سكريبت مكلف ويستغرق وقتًا طويلاً جدًا، ويحتاج زاحف جوجل للاطلاع على ما يزيد على 130 تريليون صفحة على الويب، لذا عندما يزحف على صفحة ويب فإنه يحلل HTML فورًا ثم يضع أكواد جافا سكريبت في رتل انتظار لتشغيلها لاحقًا، وبحسب وثائق جوجل قد تبقى أكواد جافا سكريبت في رتل انتظار التصيير render queue لمدة بضع ثوانٍ أو أكثر. ومن الجدير بالذكر أيضًا الإشارة إلى ميزانية الزحف، فعملية زحف وفهرسة جوجل مقيدة بالموارد المتاحة مثل سعة النطاق الترددي والوقت وعدد روبوتات جوجل المتاحة. وتخصص جوجل ميزانية أو موارد محددة لفهرسة كل موقع إلكتروني على شبكة الإنترنت. فإذا كنت تنشئ موقع ويب ضخم يضم آلاف الصفحات والكثير من المحتوى كموقع تجارة إلكترونية على سبيل المثال، وتستخدم في صفحات موقعك قدرًا كبيرًا من أكواد جافا سكريبت لعرض المحتوى، فقد لا يتمكن جوجل من الزحف لكافة صفحات موقعك وقراءة كافة محتوياته لأنه مكلف في الزحف. ملاحظة: ينصح بقراءة الإرشادات التي يعتمدها جوجل من أجل إدارة وتحسين ميزانية الزحف الخاصة بك من هنا. لماذا يمثل السيو تحديًا في ريآكت React ؟ بعد أن كونت فكرة عامة عن طريقة عمل زاحف جوجل Googlebot وفهرسته للموقع ستتمكن كمهندس برمجيات من تحديد المشكلات المحتملة التي تواجهها محركات البحث التي تحاول الزحف إلى صفحات مواقع أو تطبيقات ريآكت وفهرستها بشكل أفضل. وفيما يلي نبين أبرز أسباب حدوث مشكلات في تحسين محركات البحث لمواقع وتطبيقات React ونوضح سبل حلها والتغلب عليها. السبب الأول: محتوى الصفحة فارغ عند المرور عليها لأول مرة تعتمد تطبيقات ريآكت بشكل أساسي على أكواد جافا سكريبت لعرض المحتوى الديناميكي للصفحات، لذا تواجه مشكلات مع محركات البحث في معظم الحالات، حيث تعتمد ريآكت بشكل افتراضي على بنية أو نموذج (هيكل التطبيق App Shell) وهو نموذج لتصميم لتطبيقات الويب يعتمد على إنشاء ملف HTML أساسي عند تحميل الصفحة، ولكن لا يحتوي هذا الملف على أي محتوى ذي معنى، ويجب على المستخدم أو روبوت الزحف تنفيذ أكواد جافا سكريبت لإنشاء ورؤية المحتوى الفعلي للصفحة. وهذا بدوره يعني أن زاحف جوجل Googlebot سيعثر على صفحة فارغة عند مروره الأول على الصفحة ولن يرى المحتوى الفعلي إلا في مرحلة تصيير أو عرض الصفحة، ونظرًا لكون الزاحف يتعامل مع آلاف الصفحات، سيتسبب هذا الأمر في تأخير فهرسة المحتوى. السبب الثاني: زيادة وقت التحميل التي تؤثر على تجربة المستخدم UX قد يستغرق جلب أكواد جافا سكريبت وتحليلها وتنفيذها وقتًا طويلًا، أضف إلى ذلك فقد تحتاج أكواد جافا سكريبت إلى الاتصال مع شبكة لطلب المحتوى من مصادر مختلفة (مثل قواعد البيانات أو الواجهات البرمجية APIs) وفي هذه الحالة سيضطر المستخدم إلى الانتظار لفترة من الزمن قبل أن يتمكن من الحصول على نتيجة الطلب وعرض المعلومات المطلوبة. وبما أن جوجل تعتبر أن مواقع الويب التي تستغرق وقتًا طويلاً للتحميل تؤثر سلبًا على تجربة المستخدم وتعتمد هذا الأمر كمعيار لتحديد ترتيب المواقع في محركات البحث فالمواقع الأبطأ في التحميل ستحصل على ترتيب أقل في صفحة نتائج البحث. السبب الثالث: صعوبة تخصيص البيانات الوصفية metadata لكل صفحة من صفحات الموقع تفيد الوسوم الوصفية أو ما يعرف بوسوم ميتا في وصف صفحات الموقع لمحركات البحث، وتسمح لمحرك بحث جوجل ولمواقع التواصل الاجتماعي بإظهار عناوين وأوصاف هذه الصفحات وصورها المصغرة، لكن المواقع المطورة باستخدام ريآكت تستخدم الوسم في صفحات الويب المستردة للحصول على هذه المعلومات، وهي لا تنفذ شيفرات جافا سكريبت للصفحة الهدف. كما تعتمد رياكت على منهجية تصيير كل المحتوى بما في ذلك وسوم ميتا من جانب العميل، وبالتالي سيكون هيكل التطبيق App shell هو نفسه لكامل الموقع أو التطبيق، بمعنى آخر ستكون البيانات الوصفية metadata موحدة لكافة صفحات موقع الويب أو التطبيق وسيكون من الصعب تخصيص هذه البيانات لكل صفحة على حدة، وبهذا سترى محركات البحث نفس البيانات الوصفية لجميع الصفحات، حتى لو كانت هذه الصفحات مختلفة تمامًا عن بعضها البعض وهذا قد يؤثر على فهرستها بصورة صحيحة. السبب الرابع: عدم وجود طريقة مدمجة لإنشاء خريطة الموقع Sitemap خريطة الموقع Sitemap هي ملف يوفر معلومات حول محتوى صفحات الموقع ومقاطع الفيديو التي تتضمنها والملفات الأخرى الموجودة على الموقع، ويوضح العلاقات أو الروابط فيما بينها، وتقرأ محركات البحث -ومن بينها محرك جوجل- هذا الملف لتتمكن من الزحف إلى موقعك بطريقة أفضل. ولا توفر مكتبة ريآكت طريقة مدمجة لإنشاء خريطة الموقع، لكن في حال كنت تستخدم مكتبة React Router للتعامل مع توجيه الصفحات Routing فيمكنك في هذه الحالة العثور على أدوات مساعدة لإنشاء خريطة الموقع، لكن هذا قد يحتاج منك لبعض الجهد. نصائح عامة لتحسين محركات البحث غير مرتبطة بمكتبة ريآكت إليك أهم النصائح والممارسات التي يمكنك الأخذ بها لتحسين محركات البحث للمواقع والتطبيقات بشكل عام وليست خاصة بتطبيقات ريآكت فقط. احصل على عنوان URL سهل لموقعك أو تطبيقك ويمكن فهمه من قبل البشر ومحركات البحث ويعطي فكرة واضحة عن المحتوى الذي يتضمنه. حَسّن ملف robots.txt لمساعدة روبوتات البحث على فهم كيفية الزحف إلى صفحات موقعك. استخدم شبكة توصيل محتوى CDN لتخزين جميع الأصول الثابتة مثل CSS و JS والخطوط، واستخدم صورًا متجاوبة لتقليل أوقات تحميل الصفحات. كما يمكن معالجة العديد مشكلات السيو باستخدام التصيير من جانب الخادم SSR أو التصيير المسبق pre-rendering وسنستعرض هذه الأساليب بمزيد من التفصيل في فقرات لاحقة. ما هي تطبيقات ريآكت المتماثلة Isomorphic يشير مصطلح إسومورفيك Isomorphic بشكل عام إلى التوافق أو التشابه في الشكل، أما في مكتبة ريآكت React فهو يعني هذا أن الخادم والعميل لهما له نفس نموذج أو قاعدة الشيفرة تقريبًا، بمعنى آخر يمكن إعادة استخدام نفس مكونات ريآكت React على كل من الخادم والعميل. وبالتالي يعرف تطبيق رياكت المتماثل Isomorphic بأنه تطبيق يمكن تشغيله على كل من جانب العميل وجانب الخادم باستخدام نفس الشيفرة، إذ يمكن استخدام نفس المكونات على كل منهما ونفس المنطق في التصيير على الخادم والتصيير على العميل وهذا يقلل هذا من تكرار الشيفرة ويضمن تناسق التطبيق. يسمح هذا النهج المتماثل للخادم يتصيير render تطبيق رياكت وإرسال النتيجة إلى المستخدمين ومحركات البحث ليتمكنوا من عرض المحتوى على الفور، بينما تُحمّل أكواد وتنفذ جافا سكريبت في الخلفية وقد اعتمدت عدة أطر على هذا الأسلوب وزادت من شهرته مثل إطار Next.js ومولد المواقع Gatsby . تجدر الملاحظة بأن المكونات المتماثلة قد تبدو مختلفة عن مكونات React التقليدية بشكل واضح. على سبيل المثال، يمكن أن تحتوي على كود يعمل على الخادم بدلاً من العميل، كما يمكن أن تشمل حتى مفاتيح سرية وبيانات وصول لواجهات برمجة التطبيقات API رغم أن كود الخادم يحذف قبل إرساله للعميل. تسهل هذه الأطر من الكثير من التعقيدات على المطورين وتقدم طريقة محددة لكتابة التعليمات البرمجية. وسنتعمق في تأثيرها على أداء التطبيقات ونوضح العلاقة بين مسارات التصيير render paths وأداء الموقع لاحقًا، لكن لنناقش قبل ذلك بعض المعايير الأساسية لقياس أداء موقع الويب. مقاييس أداء الموقع سنوضح الآن بعض المعايير التي تستخدمها محركات البحث لتصنيف وترتيب مواقع الويب لنساعدك على فهم المزيد حول رفع رتبة موقعك في نتائج البحث. إلى جانب الإجابة على استفسار المستخدم بسرعة ودقة، تعتقد جوجل أن موقع الويب الجيد يجب أن يحقق ما يلي: يتمكن المستخدمون من الوصول إلى محتوى الموقع بسرعة ودون الانتظار لوقت طويل. يتفاعل الموقع مع إجراءات المستخدم خلال وقت قصير. يُحمّل الموقع بسرعة. لا يجلب الموقع أي بيانات غير ضرورية، ولا ينفذ تعليمات برمجية مستهلكة للموارد تستنزف بيانات المستخدم أو موارد جهازه. تتوافق هذه الميزات تقريبًا مع المقاييس التالية: زمن وصول أول بايت (TTFB): هو اختصار لعبارة Time to First Byte ويعني مقدار الوقت الذي يستغرقه وصول أول جزء من المحتوى من الخادم إلى المتصفح، أو الوقت بين النقر على الرابط ووصول الجزء الأول من المحتوى للمستخدم. أضخم محتوى مرئي (LCP): وهو اختصار لعبارة Largest Contentful Paint ويمثل الزمن الذي يحتاجه عرض العنصر الذي يتضمن أضخم جزء مرئي من الصفحة وهو يعطي فكرة عن زمن انتظار المستخدم لتحميل ما يكفي من الصفحة لتصبح ذات معنى، وتوصي جوجل بأن لا تتجاوز هذه القيمة 2.5 ثانية. زمن التفاعل (TTI): وهو اختصار لعبارة Time to Interactive ويعني الوقت الذي تصبح فيه الصفحة تفاعلية أي يمكن للمستخدم النقر فوق عناصر أو تمريرها…إلخ. حجم الحزمة (Bundle Size): ويمثل إجمالي عدد البايتات المحملة والتعليمات البرمجية المنفذة قبل أن تصبح الصفحة مرئية وتفاعلية بالكامل. سنعود للحديث عن هذه المقاييس لاحقًا لفهم كيفية تأثير مسارات التصيير render paths المختلفة على كل منها بشكل أفضل. وسنوضح تاليًا ما هي مسارات التصيير المتاحة لمطوري رياكت. مسارات التصيير Render Paths نعني بمسارات التصيير الطرق المختلفة التي يمكن من خلالها الوصول إلى محتوى الصفحة حيث يمكننا تصيير أو عرض تطبيق React على جانب المتصفح client-side أو على جانب الخادم server-side وإنتاج مخرجات مختلفة. وهنالك وظيفتان تتفاوتان بشكل كبير بين التطبيقات المصيًّرة من جانب العميل والخادم وهما عملية التوجيه routing، وعملية تقسيم التعليمات البرمجية code splitting، لنتعرف على آلية القيام بكل منهما في كل حالة: حالة التصيير من جانب العميل (CSR) التصيير من جانب العميل هو مسار التصيير الافتراضي في تطبيقات رياكت ذات الصفحة الواحدة React SPA. حيث أن الخادم يوفر غلاف تطبيق shell app لا يتضمن أي محتوى فعلي وبمجرد قيام المتصفح بتحميل أكواد جافا سكريبت JavaScript المضمنة في الصفحة ويحللها وينفذها سيملأ محتوى HTML ويعرض بالكامل. في هذه الحالة يتم التعامل مع وظيفة التوجيه بواسطة تطبيق العميل client app من خلال إدارة سجل المتصفح browser history. وهذا يعني أنه يتم تصيير أو عرض نفس ملف HTML بغض النظر عن المسار الذي طُلِب، ويقوم العميل بتحديث حالة العرض view state الخاصة به بعد عرضه. أما تقسيم الكود في هذه الحالة فهو أمر بسيط نسبيًا حيث يمكن تقسيم التعليمات البرمجية الخاصة بالتطبيق باستخدام الاستيراد الديناميكي dynamic imports أو بالاستعانة بمكتبة React.lazy إذ يقسم الكود بحيث يتم تحميل التبعيات المطلوبة فقط بناءً على المسار أو إجراءات المستخدم. إذا كانت الصفحة بحاجة إلى جلب بيانات معينة من الخادم لتصيير المحتوى مثل عنوان المدونة أو وصف منتج ما فلن تتمكن من القيام بذلك إلا عندما يتم تثبيت المكونات ذات الصلة وعرضها، وسيرى المستخدم غالبًا عبارة مثل "جاري تحميل البيانات" أثناء قيام موقع الويب بجلب واستيراد الموقع لبيانات إضافية. إذَا في هذه الحالة لن تسترد البيانات من الخادم إلا عند الحاجة إليها لعرض المحتوى. وهذا يقلل وقت التحميل الأولي للصفحة ويوفر تجربة مستخدم أفضل. ومع ذلك، قد يرى المستخدم مؤشر تحميل أثناء استرداد البيانات الإضافية. التصيير من جانب العميل باستخدام البيانات التمهيدية (CSRB) لنفترض أن لدينا حالة تصيير من جانب العميل CSR ولكن بدلاً من جلب البيانات بعد تصيير وعرض مستند DOM بحيث تظهر المحتويات أو البيانات ذات الصلة بشكل تدريجي، سنفترض أن الخادم يرسل البيانات ذات الصلة مباشرة داخل كود HTML المُصَيَّر. يمكن أن نضيف عقدة node كما يلي داخل صفحة HTML: <script id="data" type="application/json"> {"title": "My blog title", "comments":["comment 1","comment 2"]} </script> يضمن هذا الكود البيانات بصيغة JSON داخل صفحة HTML، وتحلل هذه القاعدة التي تحتوي على البيانات المطلوبة (وهي عنوان المدونة والتعليقات عليها) وتفسر عند إضافة المكون إلى مستند DOM على النحو التالي: var data = JSON.parse(document.getElementById('data').innerHTML); بهذا نكون قد وفرنا عملية ذهاب وعودة من الخادم، وسيظهر المحتوى بشكل أسرع للمستخدم لأن المتصفح لم يضطر إلى الانتظار لطلب البيانات من الخادم مرة أخرى. تصيير المحتوى الثابت من جانب الخادم (SSRS) في هذه الحالة يتم إنشاء وتصيير الصفحة بالكامل على جانب الخادم قبل إرساله إلى المتصفح، تخيل على سبيل المثال حالة لموقع أو تطبيق ويب تحتاج فيه إلى إنشاء كود HTML بسرعة، على سبيل المثال إذا كنت تريد بناء تطبيق ريآكت لآلة حاسبة عبر الإنترنت، وأدخل المستخدم لهذا التطبيق استعلام من المستخدم من خلال عنوان URL على النحو التالي: /calculate/34+15 يحتاج التطبيق لمعالجة هذا الاستعلام وتقييم النتيجة والرد بكود HTML الذي تم إنشاؤها، وبما أن كود HTML النتاتج يملك بنية بسيط جدًا فلست بحاجة لأن تقوم رياكت بإدارة DOM ومعالجته ويمكنك الاكتفاء بعرض HTML الناتج مباشرة. لذلك يمكن توفير محتوى HTML و CSS فقط واستخدام التابع renderToStaticMarkup لتحقيق ذلك. سيتعامل التطبيق في هذه الحالة مع التوجيه بالكامل من جانب الخادم لأنه يحتاج إلى إعادة حساب HTML لكل نتيجة (كلما تم تغيير عنوان URL)، كما يمكن استخدام التخزين المؤقت لكود HTML الناتج ضمن شبكة توصيل المحتوى CDN لتسريع استجابة التطبيق. ويمكن أيضًا تخزين ملفات CSS مؤقتًا بواسطة المتصفح لتحميل الصفحات اللاحقة بسرعة أكبر. التصيير من جانب الخادم مع المكونات المعززة (SSRH) المكونات المشبعة أو المعززة Hydrated Components هي مكونات رياكت تعمل كجسر بين نسخ التطبيق التي تنشأ على الخادم والعميل، ولفهم آلية عملها تخيل الآن أنك تحتاج لتطوير تطبيق رياكت كامل الوظائف على العميل، أي تطبيق يتطلب تفاعل المستخدمين ويوفر إمكانية تنقلهم بين الصفحات ويحتاج لجلب البيانات من الخادم. ملاحظة:يشير مصطلح التشبيع أو التعزيز hydration في رياكت إلى الطريقة التي تتصل بها React مع كود HTML الموجود مسبقًا والذي تم تصييره بالفعل بواسطة رياكت في جانب الخادم. فهو عملية يتم فيها ربط أو تعزيز شيفرة جافا سكريبت التفاعلية لمحتوى HTML الثابت المصير مسبقًا على الخادم لجعل كود HTML تفاعليًا. وبالتالي تقوم رياكت بتنفيذ التصيير الأول لصفحات الموقع على الخادم، وترسل محتوى HTML الناتج إضافة إلى ملفات جافا سكريبت إلى العميل، ثم تقوم بإعادة تعزيز الكود المرسل من جانب الخادم، ليصبح التطبيق وكأنه تطبيق تصيير من جانب العميل (CSR) من هذه النقطة فصاعدًا. توفر رياكت عدة طرق مدمجة لتنفيذ هذه الإجراءات تسمى ReactDOMServer، حيث تعالج رياكت أول طلب لعرض أو تصيير صفحة ما من جانب الخادم، ثم تتعامل مع الطلبات التالية لعرض هذه الصفحة من جانب العميل. ولهذا يطلق على هذه التطبيقات اسم تطبيقات رياكت العامة لكون عملية العرض أو التصيير تتم على جانبي الخادم والعميل كليهما، وقد يتم تقسيم (أو تكرار) الكود الذي يتعامل مع التوجيه على كل من الخادم والعميل. إن تقسيم الكود أمر صعب بعض الشيء أيضًا، لأن توابع ReactDOMServer المستخدمة لإنشاء صفحات HTML ثابتة على الخادم لا تدعم React.lazy، لذلك لا يمكن استخدامها لتحميل المكونات ديناميكيًا على الخادم وللتعامل مع هذا الأمر قد تضطر لاستخدام المكونات القابلة للتحميل وهي مكونات يمكنك تحميلها ديناميكيًا عند الحاجة. وقد تضطر إلى استخدام شيء مثل Loadable Components. يجب التنويه أيضًا أن ReactDOMServer تقوم فقط بعرض بسيط. وبالرغم من استدعاء طريقة التصيير لمكوناتك، إلا أن الطريقة componentDidMount التي ستحتاج لاستدعائها بعد أن يتم تثبيت المكون في DOM لا تستدعى في هذه الحالة. لذلك تحتاج إلى إعادة تصميم كودك لتوفر البيانات لمكونات التطبيق باستخدام طريقة بديلة. هنا تبرز فائدة إطارات العمل مثل NextJS. فهي تختفي التعقيدات المرتبطة بعمليات التوجيه وتقسيم الكود في حالة تصيير الصفحات على الخادم وتعتمد أسلوب التصيير من جانب الخادم مع المكونات المعززة SSRH لتوفر تجربة أكثر سلاسة للمطورين. التصيير المسبق للمحتوى الثابت (PRS) ماذا لو تمكنا من عرض صفحات الموقع قبل أن يطلبها المستخدم؟ يسمى هذا الأمر التصيير المسبق Pre-rendering وهو أسلوب لإنشاء محتوى HTML للتطبيق بالكامل على الخادم قبل أن يطلبه المستخدم، بعدها يرسل المحتوى للمتصفح ويعرض مباشرة دون الحاجة إلى إنشاء واجهة المستخدم بالكامل من البداية، ويمكن القيام بذلك في وقت إنشاء الموقع أو ديناميكيًا عندما تتغير البيانات. ويمكن فيما بعد تخزين محتوى HTML الناتج على شبكة توصيل المحتوى CDN وتصييره بشكل أسرع عندما يطلبه المستخدم وهو أمر مفيد في كافة المواقع التي لا يعتمد محتواها على البيانات المقدمة من قبل المستخدم كالمدونات وتطبيقات التجارة الإلكترونية. التصيير المسبق مع تفعيل المكونات (PRH) قد ترغب في أن يكون HTML المصير مسبقًا تطبيق React يعمل بكامل طاقته عندما يعرضه العميل. في هذه الطريقة، ينشأ محتوى HTML للتطبيق بالكامل على الخادم كما هو الحال في التصيير المسبق العادي، وإلى جانب ذلك تضمن كذلك معلومات الحالة المقابلة لكل عنصر رياكت في محتوى HTML. وبالتالي في هذه الطريقة، بعد تقديم الطلب الأول لعرض التطبيق، سيتصرف التطبيق مثل تطبيق React القياسي. وبشكل مشابه لحالة تصيير الصفحات على الخادم SSRH من حيث التعامل مع وظائف التوجيه وتقسيم التعليمات البرمجية. مصفوفة الأداء لنناقش الآن كيفية تأثير كل مسار من مسارات التصيير التي وضحناها سابقًا على مقاييس أداء مواقع الويب ونعرف أي طريقة منها هي الأفضل من خلال الجدول التالي الذي يعطي نتيجة تتراوح من 1 إلى 5 وتدل النتائج على ما يلي: نتيجة غير مرضية نتيجة ضعيفة 3.نتيجة متوسطة نتيجة جيدة نتيجة ممتازة TTFB LCP TTI حجم الحزمة المجموع CSR 5 يمكن تخزين HTML مؤقتاً على شبكة CDN 1 يحتاج للذهاب مرات متعددة للخادم لجلب البيانات 2 تأخر في جلب البيانات وفي تنفيذ أكواد جافا سكريبت 2 تحميل جميع تبعيات الجافا سكريبت قبل التصيير 10 CSRB 4 يمكن تخزين HTML مؤقتًا بشرط عدم اعتماده على بيانات الطلب 3 تحميل البيانات مع التطبيق 3 يجب جلب جافا سكريبت وتحليلها وتنفيذها قبل التفاعل 2 تحميل جميع تبعيات الجافا سكريبت قبل التصيير 12 SSRS 3 يُولَّد كود HTML في كل طلب ولا يمكن تخزينه مؤقتًا 5 عدم تحميل جافا سكريبت أو عمليات غير متزامنة 5 الصفحة تكون تفاعلية فورًا بعد الرسم الأول 5 يحتوي على المحتوى الثابت الأساسي فقط\ 18 SSRH 3 يُولَّد HTML في كل طلب ولا يمكن تخزينه مؤقتًا 4 الرسم الأول أسرع بسبب التصيير الأول على جانب الخادم 2 أبطأ بسبب تنشيط DOM بعد تحليل ورسم HTML أول مرة 1 HTML الناتج+ تحميل تبعيات الجافا سكريبت 10 PRS 5 تخزين HTML مؤقتًا على شبكة CDN 5 عدم تحميل جافا سكريبت أو عمليات غير متزامنة 5 تكون الصفحة تكون تفاعلية فورًا بعد الرسم الأول 5 يحتوي على المحتوى الثابت الأساسي فقط 20 PRH 5 تخزين HTML مؤقتًا على شبكة CDN 4 التصيير الأول أسرع بسبب التصيير من جانب الخادم لأول مرة 2 أبطأ بسبب تفعيل DOM بعد تحليل ورسم HTML الأول 1 HTML المصير+ تحميل تبعيات الجافا سكريبت 12 يمكن أن نستنتج من الجدول السابق ما يلي: يؤدي التصيير المسبق للمحتوى الثابت (PRS) إلى أفضل أداء لمواقع الويب، بينما قد يؤدي التصيير من جانب الخادم مع المكونات المعززة (SSRH) أو التصيير من جانب العميل (CSR) إلى نتائج غير مرضية. من الممكن أيضًا استخدام أساليب متعددة لأجزاء مختلفة من موقع الويب. على سبيل المثال، قد تكون مقاييس الأداء هذه مهمة لصفحات الويب العامة حتى يمكن فهرستها بشكل أكثر فعالية، بينما قد تكون أقل أهمية بمجرد دخول المستخدم ورؤية بيانات حسابه الخاصة. يعتمد اختيار كل مسار على طبعة تطبيقك وكيفية معالجة بياناتك، اختر ما يناسبك بحيث تحسن تجربة المستخدمين بأفضل صورة ممكنة. طرق إضافية لتحسين سيو مواقع الويب حاولنا في هذه المقالة أن نناقش الأساليب والتقنيات الشائعة حاليًا بتحسين أداء تطبيقات رياكت وتعزيزها في محركات البحث، ويمكن إضافة إلى ما سبق الاطلاع على النصائح والاستراتيجيات التي يوفرها مطورو جوجل. وأخيرًا من العوامل الإضافية التي تساعدك على تحسين سيو تطبيقات ومواقع الويب المطورة بمكتبة رياكت اعتماد نظام إدارة محتوى موقعك CMS جيد لاسيما عند إنشاء مواقع ويب ذات محتوى ضخم، وتوفير ميزة إنشاء وتعديل معاينات وسائل التواصل الاجتماعي بسهولة وتحسين الصور لتتناسب مع مختلف أحجام الشاشات فهذه العوامل من شأنها تحسين سرعة تحميل الصفحة وتحسين تجربة المستخدم بشكل كبير. الخاتمة هذا تكون قد وصلت لنهاية هذا المقال الذي وضحنا فيه أبرز تحديات تحسين محركات البحث في تطبيقات ومواقع رياكت، ووضحنا أهم الاستراتيجيات التقنيات المستخدمة التي يمكنك القيام بها للتغلب عليها لتحسين سيو تطبيقات ومواقع رياكت React الخاصة بك وتحسين فرصك في ظهورها في نتائج البحث. اقرأ أيضًا مقارنة شاملة بين رياكت React و Next.js المصطلحات المستخدمة في React شمولية تطبيقات React لكافة أنواع المستخدمين تقسيم تطبيق React إلى مكونات
  2. لا شك أن اختيار لغة البرمجة المناسبة لتطبيقاتك وبرامجك أمرٌ صعب، لاسيما عندما لا تملك خبرة عميقة بالخيارات المتاحة، وفي مقال اليوم نعقد لك مقارنة شاملة بين لغتي C++‎‎ وجافا ونستكشف أبرز الاختلافات الأساسية بينهما، وما هي أبرز العوامل التي عليك أخذها بعين الاعتبار عند اختيارك لإحديهما. هناك الكثير من المقالات التي تقارن بين المميزات التقنية للغتي C++‎‎ وجافا، ولكنها لا توضح لك بشكل دقيق ما هي الجوانب التي ستؤثر بها هذه الاختلافات، على سبيل المثال، لا تدعم لغة جافا الوراثة المتعددة بينما تدعمها لغة C++‎، حسنًا ماذا يعني ذلك لي كمطور؟ وهل هذا أمر جيد أم لا؟ لماذا يعتبر البعض بأن هذه ميزة في جافا، بينما يعتبرها آخرون مشكلة. دعنا نستكشف كافة الحالات التي يجب على المطورين فيها اختيار لغة C++‎‎ أو جافا أو ربما لغة أخرى في تطوير التطبيقات، ولماذا يعد اختيار اللغة الصحيحة أمرًا مهمًا لنجاح مشروعك البرمجي. الفروقات الأساسية بين C++‎‎ وجافا: بناء اللغة والنظام البيئي أُُطلِقت لغة C++‎‎ عام 1985 كواجهة أمامية للغة سي C فهي تستخدم الكثير من مفاهيم وبنية لغة C، ولكنها أيضًا تضيف ميزات جديدة وتحسينات عليها، وهذا يشبه إلى حد ما طريقة ترجمة لغة TypeScript إلى جافا سكريبت JavaScript . عادةً ما تقوم مترجمات C++‎‎ الحديثة بترجمة أكواد C++‎‎ إلى لغة الآلة الأصلية، وعلى الرغم من أن البعض يرى أن مترجمات C++‎‎ تقلل من قابلية التنقل بين المنصات، وتتطلب إعادة بناء البرامج للتكيف مع المعماريات الحديثة للحواسيب، إلا أن كود C++‎‎ يعمل على جميع منصات المعالجات تقريبًا. أما لغة جافا فقد صدرت لأول مرة في عام 1995، وهي تتميز بأنها لا تترجم مباشرةً إلى لغة الآلة الأصلية Native Machine Code، بل تترجم بدلاً من ذلك إلى لغة وسيطة تسمى شيفرة البايت bytecode، وهي لغة ذات تمثيل ثنائي مخصصة للعمل على آلة جافا الافتراضية JVM أي بعبارة أخرى، يحتاج مترجم جافا الذي يترجم كود لغة جافا إلى كود يفهمه الحاسوب إلى ملف تشغيلي أصيل خاص بكل منصة أو نظام التشغيل كي يعمل بشكل صحيح. لذا، تختلف جافا عن بعض لغات البرمجة الأخرى من حيث أن كودها المصدري لا يتحول مباشرة إلى برنامج قابل للتشغيل بل يحول إلى كود البايت أولًا والذي يحتاج تنفيذه لبرنامج آخر يسمى آلة جافا الافتراضية. وتصنف كل من لغة C++‎‎ و جافا بأنها من عائلة اللغات الشبيهة بلغة سي C، حيث أنهما تشبهان لغة C بشكل عام في تراكيبها اللغوية وبنية التعليمات البرمجية، لكن يكمن الفرق الأبرز بينما في البيئة التشغيليلة الخاصة بكل لغة، حيث يمكن للغة C++‎‎ استدعاء المكتبات المستندة إلى C أو ++C، أو استدعاء واجهة برمجة التطبيقات API لنظام التشغيل بسهولة كبيرة، أما لغة جافا فهي ملاءمةً بشكل أفضل للمكتبات المعتمدة على لغة جافا، ويمكن الوصول إلى مكتبات C في جافا باستخدام واجهة برمجة التطبيقاتJava Native Interface أو اختصارًا JNI، لكن هذا قد يتسبب في وقوع بعض الأخطاء ويتطلب كتابة بعض أكواد C أو ++C، كما تتفاعل لغة C++‎‎ مع الأجهزة بسهولة أكبر من لغة جافا لكونها أقرب للعتاد وتعد أقرب إلى لغات البرمجة منخفضة المستوى. الفروقات التفصيلية بين لغة C++‎‎ وجافا: الأنواع المعمعة وإدارة الذاكرة وغيرها يمكننا مقارنة لغة C++‎‎ بلغة جافا من جوانب عديدة، ففي بعض الحالات يكون قرار اختيار لغة C++‎‎ أو لغة جافا واضحًا وسهلًا، على سبيل المثال في حال أراد مطور ما برمجة تطبيقات أصيلة لنظام التشغيل أندرويد ففي الغالب سيختار لغة جافا لهذه المهمة، إلا في حال كان التطبيق المطلوب عبارة عن لعبة فيديو، ففي هذه الحالة سيختار لغة C++‎‎ أو لغة أخرى من لغات برمجة الألعاب تساعده على الحصول على رسوم متحركة أكثر انسيابيةً في الزمن الحقيقي، فغالبًا ما تسبب إدارة الذاكرة في جافا في حدوث تأخير غير محبب أثناء اللعب. أما بالنسبة لتطوير التطبيقات متعددة المنصات التي ليست ألعابًا فهي خارج نطاق المناقشة اليوم، إذ لا تعد لا لغة C++‎‎ ولا لغة جافا مثاليتين في هذه الحالة لأنهما لغتان تحتاجان لكتابة تعليمات برمجية طويلة جدًا لتطوير واجهة المستخدم الرسومية، ولا تساعد المطورين على برمجتها بكفاءة، وبالنسبة إلى التطبيقات عالية الأداء من الأفضل إنشاء وحدات C++‎‎ للقيام بالأعمال الثقيلة، واستخدام لغة أكثر إنتاجية لتطوير الواجهة الأمامية لهذه التطبيقات. وبما أن اختيار اللغة الأنسب قد لا يكون واضحًا بالنسبة لبعض المشاريع، لذلك دعونا نقارن بين اللغتين بتفصيل أكبر من خلال الجدول التالي: الخاصية C++‎‎ جافا سهولة الاستخدام للمبتدئين لا نعم أداء زمن التشغيل ممتاز جيد التأخير Latency قابل للتنبؤ غير قابل للتنبؤ المؤشرات الذكية للعد المرجعي Reference-counting smart pointers نعم لا أسلوب جمع وتحرير القمامة أو البيانات المهملة في الذاكرة لا مطلوب تخصيص ذاكرة المكدس Stack memory allocation نعم لا الترجمة إلى ملف أصيل قابل للتنفيذ نعم لا الترجمة إلى شيفرة البايت bytecode جافا Compilation to Java bytecode لا التفاعل المباشر مع واجهات برمجة تطبيقات نظام التشغيل منخفضة المستوى نعم يتطلب كود C التفاعل المباشر مع مكتبات C نعم يتطلب كود C التفاعل المباشر مع مكتبات جافا من خلال JNI نعم البناء وإدارة الحزم بشكل موحد لا Maven إضافة إلى الميزات التي قارنها في الجدول أعلاه، سنركز أيضًا على ميزات أخرى مثل البرمجة كائنية التوجه OOP مثل الوراثة المتعددة multiple inheritance، والأنواع المعممة generics، والقوالب templates، والانعكاس reflection. فكلا اللغتين مثلًا تدعمان البرمجة كائنية التوجه مع فرق بسيط بينهما حيث تفرض لغة جافا استخدام نموذج البرمجة كائنية التوجه، بينما تدعم لغة C++‎‎ البرمجة كائنية التوجه إلى جانب نماذج برمجة أخرى. الوراثة المتعددة الوراثة هي أحد مبادئ البرمجة كائنية التوجه وهي تمكن صنف ابن child class من وراثة الواصفات attributes والتوابع methods من صنف أب parent class. وأحد الأمثلة القياسية على مبدأ الوراثة هو صنف المستطيل Rectangle الذي يرث من صنف الشكل Shape الأكثر عمومية: // Note that we are in a C++‎‎ file class Shape { // Position int x, y; public: // The child class must override this pure virtual function virtual void draw() = 0; }; class Rectangle: public Shape { // Width and height int w, h; public: void draw(); }; تحدث الوراثة المتعددة عندما يرث صنف ابن من عدة أصناف آباء بذات الوقت. فيما يلي مثال يستخدم صنف المستطيل Rectangle وصنف الشكل Shape وصنف إضافي Clickable: // Not recommended class Shape {...}; class Rectangle: public Shape {...}; class Clickable { int xClick, yClick; public: virtual void click() = 0; }; class ClickableRectangle: public Rectangle, public Clickable { void click(); }; لدينا في هذه الحالة صنفان أساسيان هما صنف الشكل Shape (الذي يمثل الصنف الأساسي أو الصنف الأب للمستطيل Rectangle) والصنف الأساسي Clickable والصنف ClickableRectangle الذي يرث من كل من الصنفين السابقين ويملك خصائص نوعي الكائنات التي يولدها كل صنف. تدعم لغة C++‎‎ الوراثة المتعددة، بينما لا تدعم لغة جافا هذه الميزة، وتعد ميزة الوراثة المتعددة مفيدة في بعض حالات المحددة مثل: إنشاء لغة تخصصية Domain-Specific Language أواختصارًا DSL للعمل في مجال معين ولحل مشكلة مخصصة. إجراء حسابات معقدة في وقت تصريف البرنامج كما في تطبيقات الزمن الحقيقي. تحسين سلامة المشروع البرمجي وضمان أمنه بطرق لا يمكن تحقيقها في لغة جافا. ومع ذلك، يُنصح عمومًا بعدم استخدام الوراثة المتعددة إلا عند الضرورة لكونها قد تعقد الكود البرمجي. الأنواع المعممة والقوالب تعمل الإصدارات المعممة Generics للأصناف مع أي نوع من أنواع البيانات وهي تسهل إعادة استخدام الكود البرمجي، وتدعم لغة جافا هذه الميزة عن طريق الأنواع المعممة في حين تدعم لغة C++‎‎ هذه الميزة عن طريق القوالب templates، ولكن مرونة التعامل مع قوالب C++‎‎ تجعل عملية البرمجة أكثر أمنًا وقوة. حيث يمكن لمترجمات C++‎‎ إنشاء أصناف أو وظائف مخصصة جديدة في كل مرة تستخدم فيها أنواعًا مختلفة مع القالب، كما يمكن لقوالب C++‎‎ استدعاء وظائف أو دوال مخصصة بناءً على أنواع وسطاء الدالة، مما يسمح بتخصيص شيفرات مخصصة لأنواع بيانات معينة بدلًا من إنشاء إصدار واحد يناسب جميع أنواع البيانات، يطلق على هذه الميزة اسم تخصيص القالب template specialization. لا تملك لغة جافا ميزة مكافئة لهذه الميزة. بل على العكس تمامًا عند استخدام الأنواع المعممة generics في لغة جافا، تنشئ مترجمات لغة جافا كائنات عامة دون أنواع محددة من خلال عملية تسمى شطب النوع type erasure. وتجري عملية التحقق من النوع أثناء عملية الترجمة، ولكن لا يمكن للمبرمجين تعديل سلوك صنف أو تابع عام بناءً على نوع وسطائه. وكي تتمكن من فهم هذا الأمر بشكل أفضل، انظر سريعًا على الدالة العامة التالية format: std::string format(std::string fmt, T1 item1, T2 item2) تستخدم هذه الدالة قالب template<class T1, class T2>‎‎ من مكتبة C++‎‎ تم إنشاؤها: std::string firstParameter = "A string"; int secondParameter = 123; // Format printed output as an eight-character-wide string and a hexadecimal value format("%8s %x", firstParameter, secondParameter); // Format printed output as two eight-character-wide strings format("%8s %8s", firstParameter, secondParameter); تنشأ الدالة العامة format في لغة C++‎‎ على النحو التالي (لاحظ أننا حددنا نوع وسطاء الدالة ليكون نص string ثم عدد صحيح int) std::string format(std::string fmt, std::string item1, int item2) بينما تنشأ هذه الدالة في لغة جافا دون تحديد أنواع الكائنات string و int الخاصة بوسطاء الدالةitem1 و item2 وفي هذه الحالة، يعرف القالب في لغة C++‎‎ أن آخر معامل مرسل هنا هو عدد صحيح int، وبالتالي يمكنه إجراء عملية تحويله لنص std::to_string المطلوب في الاستدعاء الثاني للدالة format. وبدون استخدام القوالب، قد تؤدي عبارة printf في لغة C++‎‎ والتي تحاول طباعة رقم على هيئة سلسلة نصية كما في الاستدعاء الثاني للدالة formatفي الكود السابق إلى سلوك غير محدد وقد تتسبب في تعطل التطبيق أو طباعة قيم غير صحيحة. من ناحية أخرى،ستكون الدالة في جافا قادرة على التعامل مع الرقم كنص في الاستدعاء الأول، ولكن لن تنسقه على هيئة قيمة ست عشرية مباشرةً. وعلى الرغم من بساطة هذا المثال، لكنه يوضح قدرة لغة C++‎‎ على تعريف واختيار قالب مخصص للتعامل مع أي كائن من الصنف بشكل ديناميكي دون الحاجة لتعديل الصنف أو تعديل كود الدالة format، ويمكنك الحصول على نفس النتيجة في لغة جافا باستخدام ميزة تسمى الانعكاس reflection كبديل على استخدام الأنواع المعممة generics، ولكن هذه الطريقة أقل مرونة وأكثر عرضة للأخطاء. الانعكاس Reflection تمكنك لغة البرمجة جافا من معرفة واستكشاف كافة تفاصيل بنية الكائن (في وقت التشغيل)، حيث يمكنك معرفة ما هي الأعضاء المتاحة في صنف أو نوع صنف معين. وتُسمى هذه الميزة الانعكاس Reflection لكونها تشبه رفع مرآة أمام الكائن لرؤية كل ما يوجد بداخله. في حين لا تمتلك لغة C++‎ ميزة الانعكاس بالكامل، ولكن تمكنك إصدارات C++‎‎ الحديثة من الحصول على معلومات عن نوع الصنف واكتشاف نوع صنف محدد في وقت تنفيذ الكود، لكنها غير قادرة على الوصول إلى معلومات أخرى مثل معلومات حول خصائص الصنف. ‎‎‎ إدارة الذاكرة تختلف لغتا جافا C++‎ كذلك في الأسلوب المتبع في إدارة الذاكرة والتي تتبع عادة أحد طريقتين رئيسيتين الأولى هي الطريقة اليدوية التي يتوجب فيها على المطورين تتبع الذاكرة وتحريرها يدويًا، والطريقة الثانية هي الطريقة الآلية ، حيث يقوم البرنامج بتتبع ومراقبة الكائنات التي لا تزال قيد الاستخدام في الذاكرة، فإذا لم يعد أي كائن قيد الاستخدام، سيعتبر قمامة ويمكن في هذه الحالة إعادة استخدام الذاكرة التي كان يحجزه ويستخدمها، ويسمى هذا الأسلوب في إدارة الذاكرة جمع القمامة أو كنس البيانات المهملة garbage collection. تستخدم لغة جافا هذا الأسلوب الذي يمكنها من إدارة الذاكرة بسهولة أكبر من الطريقة اليدوية، كما أنه يساعد في منع وقوع أخطاء تحرير الذاكرة التي تسهم غالبًا في حدوث ثغرات أمنية . من ناحية أخرى لا توفر لغة C++‎‎ طريقة إدارة الذاكرة الآلية بشكل مدمج لكنها تدعم نوعًا من أنواع كنس البيانات يطلق عليه اسم المؤشرات الذكية smart pointers حيث تستخدم المؤشرات الذكية أسلوبًا يسمى عدّ المراجع وهي طريقة آمنة وفعالة في حال استخدامها بشكل صحيح، كما تقدم لغة C++‎‎ أيضًا مفهوم الهودام destructors التي تقوم بتنظيف أو تحرير الموارد المخصصة للكائن عن حذفه من الذاكرة. وتقدم لغة جافا تخصيص الذاكرة بالطريقة الآلية وفق أسلوب يسمى تخصيص الذاكرة المعتمد على الكومة heap allocation (باستخدام الدالة new أو delete أو الدالة malloc الأقدم التي أتت من لغة C) وتخصيص الذاكرة المعتمد على المكدس stack allocation. ويمكن القول بأن تخصيص الذاكرة المعتمد على المكدس أسرع وأكثر أمانًا من تخصيص الذاكرة المعتمد على الكومة، لأن المكدس هو بنية بيانات خطية في حين أن الكومة هو بينة بيانات شجرية، لذا فإن أسلوب ذاكرة المكدس أسهل بكثير من ناحية التعامل مع تخصيص وتحرير الذاكرة. وهناك أيضًا ميزة أخرى لصالح لغة C++‎‎ تتعلق بتخصيص الذاكرة بالاعتماد على المكدس وهي استخدام تقنية برمجية معروفة باسم Resource Acquisition Is Initialization أو اختصارًا RAII أي أن الحصول على المورد يجب أن يحدث عند إنشاء وتهيئة الكائن، وتربط الموارد مثل المراجع references بدورة حياة الكائن الذي يتحكم فيها، وتحذف أو تحرر في نهاية دورة حياة هذا الكائن. يُحرر مرجع المؤشر الذكي المشار إليه في أعلى الدالة تلقائيًا عند الخروج من الدالة. كما تُحرر الذاكرة المرتبطة به إذا كان هذا هو المرجع الأخير إلى المؤشر الذكي. وبهذه الطريقة تعمل المؤشرات الذكية في C++‎‎ دون الحاجة إلى تحرير المراجع يدويًا، وعلى الرغم من أن لغة جافا تتبع طريقة مشابهًا لهذا الأسلوب في تحرير الذاكرة، إلا أن الأمور تنجز بطريقة أصعب من تقنية تخصيص المورد عند إنشاء الكائن RAII المستخدمة في لغة C++‎‎ خاصةً إذا كنت بحاجة إلى إنشاء عدة موارد في نفس كتلة التعليمات البرمجية. الأداء في وقت التشغيل يدل هذا المفهوم على مدى سرعة تشغيل البرنامج وفي هذا الصدد تملك لغة جافا أداء تشغيل جيد، ولكن تظل لغة C++‎‎ أعلى منها في الأداء لأن إدارة الذاكرة اليدوية أسرع من تقنية جمع القمامة. وبالرغم من ذلك يمكن أن تتفوق لغة جافا في الأداء على C++‎‎ وذلك في حالات خاصة معينة بسبب ميزة الترجمة اللحظية للغة Just-In-Time أو اختصارًا JIT وبشكل مخصص تُشغّل مكتبة الذاكرة القياسية في جافا جمع القمامة بشكل كبير، مقارنة بالتخصيص الأقل لذاكرة الكومة Heap في ++C لكن تبقى لغة جافا لغة سريعة نسبيًا ومقبولة الأداء في التطبيقات التي لا تكون فيها سرعة الاستجابة أمرًا أساسيًا مثل الألعاب أو التطبيقات التي تتطلب استجابة في الزمن الحقيقي. بناء وإدارة الحزم يمكن التغاضي عن نقص أداء لغة جافا مقابل سهولة الاستخدام التي توفرها لا سيما في إدارة الحزم وطريقة بناء التطبيقات وإضافة التبعيات الخارجية إليها. حيث توفر لغة جافا أداة تسمى Maven تبسط العمل على المشاريع وتمكنك من إداراتها ببضع خطوات سهلة كما أنها تتكامل مع العديد من بيئات التطوير المتكاملة مثل IntelliJ IDEA. أما في لغة ++C فلا يوجد مستودع حزم قياسي ولا أسلوب موحد لبناء كود تطبيقات C++‎‎ فبعض المطورين يفضلون استخدام محرر فيجوال استوديو Visual Studio، بينما يستخدم آخرون أداة CMake أو مجموعة أدوات مخصصة أخرى، كما أن بعض مكتبات C++‎‎ التجارية المتنوعة غير مفتوحة المصدر تكون متوفرة بتنسيق ثنائي، ولا يوجد أسلوب ثابت لدمج تلك المكتبات في التطبيقات، وقد تتسبب التغييرات في إعدادات البناء أو إصدارات المُصرّف مشكلات وأخطاء تحول دون عمل هذه المكتبات الثنائية بشكل صحيح. ملاءمة اللغة للمبتدئين تعد لغة C++‎‎ صعبة الاستخدام وغير ملائمة للمبتدئين ليس فقط بسبب تعقيد بناء التطبيقات باستخدمها وصعوبة إدارة الحزم البرمجية، بل بسبب عوامل أخرى من بينها صعوبة تصحيح الأخطاء البرمجية، وصعوبة استخدام اللغة بطريقة آمنة ما لم تكن على دراية جيدة بلغة C ولغات التجميع وبطريقة عمل أجزاء الحاسوب منخفضة المستوى، فلغة C++‎‎ أداة قوية يمكن أن تنجز من خلالها الكثير، ولكنها لغة خطيرة إذا استخدمتها بطريقة خاطئة. من ناحية أخرى، تعد لغة جافا أسهل للمبتدئين مقارنةً بلغة C++‎‎ بسبب أسلوب إدارة الذاكرة السهل الذي تتبعه والذي شرحناه سابقًا، فلن يحتاج مطورو جافا للقلق بشأن تحرير ذاكرة الكائنات غير المستخدمة حيث يمكن للغة التعامل مع هذه المهمة تلقائيًا بكل سهولة. هل أختار C++‎‎ أم Java؟ الآن بعد تعرفت على أهم الاختلافات والفروقات بين لغة C++‎‎ و Java لنعد للسؤال الأساسي هل أستخدم C++‎‎ أم جافا في تطوير تطبيقاتي؟ في الواقع لا يوجد جواب موحد على هذا السؤال يناسب الجميع حتى مع الفهم العميق لميزات كل من هاتين اللغتين. فقد يفضل مهندسو البرمجيات الذين لا يعرفون مفاهيم البرمجة على مستوى منخفض لغة البرمجة جافا عندما لا يكون أمامهم سوى خيارين فقط هما جافا أو C++‎، إلا في حال الحاجة لتطوير تطبيقات زمن حقيقي مثل الألعاب كما شرحنا من قبل. من ناحية أخرى، قد يختار المطورون الذين يسعون إلى تطوير خبراتهم البرمجية لغة C++‎. ورغم ذلك قد تؤثر الفروق التقنية بين لغتي C++‎‎ و Java بشكل بسيط على قرار اختيار اللغة الأنسب، ويعتمد نوع اللغة بشكل أكبر على طبيعة التطبيقات أو المنتجات التي تريد تطويرها فقد تكون جافا هي الأنسب أو هي الأنسب أو ربما في النهاية برمجة ثالثة هي الأنسب. ولمساعدتك على الاختيار إليك الشكل التالي الذي يعد بمثابة دليل يساعدك على تحديد اللغة الأفضل حسب طبيعة التطبيق الذي تنوي تطويره. الخلاصة تعرفنا في هذا المقال على أبرز الفروقات بين لغة C++‎‎ وجافا من عدة جوانب تتعلق بالإنتاجية وإدارة الذاكرة وطريقة عمل مصرّفات اللغة وغيرها من العوامل، ويمكن القول ختامًا أن لغة C++‎‎ تتفوق في الأداء على لغة جافا وتوفر إمكانية الوصول إلى ميزات منخفضة المستوى التي تفتقر إليها لغة جافا، في حين تتميز جافا بسهولة الاستخدام وتعتبر خيارًا أفضل للمبرمجين المبتدئين. وبالرغم من أن C++‎‎ و Java تنتميان إلى عائلة لغات سي C، إلا أنهما يحملان تصميمًا مستقلاً، ويبقى العامل الأهم في اختيار اللغة المناسبة هو أهداف تطبيقك وطبيعة المهام المطلوبة فيه. ترجمة بتصرف لمقال An In-depth Look at C++ vs. Java للكاتب Timothy Mensch اقرأ أيضًا دليلك الشامل إلى لغات البرمجة عالية المستوى أنظمة التصريف المستخدمة لبناء البرامج المكتوبة بلغة Cpp وأهم أخطاء عملية البناء مواضيع متقدمة عن الأنواع والتعامل معها في Cpp البواني وتهيئة الكائنات Object Initialization في جافا المؤشرات الذكية (Smart Pointers) في Cpp
  3. لاشك أن أي مطور تطبيقات سمع بلغة البرمجة كوتلن Kotlin ولغة جافا Java فهما من لغات البرمجة القوية والمستخدمة بشكل كبير مع نظام التشغيل أندرويد، وبالرغم من أن لغة جافا قد خسرت معركة نظام أندرويد لصالح لغة كوتلن بعد أن أعلنت جوجل عام 2017 أن كوتلن هي اللغة الرسمية المعتمدة لنظام التشغيل أندرويد وأنها أكثر ملاءمةً لتطبيقات الهواتف المحمولة الجديدة، إلا أن كلا اللغتين تملكان العديد من نقاط القوة، ومن المهم للمطورين فهم الاختلافات اللغوية بينهما لتحقيق انتقال سهل من جافا إلى كوتلن. سنناقش في هذا المقال مميزات كل لغة من هاتين اللغتين، ونسلط الضوء على أبرز جوانب التشابه والاختلاف بينهما لنساعدك على تحديد اللغة الأنسب لك من بينهما والانتقال بسلاسة بينهما. هل لغة كوتلن ولغة جافا متشابهتان؟ نعم، حيث تملك هاتان اللغتان الكثير من القواسم المشتركة، فكلتاهما تعملان على آلة جافا الافتراضية JVM ولن يحتاج المطورون للقلق بشأن كتابة الأكواد البرمجية الأصلية للآلة، ويمكن للغتين أن تستدعيا أكواد بعضهما البعض بسهولة-بمعنى أن بإمكانك استدعاء أكواد جافا من داخل أكواد كوتلن- والعكس صحيح. كما يمكن استخدام لغة جافا في تطبيقات الواجهة الخلفية التي تعمل من طرف الخادم وفي تطبيقات قواعد البيانات وتطبيقات الواجهة الأمامية والأنظمة المدمجة وتطبيقات الجوال وغيرها من أنواع التطبيقات المتنوعة الأخرى، وكذلك تتميز لغة كوتلن أيضًا بأنها لغة متعددة الاستخدامات فهي تعمل على أي نظام تشغيل يدعم آلة جافا الافتراضية JVM وتعد اللغة الرسمية لتطوير تطبيقات أندرويد، ويمكن كذلك استخدامها في برمجة تطبيقات جافا سكريبت من خلال مشروع Kotlin/JS الذي يسمح بكتابة شيفرات مشتركة بين اللغتين، ولتطوير تطبيقات من طرف الخادم، وتطبيقات الويب، وتطبيقات سطح المكتب. إن لغة جافا هي لغة أقدم وأكثر نضجًا من كوتلن، فقد صدرت لأول مرة في عام 1996، بينما أُطلِق أول إصدار من كوتلن Kotlin 1.0 في عام 2016، وبالرغم من ذلك تمكنت لغة كوتلن من فرض نفسها بسرعة وأصبحت اللغة الرسمية المفضلة لتطوير نظام التشغيل أندرويد في عام 2019، ويمكن القول بأننا إذا استثنينا مجال الأندرويد، فلن يكون هناك أي مميزات ترجح كفة كوتلن على جافا. وإليك الجدول التالي الذي يوضح تطور لغتي البرمجة Java و Kotlin: السنة جافا كوتلن 1995–2006 JDK Beta, JDK 1.0, JDK 1.1, J2SE 1.2, J2SE 1.3, J2SE 1.4, J2SE 5.0, Java SE 6 لم تكن لغة كوتلن موجودة 2007 بدء العمل على مشروع Project Loom لم تكن لغة كوتلن موجودة بعد 2010 لم يطلق إصدار جديد من جافا بدء تطوير كوتلن 2011 Java SE 7 الإعلان عن مشروع كوتلن 2012 لم يطلق إصدار جديد من جافا اعتبار لغة كوتلن مفتوحة المصدر 2014 Java SE 8 (LTS) لم يطلق إصدار جديد من كوتلن 2016 لم يطلق إصدار جديد من جافا Kotlin 1.0 2017 Java SE 9 Kotlin 1.2 والإعلان عن دعم Kotlin لنظام أندرويد. 2018 Java SE 10, Java SE 11 (LTS) Kotlin 1.3 ودعم الروتينات المساعدة coroutines 2019 Java SE 12, Java SE 13 Kotlin 1.4 (توافق مع Objective-C و Swift) والإعلان عن كوتلن كلغة مفضلة للمطورين من قبل جوجل. 2020 Java SE 14, Java SE 15 لم يطلق إصدار جديد من كوتلن 2021 Java SE 16, Java SE 17 (LTS) Kotlin 1.5, Kotlin 1.6 2022 Java SE 18, JDK 19 Kotlin 1.7 (alpha version of Kotlin K2 compiler), Kotlin 1.8 2023 Java SE 20, Java SE 21, JDK 20, JDK 21 Kotlin 1.9 2024 Java SE 22 Kotlin 2.0 مقارنة بين كوتلن وجافا من ناحية الأداء واستهلاك الذاكرة قبل الخوض في تفاصيل مميزات لغتي كوتلن وجافا، لنقارن أداءهما واستهلاكما للذاكرة حيث يشكل هذان العاملان اعتبارين مهمين لكل من المطورين والمستخدمين على حد سواء. أولًا: من ناحية الأداء على الرغم من أن كوتلن و جافا ولغات البرمجة الأخرى التي تنفذ على الآلة الافتراضية JVM الأخرى مختلفة في عدة عوامل عن بعضها، إلا أنها متشابهة إلى حد ما في الأداء، لا سيما عند مقارنتها مع عائلة اللغات المعتمدة على المصرّفات والتي تنفذ على المعالج الخاص بالآلة مثل مُصرّف GCC أو Clang ، فقد صممت آلة جافا الافتراضية JVM في الأصل لاستهداف الأنظمة المدمجة محدودة الموارد التي كانت متوفرة في فترة التسعينيات، وهذا فرض قيدين أساسيين على هذه اللغات لتتمكن من العمل بكفاءة على موارد محدودة وهما: جعل شيفرة البايت لآلة جافا الافتراضية بسيطة قد المستطاع: إذ يحتوي الإصدار الحالي من آلة جافا الافتراضية JVM التي تترجم لغتي كوتلن وجافا، على 205 تعليمة فقط، في حين يمكن للمعالجات الحديثة x64 أن تدعم وتنفذ أكثر من 6000 تعليمة مشفرة اعتمادًا على طريقة العد. التحسين في وقت التشغيل Runtime: فأسلوب تعدد المنصات الذي يعتمد على كتابة الكود مرة واحدة وتشغيله في أي مكان يساعد على إجراء عمليات تحسين الأداء في وقت تشغيل الكود (وليس في وقت تصريفه compile-time)، بعبارة أخرى تقوم آلة جافا الافتراضية JVM بترجمة معظم شيفرة البايت الخاصة بها إلى تعليمات في وقت التشغيل، فعند ترجمة كود جافا أو كوتلن لا يحول هذا الكود مباشرةً إلى كود الآلة بل يحول إلى كود بايت byte code وسيط ثم تترجم الآلة الافتراضية هذا الكود الوسيط إلى تعليمات يمكن للجهاز فهمها أثناء تشغيل البرنامج، وبالتالي تؤجل بعض عمليات التحسين لحين تشغيل البرنامج بشكل فعلي. كما يمكن استخدام تطبيقات مفتوحة المصدر لآلة جافا الافتراضية JVM مثل HotSpot لتحسين الأداء إذ تقوم في هذه الحالة بتجهيز كود بايت مسبق الترجمة pre-compiles لتشغيله بشكل أسرع من خلال المفسر interpreter. فكما ترى تتبع كل من جافا وكوتلن أساليب متشابهة لترجمة وتشغيل التعليمات البرمجية لذا لا توجد سوى اختلافات طفيفة في الأداء بينهما سببها بعض الخصائص الفريدة المميزة لكل لغة، على سبيل المثال: تتجنب لغة كوتلن استدعاء الدوال باستخدام الدوال المباشرة inline functions مما يحسن أداءها، بينما تعتمد جافا على الدوال التي تتطلب تخصيص ذاكرة إضافية. تتجنب كوتلن استخدام الدوال العليا higher-order functions التي تستخدمها جافا لامدا Java lambda في الاستدعاء الديناميكي للدالة من خلال التعليمة InvokeDynamic مما يحسن الأداء. تحتوي شيفرة كود البايت bytecode التي تولدها لغة كوتلن على ميزة التحقق من القيم الفارغة عند استخدام التبعيات الخارجية، مما يبطئ أداءها مقارنةً بجافا. ثانيًا: من ناحية الذاكرة صحيح أن استخدام الكائنات للأنواع الأساسية (وهو الأسلوب المتبع في لغة كوتلن) يتطلب تخصيصًا أكثر للذاكرة مقارنة باستخدام أنواع البيانات الأولية (وهو الأسلوب المتبع في لغة جافا) من الناحية النظرية، لكن من الناحية العملية تستخدم تعليمات كود البايت في لغة جافا تقنية التغليف التلقائي autoboxing وفك التغليف unboxing للتعامل مع الكائنات والتي يمكن أن تضيف حملًا وعبئًا حسابيًا عند استخدامها بشكل مفرط. على سبيل المثال، لا تقبل الدالة String.format الخاصة بجافا سوى الكائنات كدخل لها، لذلك لذلك عند الحاجة لتنسيق عدد صحيح من نوع int في جافا، يتم تغليفه تلقائيًا في كائن من نوع Integer قبل استدعاء الدالة String.format. ويمكن القول بشكل عام، لا توجد اختلافات كبيرة بين لغتي جافا وكوتلن فيما يتعلق بالأداء والذاكرة، وبالرغم من أنك قد تجد معايير أداء مختلفة عبر الإنترنت تظهر اختلافات طفيفة في الاختبارات الدقيقة بين هاتين اللغتين، ولكن لا يمكن تعميم هذه النتائج. والجدير بالملاحظة أن الأداء واستهلاك الذاكرة لا ينبغي أن يكونا العاملين الأساسيين في اختيارك بين كوتلن أو جافا ومن الأفضل التركيز على عوامل أخرى مثل المميزات الخاصة بكل لغة. مقارنة الميزات الفريدة لكل من لغتي كوتلن وجافا تشترك لغتا كوتلن وجافا في العديد من السمات الأساسية، لكن كل لغة تقدم ميزات فريدة ومختلفة عن الأخرى، فمنذ أن أصبحت كوتلن لغة جوجل المفضلة لتطوير أندرويد، برزت ميزات مثل الدوال الإضافية وإمكانية تعيين القيم null بشكل صريح Explicit nullability بكونها الميزات الأكثر فائدة للغة، من ناحية أخرى عند استخدام كوتلن فإن أكثر الميزات التي سنفتقدها والتي كانت تتيحها لغة جافا هي الكلمة المفتاحية protected والمعامل الشرطي الثلاثي ternary operator الذي يمكننا من التعبير عن الشروط بطريقة قصيرة ومختصرة. لنقم بتحليل مفصل للمميزات المتاحة في كولن مقارنةً بجافا، يمكنك تنفيذ الأمثلة التالية المكتوبة بلغة كوتلن وجافا لفهم الفروقات بشكل عملي. إليك بعض الميزات البارزة في لغة كوتلن بالمقارنة مع لغة جافا: الخاصية كوتلن جافا الوصف الدوال الإضافية Extension functions نعم لا تسمح لك بتوسيع صنف أو واجهة بوظائف جديدة من خلال تعريف التوابع أو الخصائص الإضافية فقط دون الحاجة إلى إنشاء أصناف جديدة class Example {}<br />// extension function declaration fun Example.printHelloWorld() { println("Hello World!") }<br /><br />// extension function usage Example().printHelloWorld() التحويلات الذكية Smart casts نعم لا تتبع الشروط داخل عبارة if الشرطية، وتحويل الأنواع تلقائياً بطريقة آمنة.<br /><br /> fun example(a: Any) { if (a is String)<br /> { println(a.length) <br />// automatic cast to String } } كما توفر لغة كوتلن معاملات تحويل آمنة بين الأنواع تعيد null عند حدوث خطأ في عملية التحويل ومعاملات تحويل غير آمنة ترمي استثناء عند حدوث خطأ في التحويل // unsafe "as" cast throws exceptions val a: String = b as String // safe "as?" cast returns null on failure val ? String? = d as? String الدوال المباشرة Inline functions نعم لا تقلل من التحميل الزائد للذاكرة وتحسن سرعة الكود عن طريق تضمين رمز الدالة (نسخه إلى موقع الاستدعاء) inline fun example() دعم مدمج لمفهوم التفويض delegation نعم لا تدعم نمط التفويض بشكل مدمج باستخدام الكلمة المفتاحية by class Derived(b: Base) : Base by b كتابة أسماء مستعارة أو بديلة لأنماط البيانات نعم نعم توفر أسماء مختصرة أو مخصصة لأنماط البيانات والتوابع والأصناف لتسهيل قراءتها typealias ShortName = LongNameExistingType الحقول غير الخاصة بالصنف لا نعم توفر جافا كافة معاملات تعديل الوصول الأربعة وهي المحمي protected والافتراضي default المعروف أيضًا بمعامل الحزمة الخاص package-private بالإضافة إلى معامل الوصول العام public والخاص private. بينما تفتقد كوتلن لمعامل تعديل الوصول المحمي والافتراضي. العامل الثلاثي Ternary operator لا نعم يستبدل عبارة if/else بكود أبسط وأكثر قابلية للقراءة: if (firstExpression) { // if/else variable = secondExpression; } else { variable = thirdExpression; } // ternary operator variable = (firstExpression) ? secondExpression : thirdExpression; توسيع نطاق البيانات بصورة ضمنية لا نعم تسمح لغة جافا بالتحويل التلقائي من نوع بيانات أصغر إلى نوع بيانات أكبر مثلًا من عدد صحيح إلى عشري بصورة تلقائية في حين لا تقوم كوتلن بذلك int i = 10; long l = i; // first widening conversion: int to long float f = l; // second widening conversion: long to float الاستثناءات المتحقق منها Checked exceptions لا نعم يحتاج المصرف إلى طريقة للتعامل مع الاستثناءات التي تقع عند تنفيذ البرنامج إما باستخدام الكلمة المفتاحية throws أو من خلال كتلة تعليمات try-catch ملاحظة: تم تصميم الاستثناءات التي يجب التحقق منها لتشجيع المطورين على تصميم برمجيات قوية. ومع ذلك، يمكن أن تؤدي إلى هذه الاستثناءات لزيادة حجم الكود، وتجعل عملية إعادة التصميم صعبة، وتؤدي إلى معالجة سيئة للأخطاء عند استخدامها بطريقة غير صحيحة. لذا فإن مسألة تحديد فيما إذا كانت هذه الميزة إيجابية أم سلبية تعتمد على احتياجات المطورين. هناك موضوع واحد تم استبعاده عمدًا من هذا الجدول وهو التعامل مع القيم الفارغة null في كل من لغتي كوتلن وجافا. إذ يستحق هذا الموضوع مقارنة أكثر تفصيلاً بين اللغتين لذا سنناقشه بشكل منفصل في الفقرة التالية. الفرق بين كوتلن وجافا في التعامل مع القيم الفارغة null إن ميزة عدم قبول القيم الفارغة هي واحدة من أروع ميزات كوتلن. إذ توفر هذه الميزة الوقت على المطورين ولا تضطرهم للتعامل مع الاستثناء من نوع NullPointerExceptions (وهو أحد الاستثناءات التي تطلق في وقت التشغيل RuntimeExceptions). ففي لغة جافا يمكنك بشكل افتراضي تعيين قيمة فارغة لأي متغير على النحو التالي: String x = null; // Running this code throws a NullPointerException try { System.out.println("First character: " + x.charAt(0)); } catch (NullPointerException e) { System.out.println("NullPointerException thrown!"); } أما في لغة كوتلن فلديك خياران، إما تجعل المتغير يقبل قيم فارغة nullable أو لا يقبل قيم فارغة non-nullable كما يلي: // This line throws a compile-time error because you can't assign a null value nonNullableNumber = null var nullableNumber: Int? = 2 // This line does not throw an error since we used a nullable variable nullableNumber = null يفضل استخدام المتغيرات non-nullable بشكل افتراضي، وتجنب استخدام المتغيرات nullable للحصول على أفضل الممارسات. ملاحظة: الهدف من الأكواد المكتوبة بكوتلن ومقارنتها مع أكواد مكتوبة بلغة جافا هو توضيح الاختلافات بين اللغتين وفي حال كنت مبتدئًا في لغة كوتلن تجنب قدر المستطاع تعيين المتغيرات لتكون nullable بدون هدف أو عند تحويل كود جافا إلى كوتلن. ومع ذلك، هناك بعض الحالات التي قد تستخدم فيها المتغيرات التي تقبل القيم null في كوتلن ومن بين هذه الحالات نذكر: الحالة مثال عندما تبحث عن عنصر في قائمة غير موجود (عادةً عند التعامل مع بنية لتخزين ومعالجة البيانات). val list: List<Int> = listOf(1,2,3) val searchResultItem = list.firstOrNull { it == 0 } searchResultItem?.let { // Item found, do something } ?: run { // Item not found, do something } عندما تريد تهيئة متغير في وقت التنفيذ وليس في لحظة الإعلان عنه باستخدام lateinit. lateinit var text: String fun runtimeFunction() { // e.g., Android onCreate text = "First text set" // After this, the variable can be used } ينصح بعدم المبالغة في استخدام التعليمة lateinit التي تؤجل تهيئة المتغير حتى يتم الوصول إليه لأول مرة بشكل مفرط مع لغة كوتلن باستثناء الحالات التي لا يمكنك فيها معرفة قيمة المتغير عند إعلانه، أو عند الحاجة لربط العرض view bindings أي ربط عناصر واجهة المستخدم (مثل الأزرار والنصوص والصور وما إلى ذلك) بمتغيرات في الشيفرة البرمجية، كي تتمكن من لتحكم فيها والتفاعل معها من خلال العرض وإجراء حقن قيم للمتغيرات في وقت التشغيل variable injections في أندرويد. @Inject // With the Hilt library, this is initialized automatically lateinit var manager: SomeManager lateinit var viewBinding: ViewBinding fun onCreate() { // i.e., Android onCreate binding = ActivityMainBinding.inflate(layoutInflater, parentView, true) // ... } بشكل عام، تتعامل لغة كوتلن مع القيم الفارغة null بمرونة أكبر وتوفر تجربةً محسنةً مقارنةً بلغة جافا. الاختلافات في الميزات المشتركة بين جافا وكوتلن على الرغم من أن كل لغة لها ميزات فريدة، إلا أن كوتلن و جافا تشتركان في العديد من الميزات أيضًا، ومن الضروري فهم خصوصية كل لغة من أجل الانتقال بسلاسة بين اللغتين. لنقم بفحص أربعة مفاهيم مشتركة بين جافا وكوتلن لكنها تعمل بشكل مختلف في كل لغة: الميزة جافا كوتلن كائنات نقل البيانات بين طبقات التطبيق المختلفة DTOs توفر سجلات جافا Java records طريقة مناسبة لنقل المعلومات عن البيانات أو حالة هذه البيانات كما تتضمن التوابع المساعدة toString و equals و hashCode بشكل افتراضي وهي متاحة منذ إصدار جافا SE 15: public record Employee( int id, String firstName, String lastName ) توفر لغة كوتلن أصناف البيانات data classes وهي تعمل بشكل مشابه لسجلات جافا كما تتوفر فيها توابع مدمجة مساعدة toString و equals وcopy: data class Employee( val id: Int, val firstName: String, val lastName: String ) تعبيرات لامدا Lambda expressions تتوفر تعبيرات لامدا في جافا منذ الإصدار جافا 8 وهي تملك بنية بسيطة كالتالي parameter -> expressionوتستخدم القوسين المتعرجين لتمرير الوسطاء(parameter1, parameter2) -> { code } كما في المثال التالي الذي يستخدم تعبيرات لامدا لصباعة عناصر قائمة أعداد صححية: ArrayList ints = new ArrayList<>(); ints.add(5); ints.add(9); ints.forEach((i) -> { System.out.println(i); }); تكتب تعبيرات لامدا في كوتلن بالصيغة التالية { parameter1, parameter2 -> code } وتحيط بها دائمًا الأقواس المتعرجة على سبيل المثال يستخدم الكود التالي تعبير لامدا لتعريف دالة بسيطة تقارن طول سلسلتين نصيتين: var p: List = listOf("firstPhrase", "secondPhrase") val isShorter = { s1: String, s2: String -> s1.length < s2.length } println(isShorter(p.first(), p.last())) التزامن Concurrency توفر الخيوط threads في لغة جافا ميزة تنفيذ مهام متزامنة وتسهل الحزمة java.util.concurrency التعامل مع تعدد المهام من خلال توفير أصناف خاصة مثل Executor و ExecutorService (كما يمكن مشروع Project Loom لتحسين جافا من إنجاز التزامن بطريقة أسرع من خلال توفير خيوط خفيفة الوزن lightweight threads). تعمل الروتينات المساعدة في كوتلن التي توفرها مكتبة kotlinx.coroutines على تسهيل التزامن وتعدد المهام، كما تتضمن قسم خاص لتعدد الخيوط multithreading. ويساهم مدير الذاكرة في إصدار Kotlin 1.7.20 والإصدارات الأحدث بتقليل القيود السابقة على تنفيذ التزامن وتعدد الخيوط للمطورين الذين ينتقلون بين نظام التشغيل أندرويد Android وآي أو إس iOS. السلوك الثابت في الأصناف Static behavior in classes تسهل الأعضاء الثابتة أو الستاتيكية static members في جافا مشاركة التعليمات البرمجية بين كائنات الصنف وتضمن إنشاء نسخة واحدة فقط من العنصر. ويمكن تحقيقها من خلال كتابة الكلمة المفتاحية static قبل المتغيرات والدوال والكتل: class Example { static void f() {/…/} } تتيح الكائنات المرافقة للأصناف companion objects في كوتلن من تحقيق السلوك الثابت للأصناف ومشاركة التعليمات البرمجية بين كائنات الصنف فالأعضاء في الكائن المرافق تكون مشتركة بين جميع كائنات الصنف ولا حاجة لإنشاء كائن منه لاستخدامها، ولكن صياغتها ليست بسيطة كما هو الحال في جافا: إليك مثال على كيفية استخدام كائن مرفق في كوتلن:\ class Example { companion object { fun f() {/…/} } } بالطبع، تملك كل من لغة البرمجة كوتلن وجافا أيضًا تراكيب مختلفة في كتابة التعليمات البرمجية، ولن نناقش كل الاختلافات هنا في سياق المقال الحالي، لكننا سنكتفي بالاطلاع على طريقة كتابة الحلقات التكرارية في كل لغة وهذا سيمنحك فكرة عن الأسلوب العام لكتابة تعليمات كل لغة: نوع الحلقة جافا كوتلن حلقة for باستخدام in for (int i=0; i<=5; i++) { System.out.println("printed 6 times"); } for (i in 0..5) { println("printed 6 times") } حلقة for باستخدام until for (int i=0; i<5; i++) { System.out.println("printed 5 times"); } for (i in 0 until 5) { println("printed 5 times") } forEach List<String> list = Arrays.asList("first", "second"); for (String value: list) { System.out.println(value); } var list: List<String> = listOf("first", "second") list.forEach { println(it) } while int i = 5; while (i > 0) { System.out.println("printed 5 times"); i--; } var i = 5 while (i > 0) { println("printed 5 times") i-- } احرص على الفهم العميق لميزات لغة كوتلن فهذا سيساعدك في التحول بين كوتلن وجافا بسهولة وسلاسة هل أختار جافا أم كوتلن من أجل مشاريع أندرويد لقد ناقشنا حتى الآن العديد من العوامل المهمة التي يجب التفكير فيها عند اتخاذ القرار بين كوتلن وجافا في من وجهة نظر عامة، لكن لا تكتمل عملية المقارنة بين لغتي كوتلن وجافا إذا لم نتطرق للحديث عن نظام أندرويد. فإذا كنت تنوي تطوير تطبيق أندرويد من الصفر وتتساءل ما إذا كان يجب عليك أن تستخدم جافا أو كوتلن فينصح بأن تختار كوتلن بدون أي تردد فهي لغة الأندرويد المفضلة لدى جوجل كما ذكرنا سابقًا. ورغم ذلك، فإن الإجابة على هذا السؤال قد يخضع لاعتبارات أخرى بالنسبة لتطبيقات الأندرويد المطورة مسبقًا. إذا يجب عليك أيضًا مناقشة أمور إضافية مثل مسألة الأعباء التقنية والكلفة المرتبطة بتعديل التقنية المستخدمة في هذه التطبيقات من لغة جافا إلى كوتلن وخبرة المطور Developer Experience الذي يعرف من قبل جافا ويتوجب عليه تعلم لغة جديدة! فإذا كان لدى شركة ما تطبيق أندرويد يستخدم جافا، فمن المستبعد أن تعيد برمجته من جديد بكوتلن بل ستفضل إضافة ميزات جديدة له فقط، وهذا أمر مفهوم. فطبيعة السوق تنافسية وتتطلب دورة سريعة لتحديثات التطبيق. لكن يجب الانتباه فالعبء التقني له تأثير خفي، فهو يزيد من التكاليف مع كل تحديث لأن المهندسين يضطرون للتعامل مع الشيفرة غير المستقرة التي من الصعب إعادة تنظيمها، وقد يدخل الشركات في دورة لا نهائية من التكاليف الباهظة الناتجة عن كثرة عمليات التعديل. وقد يكون من الأجدى التوقف عن تعديل الأكواد القديمة والاستثمار في حلول طويلة الأمد، حتى لو تطلب ذلك عمليات إعادة كتابة الكود البرمجي من جديد أو تحديث كود البرنامج لاستخدام لغة حديثة مثل كوتلن. كما أن استخدام لغة برمجة جديدة من قبل المطورين أمر يستحق النقاش حيث يمكن أن يفيد المطورين باختلاف خبراتهم إذ: يستفيد المطورون المبتدئون من الموارد المناسبة. يتحسن المطورون ذوو المستوى المتوسط ويحصلون على فرص أكبر للعمل. يحتاج المطورون الكبار إلى القدرة على تصميم وتنفيذ شيفرات رائعة. ولا شك أن الاهتمام بتجربة المطورين الخبراء مهم وضروري لاسيما بأن خبرتهم تؤثر على جميع المطورين ويمكن باستخدام لغات حديثة مثل كوتلن تطوير التطبيقات بسرعة وكفاءة وسهولة وهو ما سيستغرق وقتًا أطول مع لغات أقدم مثل جافا. الخلاصة تعرفنا في مقال اليوم على أهم الفروقات بين لغتي كوتلن و جافا اللتان تعدان من لغات البرمجة القوية والفعالة، ووجدنا أنه وبالرغم من أن لغة جافا لديها مجموعة واسعة من التطبيقات، فقد استحوذت كوتلن على المكانة التي امتلكتها لغة جافا والتي كانت لغة مفضلة لتطوير تطبيقات الأندرويد فقد وجهت جوجل كل جهودها نحو تطوير ودعم كوتلن، ما جعلها تحظى بالأولوية أمام جافا. لذا ينصح أي مطور تطبيقات باعتماد لغة كوتلن في أي شيفرة جديدة يكتبها لا سيما أن بيئة تطوير تطبيقات جافا IntelliJ IDEA تأتي مع أداة تحويل تلقائي من لغة جافا إلى كوتلن. ترجمة وبتصرف للمقال Kotlin vs. Java: All-purpose Uses and Android Apps للكاتب Gabriel Gircenko اقرأ أيضًا تعرف على لغة جافا Java الدليل السريع إلى لغة البرمجة Kotlin Kotlin هو جافا الجديد كيفية كتابة برامج صحيحة باستخدام لغة جافا
  4. لقد أحدثت النماذج اللغوية الكبيرة LLMs وأشهرها روبوت المحادثة ChatGPT الذي طورته شركة OpenAI ثورة في الكثير من المجالات وأثبتت قدرتها على توليد نصوص تشبه النصوص البشرية، كما سهلت أداء العديد من المهام في مختلف المجالات ومن أبرزها مجال تطوير البرمجيات، فقد وفرت للمطورين والمبرمجين واجهات برمجية APIs تساعدهم في إنشاء تطبيقات ذكاء اصطناعي بسهولة كبيرة. سنتعلم في مقال اليوم كيفية تطوير تطبيق بايثون قادر على فهم نصوص ملف PDF والإجابة على أي أسئلة نطرحها حول محتوى هذا الملف بالاستفادة من ميزات نماذج الذكاء الاصطناعي التي توفرها واجهة برمجة تطبيقات OpenAI API وإطار عمل LangChain، وهو إطار عمل للغة البرمجة بايثون يمكن دمجه في تطبيقاتك لتعزز إمكانيات النماذج اللغوية الكبيرة. نظرة عامة على التطبيق المطلوب ومتطلباته نريد تطوير تطبيق بايثون باسم "اسأل PDF" يتيح هذا التطبيق للمستخدم تحميل ملف PDF من جهاز الحاسوب الخاص به، ويسمح له بطرح أي سؤال، ويستخدم تقنيات معالجة اللغة الطبيعية NLP للبحث عن إجابة مناسبة على هذا السؤال بناءً على محتوى الملف ويعرض له الإجابة. يحتاج تنفيذ التطبيق إلى المتطلبات التالية: معرفة بأساسيات لغة بايثون وتثبيت بيئة بايثون على الحاسوب المحلي. تثبيت أداة pip لتثبيت وإدارة حزم البرمجيات المساندة لبايثون. إنشاء حساب على على منصة OpenAI والحصول على مفتاح الواجهة البرمجية OpenAI API key خاص بك. معرفة بأساسيات نماذج وأطر عمل الذكاء الاصطناعي واختيار النماذج المناسبة لعمل التطبيق. معرفة بطريقة معالجة النصوص باستخدام التضمينات الرقمية ومخازن المتجهات. ما أهمية التضمينات الرقمية في تطبيقات البحث كي نتمكن من إنجاز عملية البحث في ملف pdf نحتاج لإنشاء تضمينات رقمية "Embedding" لكل جزء من أجزاء الملف، فبما أننا سنبحث في ملفات تتضمن الكثير من النصوص، فسنحتاج للاعتماد على تقنية التضمين وهي تقنية تستخدم في معالجة اللغة الطبيعية NLP وتقوم بتمثيل النصوص بشكل أعداد عشرية لتساعدنا على اكتشاف المعنى الدلالي للكلمات والعبارات والجمل بسرعة وفعالية، وتحولها لتنسيق يسهل الوصول إليه والبحث فيه عن المعلومات بدقة وكفاءة، بعدها سنقسم الملف إلى أجزاء صغيرة chunks حجم كل منها مثلًا 1000 حرف، ونفهرس هذه الأجزاء في قاعدة بيانات أو مخزن متجهات vectors حتى نتمكن من استرجاعها بسهولة عندما نريد الإجابة على الأسئلة الموجودة فيه. بهذه الطريقة ستملك النصوص ذات المحتوى المتشابه لغويًا متجهات متشابهة وبهذا يمكن مقارنة المتجهات والعثور على النصوص المتشابهة بسهولة وسرعة، وعندما نريد الحصول على إجابة لسؤال معين فسوف ننشئ كذلك تضمين رقمي لنص السؤال، ثم نقارن تضمين السؤال مع جميع المتجهات التي خزناها في مخزن المتجهات، ونختار الأكثر تشابهًا بناءً على حسابات رياضية على المتجهات، ثم نمرر تضمين السؤال مع التضمينات الأكثر تشابهًا معه إلى روبوت المحادثة ليولّد لنا الإجابة المناسبة بناءً على هذه المعطيات. خطوات التطبيق العملي لننشئ تطبيق واجهة رسومية GUI بلغة بايثون باستخدام المكتبة المدمجة Tkinter يمكننا من خلالها تحميل ملف PDF الذي نريده ثم نبدأ بمعالجة محتواه واستخراج كافة النصوص منه، فبعد تحميل الملف المطلوب سنحول نصوصه إلى تضمينات كما شرحنا سابقًا، ثم سنبدأ بعملية طرح سؤال متعلق بمحتوى الملف. سنحول السؤال كذلك لتضمين أو تمثيل رقمي ونبحث عن كافة المحتوى المشابه له وسنرسل بعد ذلك للواجهة البرمجية ChatGPT API سلسلة تتضمن السؤال وكل المحتوى المرتبط به الذي وجدناه ضمن الملف كمدخلات، ثم سنولّد بناءً على ذلك الإجابة المناسبة بالاستفادة من إمكانيات النماذج اللغوية الكبيرة LLMs. وإليك خطوات القيام بذلك بالتفصيل: خطوة 1: الحصول على مفتاح API الخاص بك من OpenAI للحصول على مفتاح API الخاص بك من OpenAI عليك اتباع الخطوات التالية: انتقل إلى الصفحة الرسمية لمنصة openai وأنشئ حسابًا جديدًا إذا لم تكن قد أنشأته من قبل ثم سجل الدخول لحسابك. انقر على القسم اAP للدخول لحسابك في صفحة مطوري OpenAI. انقر على أيقونة ChatGPT أعلى يمين الصفحة واختر البند API Keys. انقر على Create new secret key لإنشاء مفتاح API الخاص بك. أدخل اسمًا اختياريًا لمفتاح API للرجوع إليه لاحقًا. هذا كل ما يلزم لإنشاء مفتاح API الخاص بك من OpenAI. سيبدأ المفتاح بـ sk-‎ انسخه واحفظه في مكان آمن واحرص على عدم مشاركته مع أحد أو كتابته في كود عام، فقد يقوم الآخرون باستخدام مفتاح API الخاص بك ويستهلكون رصيدك المتاح. ملاحظة: عليك الاشتراك بخطة مدفوعة للاستفادة من واجهة برمجة التطبيقات OpenAI API في تطبيقاتك، وسيكون المبلغ المقتطع بحسب الاستهلاك والنموذج المستخدم، فلكل نموذج تكلفة مختلفة وكلما زادت طلباتك أو عدد الرموز tokens المرسلة والمستقبلة من النموذج كلما زادت فاتورتك، لكن يمكنك في البداية الحصول على رصيد مجاني بقيمة ‎5 دولار‎‎‎ عند الاشتراك حديثًا في المنصة للتجربة. خطوة 2: إنشاء ملفات تطبيق بايثون اسأل PDF للقيام بهذه الخطوة يجب أن تكون بيئة بايثون مثبتة على جهازك، ولإنشاء مشروع بايثون جديد، افتح سطر الأوامر (أو الطرفية) في نظام التشغيل وانتقل للمسار المطلوب، وأنشئ مجلدًا جديدًا للمشروع وليكن باسم askpdf، ثم أنشئ ضمنه ملفين الأول ask_pdf.py لإضافة كود الواجهة الرسومية للتطبيق، والثاني ملف ‎.env لإضافة مفتاح الواجهة الرسومية OpenAI API Key اللازمة لعمل التطبيق من خلال كتابة التعليمات التالية تباعًا في سطر الأوامر: C:\Users\PC>d: D:\>md askpdf D:\>cd askpdf D:\askpdf>touch ask_pdf.py Touching ask_pdf.py D:\askpdf>touch .env Touching .env D:\askpdf> تثبيت المكتبات اللازمة لعمل التطبيق يحتاج التطبيق إلى تثبيت العديد من مكتبات ووحدات بايثون الخارجية باستخدام مدير الحزم pip وهي كالتالي: pip install python-dotenv PyPDF2 langchain langchain-openai langchain-community faiss-cpu إليك وصفًا موجزًا لكل مكتبة ولماذا استخدمناها في التطبيق: المكتبة python-dotenv لقراءة قيمة المفتاح الذي سأخزنه في الملف env. المكتبة PyPDF2 ضرورية للتعامل مع ملف PDF وقراءته محتوياته. إطار عمل LangChain ليسهل التعامل مع نموذج text-embedding-ada-002 لتقسيم النصوص، وإنشاء تضمينات وفهارس لها، كما أنه يدعم نموذج GPT-3.5 Turbo Instruct لتوليد إجابات على الأسئلة المطروحة. سنستدعي مكتبات langchain و langchain-openai و langchain-community من هذا الإطار وهي مكتبات مفيدة تتكامل مع نماذج OpenAI وتوسع إمكانياتها. وأخيرًا سنستخدم مكتبة Faiss وهي مكتبة بايثون يوفرها إطار LangChain تسرع عملية البحث عن التشابه في البيانات النصية دون الحاجة لاستهلاك الكثير من الموارد. كتابة الكود البرمجي للتطبيق الخطوة التالية هي إضافة الكود الخاص بالتطبيق، سأفتح الآن مجلد المشروع بمحرر الأكواد VSCode وأكتب بدايةً في ملف إعدادات التطبيق ‎.env قيمة مفتاح الواجهة البرمجية على النحو الآتي: OPENAI_API_KEY= Your_KEY وعليك بالطبع استبدال Your_KEY في الكود السابق بالقيمة الفعلية لمفتاحك ليعمل التطبيق بشكل صحيح. الآن سننتقل للملف الأساسي للتطبيق ask_pdf.py ونستورد بدايةً كافة المكتبات والوظائف اللازمة لعمل التطبيق. ويوضح التعليق ضمن الكود دور كل منها على النحو الآتي: import os from dotenv import load_dotenv # مكتبات إنشاء الواجهة الرسومية import tkinter as tk from tkinter import filedialog, scrolledtext, ttk from tkinter import font from PIL import Image, ImageTk # قراءة ومعالجة ملفات PDF from PyPDF2 import PdfReader # الواجهة البرمجية OpenAI from langchain_openai import OpenAI from langchain_community.vectorstores import FAISS from langchain_community.callbacks import get_openai_callback from langchain.text_splitter import CharacterTextSplitter from langchain.chains.question_answering import load_qa_chain from langchain_openai import OpenAIEmbeddings بعدها سننشئ الصنف الأساسي للتطبيق باسم askpdfgui ونعرّف بداخله التابع __init__ لإضافة وتهيئة عناصر الواجهة الرسومية Widgets للتطبيق، ستتضمن الواجهة عناصر متعددة مثل حاويات العناوين Labels وشريط تقدم Progressbar وزِرَّين Buttons الأول لتحميل ملف PDF من الجهاز لذاكرة التطبيق ومعالجته، والثاني للبحث عن إجابة السؤال الذي يكتبه المستخدم ولا يكون هذا الزر مُفعّلًًا إلا بعد اكتمال تحميل ومعالجة الملف. class askpdfgui: def __init__(self, root): self.root = root self.root.title("اسأل PDF") self.root.geometry("800x700") custom_font = font.Font(family="Verdana", size=24, weight="bold") self.label = tk.Label(root, text="? PDF اسأل", font=custom_font) self.label.pack(pady=10) # عرض تقدم حالة تحميل الملف self.progress_label = ttk.Label(root, text="", foreground="green") self.progress_label.pack(pady=5) self.progress_bar = ttk.Progressbar(root, orient="horizontal") self.progress_bar.pack(pady=5) script_dir = os.path.dirname(os.path.abspath(__file__)) image_path = os.path.join(script_dir, "robopdf.png") image = Image.open(image_path) tk_image = ImageTk.PhotoImage(image) self.image_label = tk.Label(root, image=tk_image) self.image_label.image = tk_image self.image_label.pack(pady=10) # إضافة زر تحميل الملف self.upload_button = tk.Button( root, text="حمل ملفك هنا", command=self.upload_pdf ) self.upload_button.pack(pady=10) # حقل نصي لكتابة السؤال self.question_entry = tk.Entry(root, width=60, justify="center") self.question_entry.pack(pady=10) # زر طرح السؤال وإلغاء تفعيله لحين اكتمال معالجة الملف self.ask_button = tk.Button( root, text="اطرح السؤال", command=self.ask_question, state=tk.DISABLED ) self.ask_button.pack(pady=10) # عرض نتيجة السؤال self.result_text = tk.Text(root, wrap=tk.WORD, width=60, height=10) self.result_text.configure(font=("TkDefaultFont", 12)) self.result_text.pack(pady=10, anchor=tk.CENTER) سيكون شكل التطبيق على النحو التالي: الآن وبعد أن انتيهنا من تصميم واجهة التطبيق وتهيئة خصائص عناصر واجهة المستخدم سنبرمج وظائفه، بداية نحقق كود الدالة upload_pdf التي ستمكن المستخدم من فتح نافذة لتحميل ملف PDF الخاص به لداخل التطبيق كما يلي: def upload_pdf(self): file_path = filedialog.askopenfilename(filetypes=[("PDF Files", "*.pdf")]) if file_path: self.process_pdf(file_path) بعد أن يختار المستخدم ملف PDF يستدعى التابع process_pdf الذي يأخذ كوسيط مسار هذا الملف لبدء عملية المعالجة ويستخرج النصوص منه باستخدام الدالة extract_text، ويقسمه إلى أجزاء أو مقاطع أو نصوص بطول 1000 حرف مع تداخل بقيمة 200 حرف بين الأجزاء كي لا يفقد السياق، بمعنى سيكون الجزء الأول من البداية إلى الحرف 1000 أما الجزء الثاني فسيبدأ من الحرف 801 لضمان استمرارية المعنى وعدم فقدان معلومات مهمة بين النصوص المتجاورة. هناك عدة تقنيات مختلفة متاحة لتنفيذ مهمة التضمين والفهرسة في تطبيقنا، وقد اعتمدنا في هذا هذا التطبيق على الصنف OpenAIEmbeddings من مكتبة langchain_openai ومررنا قيمة مفتاح الواجهة البرمجية openai_api_key لباني الصنف لأنه سيستخدم هنا النموذج Embading الذي توفره هذه الواجهة لتكوين التضمينات الرقمية وإنشاء المتجهات الخاصة بها بالاستعانة بمكتبة FAISS على النحو التالي: def process_pdf(self, file_path): self.progress_label.config(text="انتظر اكتمال تحميل ملف PDF...") self.progress_bar.start() self.root.update_idletasks() pdf_reader = PdfReader(file_path) text = "" for page in pdf_reader.pages: text += page.extract_text() # تقسيم نص الملف إلى أجزاء text_splitter = CharacterTextSplitter( separator="\n", chunk_size=1000, chunk_overlap=200, length_function=len ) chunks = text_splitter.split_text(text) # إنشاء تضمينات النصوص embeddings = OpenAIEmbeddings(api_key=openai_api_key) knowledge_base = FAISS.from_texts(chunks, embeddings) self.docs = knowledge_base.similarity_search("") بالنسبة للسطر الأخير في الكود: self.docs = knowledge_base.similarity_search("") سينفذ هذا السطر عملية بحث عن التشابه ويجد أقرب الجمل لسؤالك الذي ستطرحه باستخدام قاعدة بيانات المتجهات knowledge_base ويخزن النتيجة في متغير docs فهذا المتغير يخزن معلومات حول النصوص المتشابهة ودرجة التشابه بين النص أو السؤال الذي تبحث عنه والنص المخزن في قاعدة البيانات، لكن هنا لم نكتب السؤال بعد لذا نخزن نتائج البحث باعتبار السؤال فارغ، لاحقًا عندما ستتم عملية البحث الفعلية سيحدد النصوص المشابهة بشكل أفضل ويحسن عملية البحث. إليك مثالًا بسيطًا لبنية هذا المتغير، إذا كان السؤال الذي كتبه المستخدم هو "ما فائدة تعلم الذكاء الاصطناعي في تحسين مهارات التطوير" فسوف يحتوي المتغير عندها على قائمة من النصوص التي حصل عليها من الملف مع درجة التشابه مع السؤال المطروح على نحو مشابه للبنية التالية: self.docs = [ {"text": "تعلم لغات البرمجة يساعد في تطوير مهارات البرمجة", "similarity_score": 0.85}, {"text": "تعلم الذكاء الاصطناعي يحسن من تجربة المستخدم", "similarity_score": 0.92}, # ... وهكذا تأخذ هذه العملية بعض الوقت لقراءة الملف بالكامل ومعالجته بالاعتماد على حجم الملف وبعد اكتمال تحميل الملف وتحويله لتضمينات وفهرسته بالشكل المطلوب، سيُفعّل زر طرح السؤال إذ يمكن للمستخدم الآن كتابة أي سؤال في الحقل النصي question_entry للإجابة عليه بناءً على المحتوى الذي أنشأه البرنامج. self.progress_label.config(text="اكتملت عملية التحميل يمكنك طرح السؤال الآن") self.progress_bar.stop() self.progress_bar["value"] = 100 # تمكين زر طرح السؤال self.ask_button["state"] = tk.NORMAL الخطوة التالية والأخيرة هي تعريف التابع ask_question وهو أهم تابع في تطبيقنا وسنستدعيه بعد كتابة السؤال والنقر على زر طرح السؤال، ليبدأ بالبحث عن إجابة مناسبة على السؤال من محتوى المتغير doc ويرسل الطلب لواجهة OpenAI لتقديم إجابة مناسبة. def ask_question(self): user_question = self.question_entry.get() if user_question: llm = OpenAI(model="gpt-3.5-turbo-instruct", api_key=openai_api_key, temperature=0.0) chain = load_qa_chain(llm, chain_type="stuff") with get_openai_callback(): response = chain.run(input_documents=self.docs, question=user_question) print(response) # عرض الإجابة على السؤال self.result_text.tag_configure("center", justify="center") # مسح أي نص قديم إن وجد self.result_text.delete("1.0", tk.END) self.result_text.insert(tk.END, response, "center") كما هو موضح في الكود أعلاه سيحصل التابع على نص السؤال الذي أدخله المستخدم في الحقل النصي question_entry ثم يخزنه في المتغير user_question ويمرر مدخلات المستخدم إلى النموذج gpt-3.5-turbo-instruct لتوليد الإجابة، وهنا نحتاج كذلك لإدخال قيمة المفتاح openai_api_key لإنشاء اتصال مع واجهة برمجة تطبيقات OpenAI ولتخصيص أي وسطاء نحتاجها لتخصيص إجابة النموذج وأهم هذه الوسطاء temperature الذي يحدد درجة دقة السؤال. ويفضل في حالة تطبيقنا أن تبقى قيمته صفر للحصول على إجابات منطقية وحتمية تلتزم بمحتوى الملف، وعدم ترك النموذج يبدع ويؤلف إجابات لا صلة لها بالسياق وسنوضح المزيد عنه لاحقًا عن تجربة تنفيذ التطبيق. بعدها نستدعي الدالة load_qa_chain التي تنشئ سلسلةً نصيةً من نوع stuff لترسلها كمطالبة إلى الواجهة البرمجية OpenAI التي تفهم بدورها الطلب الذي أرسلناه وتعيد لنا الإجابة المناسبة response. ملاحظة: يحدد نوع السلسلة chain_type طريقة تمرير المستندات إلى النموذج اللغوي الكبير LLM وهنا مررنا القيمة ليكون stuff التي ترسل جميع المستندات في طلب واحد للواجهة البرمجية وهي تناسب حالة التعامل مع مستند واحد أو عدة مستندات صغيرة الحجم وتتضمن نفس طبيعة المحتوى، وهناك طرق أخرى هي Map-reduce تفيد في حال التعامل مع مستندات كبيرة هي تلخص كل مستند على حدا ثم تجمع التلخيصات في ملخص نهائي. أخيرًا نكتب كود تشغيل التطبيق كبرنامج رئيسي من خلال تحميل بيانات مفتاح OpenAI API من ملف ‎.‎env وفحص فيما إذا كان المفتاح قد تم تعيينه أم لا، والبدء بإنشاء واجهة المستخدم الرسومية وتشغيل التطبيق حتى يتم إغلاق النافذة. if __name__ == "__main__": load_dotenv() openai_api_key = os.getenv("OPENAI_API_KEY") if openai_api_key is None: print( "OpenAI API تأكد من تعيين قيمة مفتاح" ) else: root = tk.Tk() app = askpdfgui(root) root.mainloop() تجربة عمل التطبيق لننفذ الآن كود التطبيق ونختبر آلية عمله: انتقل لمسار وجود التطبيق واكتب الأمر python ask_pdf.py في سطر الأوامر أو الطرفية لتشغيله، ستظهر لك الواجهة الأولية ولتجربة التطبيق سأنقر على زر "حمل ملفك هنا" يمكن تحميل أي ملف pdf خاص بك في التطبيق، وفي حالتي سأختار ملف يتحدث عن لغات برمجة الذكاء الاصطناعي كما يلي: بعد اكتمال عملية التحميل سأطرح السؤال التالي: "ما هي لغات برمجة الذكاء الاصطناعي؟" وسأحصل على الإجابة كما هو موضح في الصورة التالية: كما يمكن طرح أي أسئلة أخرى حول الملف مثل اسم الكاتب أو تاريخ نشره وسيتمكن من الإجابة كما توضح الصورة أدناه: الآن لو طرحت سؤالًا لا علاقة له بمحتوى هذا الملف مثل "من اخترع المصباح الكهربائي" ستكون النتيجة كما يلي: تأثير الوسيط Temperature على دقة نتائج التطبيق يستخدم المعامل أو الوسيط temperature كما وضحت سابقًا للتحكم في دقة أو عشوائية النتائج التي تعيدها نماذج واجهة برمجة تطبيقات OpenAI وتتراوح قيمة هذا الوسيط بين 0 و 2، فعند ضبط درجة الدقة على قيم قريبة من الصفر ستحصل على نتائج واقعية وذات صلة بالطلب وحين تضبطه على قيم قريبة من 2 ستحصل على نتائج أكثر إبداعًا لكنها قد تكون غير دقيقة ولا صلة لها بالسؤال. لذا عليك تعديل قيمة هذا المعامل بما يتناسب مع طبيعة تطبيقك، وهل يحتاج للدقة كما في حال تطبيقنا الذي يبحث عن النتائج الموافقة للسؤال، أم يسمح فيه الإبداع والابتكار كتطبيقات تأليف القصص والروايات. سأجرب على سبيل المثال تغير درجة الحرارة في الكود السابق وجعلها مثلًا 1.5 وأحمّل الملف نفسه واطرح سؤال "من هو أديسون؟" مثلًا ستكون النتيجة غير منطقية كما يلي! أما عند إعادة ضبطه بالقيمة 0 فسيجيب التطبيق بأنه لا يملك معلومات عن أديسون وفق الملف الذي وفرته له. قيود على عدد الرموز Tokens في التطبيق يمكنك تجربة التطبيق على أي ملف pdf خاص بك، لكن تملك نماذج من OpenAI API حدًا أقصى لعدد الرموز Token Limits التي تمثل إجمالي طول المدخلات التي تستقبلها والنتائج التي تولدها وفي حالة النموذج gpt-3.5-turbo-instruct المستخدم في الكود فالعدد الأقصى هو 4096 رمزًا، وبالتالي قد تحصل على الخطأ التالي عند تحميل ملفات كبيرة وتجاوز إجمالي الرموز في دخل وخرج النموذج هذا العدد، ومن الإجراءات الممكنة لتجاوز هذه القيود، تحديد قيمة المعامل max_tokens للنموذج كي تحدد له طول الرد الذي سينتج وتمنعه من إنشاء محتوى طويل جدًا أو تقصير نص الطلب أو السؤال قدر المستطاع. كما تحدد بعض النماذج سرعة معالجة الرموز في الدقيقة الواحدة، فالحد الأقصى المسموح به للتضمينات في نموذج text-embedding-ada-002 هو 15000 رمز في الدقيقة، وبالتالي إذا استخدمت ملفات pdf كبيرة تتجاوز هذه الحدود فسوف تحصل على كذلك أخطاء في التنفيذ بسبب القيود التي تفرضها الواجهة البرمجية ولحلها عليك رفع خطة الاشتراك في الواجهة البرمجية. الخلاصة بهذا نكون قد وصلنا لنهاية مقالنا، الذي شرحنا فيه خطوات إنشاء تطبيق بايثون يستفيد من واجهة برمجة تطبيقات OpenAI لتحميل ملف PDF وطرح أسئلة حوله وتوليد إجابات عليها من محتوى الملف، وهناك بالطبع الكثير من الأفكار الأخرى التي يمكنك الاستفادة منها من النماذج اللغوية الكبيرة LLMs في تطوير تطبيقات متقدمة في مجال الذكاء الاصطناعي، أطلق العنان لإبداعك واستفد من قوة هذه النماذج وإمكانياتها الهائلة. اقرأ أيضًا تطوير تطبيق "وصفة" لاقتراح الوجبات باستخدام ChatGPT و DALL-E في PHP تطوير تطبيق "اختبرني" باستخدام ChatGPT ولغة جافاسكربت مع Node.js إعداد بيئة العمل للمشاريع مع بايثون اكتشاف دلالات الأخطاء في شيفرات لغة بايثون كود التطبيق ask_pdf.zip
×
×
  • أضف...