مشكلة واجهة ثنائية هشة

تُعرف مشكلة واجهة البرمجة الثنائية الهشة ( FBI) بأنها عيب في بعض مُجمّعات لغات البرمجة الكائنية ، حيث قد تؤدي التغييرات الداخلية في مكتبة الفئات الأساسية إلى توقف المكتبات أو البرامج التابعة لها عن العمل. وهي مثال على هشاشة البرمجيات .

تُسمى هذه المشكلة في أغلب الأحيان مشكلة الفئة الأساسية الهشة أو FBC ؛ ومع ذلك، فإن لهذا المصطلح معنى أوسع.

سبب

تحدث المشكلة بسبب "اختصار" يستخدم مع المترجمات للعديد من لغات البرمجة الكائنية الشائعة، وهي ميزة تصميم تم الاحتفاظ بها عندما كانت لغات البرمجة الكائنية تتطور من لغات البرمجة الهيكلية غير الكائنية السابقة مثل C و Pascal .

في هذه اللغات، لم تكن هناك كائنات بالمعنى الحديث، ولكن كان هناك بناء مشابه يُعرف باسم السجل (أو "struct" في لغة C) الذي يحتوي على مجموعة متنوعة من المعلومات ذات الصلة في جزء واحد من الذاكرة. يتم الوصول إلى الأجزاء داخل سجل معين من خلال تتبع موقع بداية السجل، ومعرفة الإزاحة من نقطة البداية إلى الجزء المطلوب. على سبيل المثال، قد يحتوي سجل "شخص" على اسم أول، واسم عائلة، وحرف من الاسم الأوسط، وللوصول إلى الحرف الأول، يكتب المبرمج الاسم thisPerson.middleInitialالذي يحوله المترجم إلى شيء مثل a = location(thisPerson) + offset(middleInitial). تتضمن وحدات المعالجة المركزية الحديثة عادةً تعليمات لهذا النوع الشائع من الوصول.

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

أعراض

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

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

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

الحلول

اللغات

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

يتمثل حل آخر في كتابة ملف وسيط يسرد الإزاحات وغيرها من المعلومات من مرحلة التجميع، والمعروفة باسم البيانات الوصفية. ثم يستخدم الرابط هذه المعلومات لتصحيح نفسه عند تحميل المكتبة في التطبيق. وتفعل منصات مثل .NET ذلك.

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

يوفر Objective-C 2.0 متغيرات مثيل غير هشة من خلال وجود مستوى إضافي من التوجيه غير المباشر للوصول إلى متغيرات المثيل.

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

الروابط

يتطلب حل آخر مُرَبطًا أكثر ذكاءً. في الإصدار الأصلي من Objective-C ، سمح تنسيق المكتبة بوجود إصدارات متعددة من مكتبة واحدة، وتضمن بعض الوظائف لاختيار المكتبة المناسبة عند استدعائها. مع ذلك، لم تكن هذه الوظيفة ضرورية دائمًا، لأن الإزاحات كانت مطلوبة فقط للحقول، حيث كانت إزاحات الدوال تُجمع أثناء التشغيل، وبالتالي لا تُسبب أخطاءً في التحقق من صحة البيانات. ولأن الدوال تميل إلى التغيير بوتيرة أسرع من الحقول، لم تكن Objective-C تعاني من مشاكل كثيرة في التحقق من صحة البيانات، وكان بالإمكان تصحيح المشاكل التي ظهرت باستخدام نظام الترقيم. أضاف الإصدار 2.0 من Objective-C "وقت تشغيل حديثًا" حلّ مشكلة التحقق من صحة البيانات للحقول أيضًا. بالإضافة إلى ذلك، تستخدم لغة TOM إزاحات مُجمّعة أثناء التشغيل لكل شيء، مما يجعل التحقق من صحة البيانات مستحيلاً.

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

بنيان

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

طريقة التوزيع

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

انظر أيضاً