إزالة دالة مهملة من قاعدة بيانات برمجية هي، نظريًا، من أبسط الأمور التي يمكن للمطور القيام بها. يكفي حذف تعريفها، والتأكد من عدم استخدامها، ثم حفظ التغييرات. لكن عمليًا، بالنسبة لأي دالة مضى عليها وقت كافٍ لتُعتبر مهملة، تكمن المشكلة في خطوة "التأكد من عدم استخدامها". فقد تُستدعى الدالة من كود كُتب منذ سنوات من قِبل شخص لم يعد ضمن الفريق، أو من مستودع لا يتلقى تغييرات إلا نادرًا، أو من لغة أو إطار عمل لا يملكه الفريق الحالي. وقد تُستدعى بشكل غير مباشر عبر غلاف برمجي، أو من خلال الانعكاس، أو عبر آلية إرسال وقت التشغيل التي لا تظهر في أي مخطط استدعاء ثابت. وقد يُشار إليها في الكود المُولّد، أو في هياكل الاختبار، أو في ملف تكوين يُفعّلها بالاسم. قد لا يملك المطور الذي يُعلّمها بأنها مهملة، ولا المطور الذي يُزيلها لاحقًا، أي وسيلة لمعرفة كل هذا دون أداة قادرة على إنشاء جرد كامل للدوال المُستدعِية عبر المستودعات.
ابحث عن جميع المتصلين قبل حذف أي شيء.
SMART TS XL يقوم بإنشاء رسم بياني لاستدعاءات اللغات المتعددة يحدد كل مستدعي لأي دالة قبل إجراء أي تغيير.
اضغط هناإن تكلفة هذا الخطأ فورية وملموسة. فإزالة دالة دون اكتشاف كامل للمستدعي تُسبب أعطالًا أثناء التشغيل في الأنظمة التي لا تزال تعتمد عليها. في نظام متجانس ذي نشر واحد، يكون نطاق الأعطال محدودًا. أما في نظام موزع ذي خدمات متعددة، كل منها منشور بشكل مستقل، فتتفاقم الأعطال: تُحدَّث الخدمة التي وفرت الدالة، بينما لا تُحدَّث الخدمات المُستخدِمة لها، ويظهر العطل أثناء التشغيل في بيئة الإنتاج في أنظمة قد تكون مملوكة لفرق مختلفة. في بيئة حاسوب مركزي حيث تستدعي برامج كوبول فقرات الأدوات المساعدة المشتركة بالاسم، قد لا يظهر العطل إلا عند تشغيل مهمة دفعية محددة، والتي قد تكون أسبوعية أو شهرية، مما يجعل المرجع المفقود غير مرئي خلال دورات الاختبار العادية. وكما هو موضح في السياق الأوسع لإدارة التعليمات البرمجية المهملة ، تتضاعف المخاطر بمرور الوقت: فالتعليمات البرمجية المهملة التي تُزال بشكل غير كامل أكثر خطورة من التعليمات البرمجية المهملة التي تُترك، لأن الإزالة تُوهم بالاكتمال بينما تستمر المستدعون المتبقون في العمل على تعريف لم يعد موجودًا.
هذه المقالة هي دليل عملي لاكتشاف المتصلين قبل إزالة الوظيفة: ما الذي يتطلبه جرد كامل للمتصلين، ولماذا تكون الأدوات التي يلجأ إليها المطورون أولاً غير كافية من الناحية الهيكلية، وكيف تتطلب أنواع مختلفة من علاقات الاستدعاء أساليب تحليل مختلفة، وكيف يبدو تعداد المتصلين الحقيقي عبر الأنظمة في قواعد التعليمات البرمجية على نطاق المؤسسة التي تمزج بين اللغات والمنصات والمستودعات.
لماذا يُعدّ اكتشاف هوية المتصل أصعب مما يبدو؟
يُعدّ اكتشاف الدوال المُستدعِية على المستوى السطحي مألوفًا لدى جميع المطورين: انقر بزر الفأرة الأيمن على اسم دالة في بيئة التطوير المتكاملة، ثم اختر "البحث عن جميع المراجع" أو "إظهار تسلسل الاستدعاءات"، واستعرض النتائج. يعمل هذا بشكل موثوق ضمن نطاق مشروع واحد مُحمّل في نسخة واحدة من بيئة التطوير المتكاملة. ولكن بمجرد أن يتجاوز حجم قاعدة التعليمات البرمجية هذا النطاق، تصبح النتائج غير مكتملة بطرق غير ظاهرة في المخرجات. لا تُشير بيئة التطوير المتكاملة إلى الدوال المُستدعِية التي لم تعثر عليها لأنها لم تُفهرس المستودعات التي تحتوي عليها. يرى المطور مجموعة نتائج تبدو كاملة، ويتابع عمله بناءً على ذلك.
تكمن المشكلة الهيكلية في اكتشاف الدوال المستدعِية على نطاق واسع في أن الأدوات التي يستخدمها المطورون بسلاسة محدودة بنطاق فهرسة خاصتها، وفي الأنظمة الكبيرة والموزعة ومتعددة اللغات، لا يغطي هذا النطاق سوى جزء ضئيل من المواضع التي قد تُستدعى فيها دالة معينة. وتتناسب ثقة المطور في اكتمال النتائج عكسيًا مع اكتمال البحث الفعلي. في قاعدة بيانات صغيرة أحادية اللغة، يكون تسلسل استدعاءات بيئة التطوير المتكاملة (IDE) موثوقًا به حقًا. أما في نظام مؤسسي يمتد عبر مستودعات ولغات وبيئات نشر متعددة، فإنه مضلل بشكل منهجي. وكما تم تحليله في سياق إنتروبيا الكود ومخاطر إعادة هيكلته ، قد تعتمد الوحدات القديمة على واجهات مهملة، بينما لا تزال الخدمات الأحدث تستدعي إجراءات مصممة في الأصل لبيئات سابقة، وهذه العلاقات بين استدعاءات الأنظمة المختلفة هي تحديدًا ما لا يستطيع البحث المحدود ببيئة التطوير المتكاملة (IDE) رؤيته.
يتطلب فهم الأسباب المحددة لفشل اكتشاف المتصل فحص كل نوع رئيسي من أنواع المكالمات: المكالمات المباشرة، والمكالمات غير المباشرة، والاستدعاءات عبر اللغات، والتوزيع الديناميكي. يفشل كل نوع لأسباب مختلفة ويتطلب تقنيات تحليل مختلفة لحله بشكل صحيح.
المكالمات المباشرة عبر حدود المستودع
الاستدعاءات المباشرة هي أبسط أنواع الاستدعاءات: حيث تستدعي دالةٌ أخرى صراحةً بالاسم. ضمن مستودع واحد، تتعامل بيئات التطوير المتكاملة (IDEs) مع هذه الاستدعاءات بكفاءة. أما عند تجاوز حدود المستودعات، فيفشل التحليل لأن فهرسة بيئة التطوير المتكاملة لا تشمل هذه الحدود. فإذا عرّف المستودع (أ) دالة مساعدة مشتركة، وقامت المستودعات (ب) و(ج) و(د) باستيرادها واستدعائها، فإن بيئة التطوير المتكاملة لأي من هذه المستودعات لا ترى إلا الاستدعاءات ضمن نطاقها المفهرس.
يُعدّ نمط استدعاء المستودعات المتعددة هذا هو القاعدة وليس الاستثناء في بنى الخدمات المصغّرة، حيث تُنشر المكتبات المشتركة كحزم وتُستخدم عبر عشرات الخدمات. يحتاج مسؤول المكتبة الذي يُهمل وظيفةً ما في الحزمة المشتركة إلى معرفة الخدمات المُستهلكة التي لا تزال تستدعيها. لا يعرف مُحرّر الحزم (IDE) الخاص به شيئًا عن هذه الخدمات. يعرف مدير الحزم الخدمات التي تعتمد على الحزمة، لكنه لا يعرف الوظيفة المُحدّدة التي تستدعيها كل خدمة داخل الحزمة. يتطلّب الربط بين "هذه الخدمة تستخدم الإصدار X من هذه الحزمة" و"هذه الخدمة تستدعي هذه الوظيفة المُهملة تحديدًا" فهرسة شفرة المصدر لكل خدمة مُستهلكة وربط الاستدعاء بتعريف الوظيفة المُحدّدة.
المكالمات غير المباشرة: الأغلفة، والمفوضون، والواجهات
قد لا يتمكن مستخدمو الدالة من استدعائها مباشرةً. بل قد يتم استدعاؤها عبر دالة وسيطة توفر تسجيلًا إضافيًا، أو معالجة للأخطاء، أو تحويلًا للمعاملات. كما يمكن إسنادها إلى مفوض أو مؤشر دالة واستدعاؤها من خلال المفوض. وقد يتم تسجيلها في سجل الخدمات أو إطار عمل الإضافات واستدعاؤها بالاسم عبر آلية إرسال. في كل هذه الحالات، يُظهر البحث المباشر عن استدعاءات الدالة المهملة نتائج غير مكتملة، لأن المستدعين الفعليين يستدعون الدالة الوسيطة أو المُرسِل، وليس الدالة المهملة نفسها.
يُعدّ الاستدعاء عبر التغليف شائعًا بشكل خاص في قواعد البيانات الكبيرة، حيث تُغلّف وظائف أساسية متعددة الجوانب، مثل التسجيل والتفويض ومنطق إعادة المحاولة، باستخدام أنماط التغليف. في الواقع، يتم استدعاء الدالة المهملة المُغلّفة بواسطة أداة تسجيل من قِبل كل من يستدعي أداة التسجيل، وليس من قِبل أي كود يحتوي على اسم الدالة المهملة. يتطلب تحديد هؤلاء المستدعين تتبعًا عبر أداة التسجيل: تستدعي أداة التسجيل الدالة المهملة، وبالتالي فإن كل من يستدعي أداة التسجيل هو مستدعي غير مباشر للدالة المهملة. هذا التتبع التكراري لرسم بياني الاستدعاءات هو ما يُميّز جردًا شاملاً للمستدعين عن البحث السطحي في المراجع.
لنأخذ مثالاً نموذجياً بلغة جافا حيث يتم الوصول إلى طريقة مهملة من خلال طبقة تفويض:
جافا
// Deprecated in the core service
@Deprecated
public BillingResult calculateLegacyFee(Account account) {
// original implementation
}
// Facade that delegates; not visible as a direct caller in a simple reference search
public BillingResult computeFee(Account account) {
return calculateLegacyFee(account); // indirect caller
}
// Actual consumer; calls computeFee, unaware of the underlying deprecated method
public void processMonthlyBilling(List<Account> accounts) {
accounts.forEach(a -> computeFee(a)); // two hops from the deprecated function
}
بحث "العثور على جميع المراجع" لـ calculateLegacyFee عائدات computeFee باعتباره المتصل الوحيد. لا يعود. processMonthlyBillingوهو المستهلك الحقيقي للسلوك المهمل. يتطلب جرد المتصلين الكامل اجتياز مخطط المكالمات من خلال computeFee لتحديد كل مسار يستدعي في النهاية الطريقة المهملة.
أدعية متعددة اللغات
تُعدّ استدعاءات اللغات المختلفة الفئة التي تفشل فيها أدوات اكتشاف المُستدعي القياسية فشلاً ذريعاً. فعندما تستدعي خدمة جافا برنامج كوبول بالاسم عبر طبقة وسيطة، أو عندما يستدعي نص برمجي بايثون إجراءً مخزناً يغلّف دالة مهملة، أو عندما تستدعي مهمة JCL برنامجاً باسمه PROGNAME يستدعي داخلياً فقرة مهملة، لا تظهر أي من هذه العلاقات في مخطط استدعاءات أي لغة منفردة. إذ لا ترى أدوات كل لغة سوى جانبها الخاص من الاستدعاء.
في بيئات الحواسيب المركزية، تُعدّ استدعاءات اللغات المتعددة هيكلية وشاملة. يُحدد تدفق مهام JCL اسم برنامج COBOL الذي يُنفّذه. يستدعي برنامج COBOL الفقرات والبرامج الفرعية بالاسم. تُشارك فقرات الأدوات المساعدة المُعرّفة في مكتبات النسخ بين العديد من البرامج. عند إيقاف فقرة في مكتبة نسخ، يتطلب العثور على جميع المُستدعين فهم علاقات استدعاء COBOL (أي البرامج التي تتضمن مكتبة النسخ والتي تستدعي الفقرة)، وعلاقات استدعاء JCL (أي المهام التي تستدعي تلك البرامج)، وأي واجهات لغات متعددة (مثل Java أو SQL التي تتفاعل مع تلك البرامج). لا توجد أداة واحدة تُغطي جميع هذه العلاقات. كما هو موضح في تحليل التحليل الثابت على الأنظمة القديمة ، لا تستطيع أدوات التحليل الثابت المُصممة للبيئات الحديثة رؤية الصورة الكاملة لكيفية تشغيل البرامج القديمة واستدعائها وترابطها عندما تمتد علاقات الاستدعاء عبر JCL وCOBOL وواجهات الأنظمة المتعددة في آنٍ واحد.
الإرسال والانعكاس الديناميكي
يستدعي بعض المستخدمين دالة ليس باسمها الحرفي في الكود المصدري، ولكن من خلال آلية تحل الدالة في وقت التشغيل: الانعكاس في جافا أو .NET، getattr/__call__ في بايثون، الربط المتأخر في كوبول من خلال CALL identifierيتم ذلك عبر الإرسال الديناميكي من خلال تعدد الأشكال، أو الاستدعاء بواسطة سلسلة نصية في أطر عمل المكونات الإضافية والأنظمة المعتمدة على التكوين. لا تحتوي هذه الدوال المستدعِية على اسم الدالة المهملة بأي شكل يمكن للتحليل الثابت اكتشافه بشكل موثوق.
ملف تهيئة يُحدد اسم دالة كسلسلة نصية، ويُحمّل أثناء التشغيل ويُستخدم لاستدعاء الدالة عبر الانعكاس، هو مُستدعي لا يظهر في أي تحليل لشفرة المصدر. إطار عمل إضافات يكتشف ويستدعي المعالجات المُسجلة عبر الواجهة هو مُستدعي يظهر في مخطط الاستدعاءات فقط كاستدعاء لآلية الإرسال، وليس كاستدعاء لأي معالج مُحدد. يتطلب تحديد هؤلاء المُستدعين مزيجًا من التحليل الثابت للعثور على أنماط الإرسال الديناميكية، وتتبع وقت التشغيل لمراقبة الاستدعاءات الفعلية، والفحص اليدوي لمنطق التهيئة والتسجيل الذي يُحدد الدوال التي يتم إرسالها. كما نوقش في دراسة التحليل الثابت على الشفرة المُشفرة والمُولدة ، عندما لا تُعبر مسارات التنفيذ مباشرةً في شفرة المصدر، يجب على التحليل الثابت إعادة بناء المسارات المُحتملة من الأنماط الهيكلية بدلاً من المراجع النصية المباشرة، وتتطلب عمليات إعادة البناء هذه تحليلًا مُدركًا للغة لآلية الإرسال نفسها.
الأدوات التي يلجأ إليها المطورون أولاً، وأين يتوقفون؟
هناك تسلسل متوقع للأدوات التي يستخدمها المطورون عند محاولة اكتشاف المتصل، ونقطة متوقعة بنفس القدر يتوقف عندها كل منها عن تقديم نتائج موثوقة. يُعد فهم هذا التسلسل مهمًا لأن مخرجات كل أداة تبدو كاملة حتى وإن لم تكن كذلك.
التسلسل الهرمي لاستدعاءات بيئة التطوير المتكاملة: موثوقية ضمن مشروع واحد
تُعدّ ميزات تسلسل استدعاءات الدوال في بيئات التطوير المتكاملة (IDE) الخطوة الأولى الأنسب. توفر كل من IntelliJ IDEA وVisual Studio وVS Code وEclipse نوعًا من "البحث عن جميع الدوال المستدعِية" أو "عرض تسلسل الاستدعاءات" الذي يُحصي الدوال المستدعِية لدالة مُحددة بشكل متكرر، ضمن النطاق المُفهرس للمشروع أو مساحة العمل الحالية. بالنسبة لدالة تُستخدم حصريًا في مستودع واحد ولغة واحدة، تُعدّ هذه الميزات دقيقة وكافية.
يُحدد نطاق البحث بوضوح ضمن نطاق الفهرسة. أما المستخدمون في مستودعات أخرى، أو في بيئات تشغيل لغات أخرى، أو في الخدمات التي تعتمد على هذا الكود عبر مدير الحزم بدلاً من مرجع المشروع المباشر، فهم خارج هذا النطاق. لا يُشير مُحرر الحزم المتكامل (IDE) إلى ما لم يتم البحث فيه. يتلقى المطور مجموعة نتائج دون أن يكون لديه أي فكرة عن عدد المستودعات الإضافية غير المفهرسة، أو عدد المستودعات التي تستخدم الدالة المهملة، أو ما إذا كانت نتيجة "صفر مستخدمين" تعني فعلاً صفر مستخدمين، أو "صفر مستخدمين ضمن جزء النظام الذي يمكن لهذه الأداة رؤيته".
البحث باستخدام grep والبحث النصي: واسع النطاق ولكنه يفتقر إلى البنية.
عندما يُشتبه في أن بحث بيئة التطوير المتكاملة (IDE) غير مكتمل، تكون الخطوة التالية عادةً هي البحث النصي: استخدام أمر grep في جميع أدلة المصدر المتاحة، أو البحث في المنصة عبر البحث في كود GitHub أو GitLab. يُوسّع هذا نطاق البحث بشكل كبير، ويجد الدوال المُستدعِية في مستودعات أخرى إذا كانت هذه المستودعات متاحة. تكمن المشكلة الهيكلية في أن البحث النصي يعثر على السلاسل النصية، وليس على الاستدعاءات. فهو يُعيد كل ظهور لاسم الدالة، بما في ذلك التعليقات التي تُشير إلى الدالة، وسلاسل التوثيق، ورسائل السجل التي تُسمّي الدالة لأغراض تصحيح الأخطاء، والسلاسل النصية التي تحتوي على اسم الدالة ولكنها لا تستدعيها. كما أنه يُفوّت الدوال المُستدعِية عندما يختلف اسم الدالة عن سلسلة البحث: الدوال المُستدعِية عبر الأسماء المستعارة، أو عبر الأسماء المُطابقة جزئيًا في لغة COBOL حيث قد تكون الأسماء مُختصرة، أو عبر الاستدعاء الديناميكي حيث يتم تجميع الاسم في وقت التشغيل.
تتطلب نتائج البحث النصي تصفية يدوية لتحديد المواقع الفعلية للاتصال، والمراجع التوثيقية، والنتائج الخاطئة الناتجة عن تداخل السلاسل النصية. في الأنظمة الكبيرة، تُعدّ هذه التصفية بحد ذاتها جهدًا كبيرًا، ولا يمكنها التحقق من اكتمال النتائج: فإذا لم يتم رصد مُتصل لاستخدامه اسمًا مختلفًا، فلن تحتوي النتائج المُصفّاة على أي إشارة إلى هذا النقص.
تحذيرات المُترجم و @Deprecated الشروح
توفر اللغات الحديثة ومجموعات الأدوات آليات لتعليقات الإهمال التي تُصدر تحذيرات عند استدعاء الدوال المهملة. جافا @Deprecated إضافة التعليقات التوضيحية مع -Xlint:deprecation يُنتج تحذيرات أثناء الترجمة عند مواقع الاستدعاء. لغة C# [Obsolete] تُصدر السمة تحذيرات أثناء عملية البناء. لا يُنتج اصطلاح Go في تسمية الدوال المهملة وتوثيقها في godoc تحذيرات تلقائيًا. هذه الآليات قيّمة، لكنها محدودة بطريقة محددة: فهي تعمل فقط مع الدوال التي تُجمّع على نفس قاعدة التعليمات البرمجية التي تم فيها وضع علامة الإهمال.
المتصل الذي يستخدم إصدارًا أقدم من المكتبة، يسبق تاريخ @Deprecated لا يتلقى المُستدعي الذي يستخدم ملفًا ثنائيًا بدلًا من التجميع من المصدر أي تحذير. كما لا يتلقى المُستدعي الذي يستخدم لغةً مختلفةً عبر واجهة متعددة اللغات أي تحذير. والأهم من ذلك، أن التحذيرات التي تظهر أثناء التجميع خاصة برؤية المُجمِّع: فهي تُحذِّر من الاستدعاءات التي يراها، وليس من الاستدعاءات في مستودعات أخرى يتم تجميعها بشكل منفصل. إن استخدام تحذيرات المُجمِّع كآلية وحيدة لاكتشاف المُستدعي في نظام متعدد الخدمات يُغفل جميع المُستدعيات التي يتم تجميعها بشكل مستقل، وهو الوضع الطبيعي في بنى الخدمات المصغرة.
أدوات التحليل الثابت: أفضل، ولكن نطاقها محدود
توفر أدوات التحليل الثابت المصممة خصيصًا تعدادًا أكثر دقة للمستدعين مقارنةً ببيئات التطوير المتكاملة (IDEs) للغة المستهدفة، ويمكنها غالبًا تجاوز حدود المستودعات إذا تم تكوينها لفهرسة قواعد بيانات متعددة. فهي تبني مخططات استدعاءات دقيقة بدلًا من الاعتماد على مطابقة النصوص، وتتعامل مع الأسماء المستعارة والاستدعاءات غير المباشرة بكفاءة أعلى من بحث بيئات التطوير المتكاملة، ويمكن تشغيلها في مسارات التكامل المستمر (CI) لاكتشاف المستدعين الجدد عند إضافتهم. إنها الطريقة الأمثل المتاحة حاليًا للغة واحدة.
يكمن القيد في نفس نطاق القيود التي تُحدّ من بيئات التطوير المتكاملة، ولكن على مستوى الأدوات: فأداة التحليل الثابت للغة جافا لا تُفهرس برامج كوبول، ومحلل كوبول لا يُفهرس خدمات جافا، ولا يُفهرس تدفقات مهام JCL. في نظامٍ تكون فيه الدالة المُهملة عبارة عن أداة مساعدة في كوبول تُستدعى من برامج كوبول تُستدعى بدورها من مهام JCL، وتُستهلك بياناتها الناتجة بواسطة خدمات جافا، فإن كل أداة تحليل ثابت لا ترى سوى جزءٍ واحد من علاقة الاستدعاء. وكما تم فحصه في سياق تقنيات إعادة البناء الأساسية ، فإن تحديد جميع مسارات التنفيذ لجزءٍ مُحدد من التعليمات البرمجية، بما في ذلك حالات الخطأ النادرة وفروع التراجع، يتطلب نوعًا من رسم خرائط الاستدعاء الكاملة التي لا تستطيع أدوات اللغة الواحدة إنشاؤها عبر حدود اللغات.
ما يتطلبه جرد المتصلين الكامل فعليًا
إنّ جردًا شاملاً لمسارات استدعاء دالة مهملة في نظام مؤسسي ليس مجرد نتيجة بحث، بل هو تعداد مُهيكل لكل مسار تنفيذي يُمكن من خلاله الوصول إلى الدالة المهملة، بما في ذلك المسارات المباشرة وغير المباشرة والمتعددة اللغات والمُرسلة ديناميكيًا. ويتطلب بناء هذا التعداد إمكانيات عديدة لا توفرها أي أداة قياسية منفردة.
رسم بياني موحد لاستدعاءات الدوال عبر اللغات. يجب أن يشمل هذا الرسم البياني جميع لغات النظام. يجب أن يُمثل كل استدعاء من إجراء JCL إلى برنامج COBOL، واستدعاء من برنامج COBOL إلى فقرة مساعدة مشتركة، واستدعاء من خدمة Java إلى نفس برنامج COBOL عبر واجهة وسيطة، عُقدًا وحوافًا في نفس الرسم البياني. الدالة المهملة هي عُقدة في هذا الرسم البياني، وتعداد المُستدعي هو اجتياز لجميع الحواف الواردة، المباشرة والمتعدية، بغض النظر عن لغة البرمجة التي نشأت منها.
اجتياز متكرر عبر مخطط الاستدعاءات الكامل. المستدعون المباشرون هم الطبقة الأولى فقط. يتطلب جرد كامل تتبع مخطط الاستدعاءات صعودًا عبر المستدعين غير المباشرين، ووظائف التغليف، وطبقات الواجهة حتى يصل الاجتياز إلى وظائف ليس لها مستدعون خاصون بها، وهي نقاط الدخول الحقيقية لسلاسل الاستدعاءات. كل وظيفة في مسار ينتهي بالوظيفة المهملة هي مستدعي بالمعنى ذي الصلة: إزالة الوظيفة المهملة ستؤدي إلى قطع كل مسار يمر بها.
فهرسة شاملة للمستودعات. يجب أن يتضمن مخطط الاستدعاءات شيفرة من كل مستودع قد يستدعي الدالة، بما في ذلك المستودعات التي تعتمد على الدالة من خلال مكتبة أو حزمة مشتركة. يتطلب ذلك فهرسة جميع المستودعات في آنٍ واحد، وحل علاقات الاستيراد الشاملة بين المستودعات لربط الاستدعاءات في مستودع ما بالتعريفات في مستودع آخر.
الكشف عن أنماط الاستدعاء غير المباشر. يجب أن يحدد التحليل الاستدعاءات التي تتم عبر الانعكاس، والتوزيع الديناميكي، ومؤشرات الدوال، والمفوضين، والاستدعاءات النصية في ملفات التكوين. يتطلب ذلك الكشف القائم على الأنماط بدلاً من حل حواف الاستدعاء المباشر: أي إيجاد آليات التوزيع الديناميكي في الكود وتحديد الدوال التي يمكن إرسالها إليها وتحت أي شروط.
التمييز بين المُستدعين النشطين والمُستدعين المُخصصين للاختبار فقط أو المُعطلين. لا تتطلب جميع المُستدعين نفس الاستجابة. يجب إزالة المُستدع الموجود فقط في بيئة اختبار للدالة المُهملة كجزء من عملية التنظيف، وليس نقله. أما المُستدع الموجود في كود تم تحديده على أنه كود مُعطل من خلال تحليل الاستخدام، فلا يُشكل عائقًا أمام إزالة الدالة. يتطلب فهم هذه الفروقات الجمع بين تعداد المُستدعين ومعلومات حول مسارات الكود النشطة فعليًا. كما هو مُفصل في دراسة اكتشاف الكود المُعطل من خلال التحليل الثابت ، يمكن أن يستمر الكود غير القابل للوصول والدوال غير المُستخدمة لسنوات في الأنظمة بالغة الأهمية بسبب عدم اكتمال التوثيق أو عدم اليقين بشأن التبعيات التاريخية، ويجب أن يُميز جرد المُستدعين لدالة مُهملة بين المُستدعين النشطين والمُستدعين المُعطلين.
عملية التخفيض التدريجي للقيمة ثم إزالتها: منهج منظم
إنّ التعامل مع إزالة الدوال المهملة كحدثٍ منفرد بدلاً من كونها عمليةً منظمة هو سبب معظم حالات فشل اكتشاف الدوال المُستدعِية. النهج الصحيح هو اعتبار الإزالة الخطوة الأخيرة في عملية متعددة المراحل تبدأ قبل حذف أي جزء من التعليمات البرمجية بفترة طويلة.
المرحلة الأولى: وضع العلامات والقياس
تتمثل الخطوة الأولى في تصنيف الدالة على أنها مهملة باستخدام الآلية المدمجة في اللغة (@Deprecated في جافا، [Obsolete] شركة#، #[deprecated] في لغة Rust، أو ما يعادلها من لغات البرمجة المناسبة، يتم تحديد عدد المتصلين الأساسيين. لا يُمثل هذا العدد الأساسي نتيجة بحث واحد، بل هو نتيجة فهرسة جميع قواعد البيانات المعروفة التي قد تستدعي الدالة، ثم حساب عدد النتائج. يخدم هذا العدد الأساسي غرضين: فهو يُحدد نطاق عملية الترحيل، ويُوفر مرجعًا يُمكن من خلاله قياس التقدم المُحرز أثناء ترحيل المتصلين.
ينبغي تنظيم خط الأساس حسب نوع المتصل وموقعه:
| فئة المتصل | العد التنازلي | درجة الأهمية | مالك |
|---|---|---|---|
| المتصلون المباشرون في نفس المستودع | N | مرتفع | الفريق الحالي |
| المتصلون المباشرون في الخدمات التابعة | N | مرتفع | أصحاب الخدمات |
| المتصلون من خلال وظائف التغليف | N | متوسط | أصحاب أغلفة المنتجات |
| المتصلون في التعليمات البرمجية المولدة أو التعليمات البرمجية الخاصة بالإطار | N | متوسط | فريق الإطار |
| المتصلون في التعليمات البرمجية المخصصة للاختبار فقط | N | منخفض | الفريق الحالي |
| المتصلون في حالة عدم وجود رمز برمجي | N | التنظيف فقط | الفريق الحالي |
المرحلة الثانية: الإخطار والترحيل
بفضل وجود قائمة كاملة بالمتصلين، تصبح عملية الترحيل جهدًا منظمًا بدلًا من كونها رد فعل. يتم إخطار كل مالك للمتصل بمواقع الاتصال المحددة: ليس "قد تقوم باستدعاء هذه الوظيفة" بل "تقوم باستدعاء هذه الوظيفة في السطر 247 من BillingService.java، السطر 82 من AccountProcessor.javaوفي اختبار التكامل في السطر 14 من BillingServiceTest.java"هذا المستوى من التحديد هو ما يتيحه مخزون المتصلين وما لا تستطيع تحذيرات الإهمال العامة توفيره."
يُعدّ توفير مسار للهجرة بالتزامن مع الإشعار أمرًا بالغ الأهمية. يجب أن يتضمن إشعار الإيقاف توثيقًا للوظيفة البديلة، ووصفًا لأي اختلافات سلوكية بين التنفيذين القديم والجديد، وفي حال كان التغيير جوهريًا، يجب تقديم مثال برمجي يوضح الوضع قبل التغيير وبعده. بالنسبة للمستخدمين في قواعد بيانات فرق أخرى، ينبغي التفاوض على الجدول الزمني للهجرة بشكل صريح بدلًا من الإعلان عنه بشكل أحادي، لأن لكل فريق أولوياته والتزاماته الخاصة بالتسليم. وكما تم توضيحه في سياق إعادة هيكلة قواعد البيانات عبر الأنظمة المترابطة ، فإن إدخال المستخدمين للبنية الجديدة تدريجيًا قبل إيقاف البنية القديمة هو الأسلوب الأمثل لمنع ظهور التغييرات الجذرية كحوادث غير متوقعة.
المرحلة الثالثة: مراقبة عدد المتصلين
بين القياس الأساسي وتاريخ الإزالة المُخطط له، يجب مراقبة عدد المُستدعين باستمرار. في كل مرة ينتقل فيها مُستدعٍ إلى الوظيفة البديلة، ينخفض العدد. يتم الوصول إلى بوابة الإزالة عندما يصل عدد المُستدعين النشطين إلى الصفر (يمكن إزالة مُستدعين الاختبار فقط ومُستدعين التعليمات البرمجية غير المستخدمة بالتزامن مع الوظيفة نفسها). تتطلب المراقبة المستمرة تشغيل عملية تعداد المُستدعين وفقًا لجدول زمني كجزء من مسار التكامل المستمر، وليس الاعتماد على جرد لمرة واحدة يصبح قديمًا مع تغييرات التعليمات البرمجية.
يرصد نظام المراقبة أيضًا المتصلين الجدد الذين تمت إضافتهم خلال فترة إيقاف استخدام الدوال القديمة. في المؤسسات الكبيرة، من الشائع كتابة أكواد جديدة تستدعي دوالًا قديمة خلال فترة الترحيل، إما لأن المطور لم يكن على دراية بإيقاف استخدامها، أو لأن مراجعة الكود لم ترصد ذلك، أو لأن مولد أكواد آلي ينتج أكوادًا تستدعي تلك الدوال القديمة. يمنع نظام الكشف عن المتصلين على مستوى التكامل المستمر (CI) للدوال القديمة، والمُهيأ للفشل عند استدعاء الدوال الجديدة، ازدياد عدد المتصلين أثناء عملية الترحيل.
المرحلة الرابعة: التحقق من الاكتمال قبل الإزالة
قبل إزالة الدالة مباشرةً، يجب إجراء تعداد نهائي للمستدعين على كامل نطاق جميع قواعد البيانات المعروفة. يعمل هذا الفحص النهائي كبوابة أمان، إذ يؤكد وصول عدد المستدعين النشطين إلى الصفر، ويكشف أي إضافات لاحقة لم يرصدها نظام مراقبة التكامل المستمر. عند هذه المرحلة، يجب أن يتحقق الجرد أيضًا من عدم وجود مستدعين ديناميكيين: ملفات التكوين التي تشير إلى الدالة عبر سلسلة نصية، والتسجيلات القائمة على الانعكاس، وأي آليات استدعاء غير مباشرة أخرى تم تحديدها خلال التحليل الأولي.
ينبغي أن يشمل التحقق مخطط التبعيات لأي مكتبات أو حزم مشتركة تعرض الدالة المهملة. إذا كانت الدالة جزءًا من واجهة برمجة تطبيقات عامة يستخدمها أطراف خارجية، فيجب أن يراعي الجدول الزمني للإزالة المستخدمين الخارجيين الذين قد لا يمكن الوصول إليهم من خلال تحليل الشفرة الداخلية. بالنسبة للأنظمة الداخلية، يغطي التحقق كل قاعدة بيانات مفهرسة. أما بالنسبة لواجهات برمجة التطبيقات المنشورة علنًا، فيغطي التحقق مجموعة المستخدمين المعروفة بالإضافة إلى فترة توقف محددة يجب على المستخدمين الخارجيين خلالها الانتقال.
كيف يختلف عمل اكتشاف المتصل في بيئات الأنظمة القديمة والأنظمة المركزية
تنطبق التحديات المذكورة أعلاه على أي نظام برمجي كبير، لكنها حادة بشكل خاص في بيئات الحواسيب المركزية والبيئات القديمة لأن علاقات الاتصال في تلك البيئات يتم التعبير عنها من خلال آليات لم يتم تصميم أدوات اكتشاف المتصل الحديثة لتحليلها.
في بيئات COBOL، تُستدعى الدوال عبر عبارات CALL التي قد تُشير إلى الهدف بواسطة سلسلة نصية حرفية، أو بواسطة عنصر بيانات يحتوي على اسم البرنامج، أو بواسطة مؤشر إجراء. يُمكن حل حالة السلسلة النصية الحرفية من خلال التحليل الثابت؛ بينما تتطلب حالة عنصر البيانات تحليل تدفق البيانات لتحديد القيمة التي قد يحملها عنصر البيانات عند نقطة الاستدعاء؛ أما حالة مؤشر الإجراء فتتطلب تتبع كيفية تعيين المؤشر. تظهر كل آلية من آليات الاستدعاء هذه بشكل مختلف في شفرة المصدر، وتتطلب تحليلاً مختلفاً لحلها.
في بيئات JCL، تُستدعى البرامج بالاسم في عبارات EXEC PGM=. اسم البرنامج عبارة عن سلسلة نصية تُشير إلى وحدة مُجمّعة في مكتبة التحميل. يتطلب تتبع مُستدعي برنامج COBOL عبر JCL تحليل JCL لاستخراج أسماء البرامج، وربط هذه الأسماء ببرامج COBOL المُجمّعة التي تُنفذها، وتحديد فقرات COBOL داخل تلك البرامج التي تستدعي الأداة المُهملة. هذا الحل متعدد الخطوات يقع خارج نطاق عمل مُحلل COBOL أو مُحلل JCL بشكل مُنفصل.
تُعدّ ملفات النسخ المشتركة حالةً بالغة الأهمية في بيئات COBOL. قد يتم تضمين فقرة مهملة مُعرّفة في ملف نسخ في العديد من البرامج عبر عبارات COPY. لا يتم نسخ الفقرة فعليًا في كل برنامج، بل يتم تضمينها أثناء عملية الترجمة. سيؤدي تحليلٌ يحسب عدد مرات ظهور اسم الفقرة في ملفات المصدر دون تحديد تضمينات ملفات النسخ إلى إفراط في الحساب (لإيجاد تعريف الفقرة في ملف النسخ نفسه) وإخفاق في الحساب (لإغفال حقيقة أن كل برنامج يتضمن ملف النسخ لديه حق الوصول إلى الفقرة). يتطلب اكتشاف المُستدعي الصحيح فهم أي البرامج تتضمن أي ملفات نسخ، وأي الفقرات داخل تلك الملفات تستدعيها فعليًا. توضح العلاقة بين المراجع المُضمّنة في الكود ومستخدميها النهائيين سبب أهمية حلّ علاقات الاستدعاء هذه على مستوى البرنامج قبل أي تغيير هيكلي: فما يبدو كمرجع نصي بسيط قد يكون الآلية الوحيدة التي من خلالها تصل عشرات البرامج إلى وظائف حيوية.
كيفية SMART TS XL يقوم ببناء قائمة كاملة بأسماء المتصلين
SMART TS XL يُنشئ هذا النظام مخططًا موحدًا لاستدعاءات البرامج عبر جميع اللغات والمنصات والمستودعات في البيئة المفهرسة. تُحلل برامج COBOL، وتدفقات مهام JCL، وخدمات Java، وتطبيقات .NET، والإجراءات المخزنة في SQL، وبرامج Python النصية، وغيرها من عناصر المصدر، باستخدام تحليل خاص بكل لغة، لتكوين مخطط مرجعي مشترك. كل دالة، وفقرة، وإجراء، وطريقة، ووحدة برنامج تُمثل عقدة في هذا المخطط. كل علاقة استدعاء، سواء كانت عبارة COBOL CALL، أو استدعاء طريقة Java، أو JCL EXEC PGM، أو SQL EXEC، تُمثل حافة مُحددة النوع. يُمثل المخطط بنية استدعاءات النظام بالكامل، وليس عرضًا جزئيًا لكل لغة.
عندما يتم وضع علامة على وظيفة ما لإزالتها، SMART TS XLيتتبع نظام تعداد المتصلين مخطط الاستدعاءات الواردة من عقدة الدالة المستهدفة، جامعًا كل متصل في كل مستوى من مستويات التسلسل الهرمي للاستدعاءات. ويكون التتبع تكراريًا، حيث يتبع المخطط عبر دوال التغليف، وطبقات الواجهة، والأدوات المساعدة الوسيطة حتى يصل إلى الدوال التي لا يوجد بها متصلون، والتي تمثل نقاط الدخول الحقيقية لسلاسل الاستدعاءات. ويتم تنظيم النتائج حسب اللغة، والمستودع، ونوع المتصل، وعمق الاستدعاء، مما يوفر للفريق جردًا منظمًا يفصل بين المتصلين المباشرين وغير المباشرين، والمتصلين النشطين عن المتصلين غير النشطين.
تُوسّع إمكانية تحليل الأثر في المنصة نطاق هذا التقرير ليشمل تقريرًا مُهيكلًا لأثر التغيير: لا يقتصر الأمر على تحديد الوظائف التي تستدعي الوظيفة المُهملة، بل يشمل أيضًا البرامج والخدمات ووظائف المعالجة الدفعية وإجراءات لغة التحكم في الوظائف (JCL) المتأثرة على جميع مستويات سلسلة التبعية. يُعد هذا التقرير الأداة التي تُتيح تنفيذ عملية الإهمال والإزالة: فهو يُحدد الجهات المسؤولة، ويُحدد مواقع الاستدعاءات، ويُحدد نطاق الترحيل المطلوب قبل إمكانية الإزالة بأمان. وكما هو موضح بالتفصيل في تحليل الأثر لإدارة التغيير المؤسسي ، فإن القدرة على حصر المكونات المتأثرة قبل إجراء أي تغيير هيكلي تُمثل شرطًا أساسيًا للتشغيل الآمن لأنظمة المؤسسة المعقدة والمترابطة.
SMART TS XL يدعم النظام أيضًا مرحلة المراقبة المستمرة لعملية إيقاف استخدام الدوال. ولأن مخطط المراجع المتبادلة يُحدَّث باستمرار مع فهرسة تغييرات شفرة المصدر، فإن عدد استدعاءات الدالة المُهملة يكون دائمًا مُحدَّثًا. يسمح تكامل خط أنابيب التكامل المستمر (CI) بفشل عمليات التحقق الآلية عند استدعاء الدوال المُهملة لأول مرة، مما يُرسِّخ منهجية الترحيل عند إدخال الشفرة الجديدة بدلًا من اكتشاف المخالفات بعد حدوثها. يغطي هذا المزيج من التعداد الأولي، وتوجيهات الترحيل، والمراقبة المستمرة دورة حياة الدالة المُهملة بالكامل، بدءًا من إضافة التعليقات التوضيحية وحتى إزالتها الآمنة.
إزالة الوظيفة بدون ندم
يكمن الفرق بين إزالة دالة بسلاسة وأخرى تتسبب في أعطال في بيئة الإنتاج، في أغلب الأحيان، في اكتمال عملية اكتشاف الدوال المستدعِية. الإزالة نفسها بسيطة: حذف التعريف ونشر الدالة. أما الإعداد فهو الجزء الأصعب، وجودة هذا الإعداد تعتمد على جودة قائمة الدوال المستدعِية التي يستند إليها.
في الأنظمة التي يكون فيها مخطط استدعاء الدوال بسيطًا، أحادي اللغة، وموجودًا ضمن مستودع واحد، يُعدّ تسلسل استدعاء الدوال في بيئة التطوير المتكاملة (IDE) وتحذيرات المُصرّف كافيين. أما في الأنظمة التي يمتد فيها مخطط استدعاء الدوال عبر لغات متعددة، ومستودعات متعددة، ومنصات متعددة، وربما عقود من التعليمات البرمجية، فإن هذه الأدوات لا تُغطي سوى جزء صغير وغير معروف من واجهة المُستدعي الفعلية. والفجوة بين ما تُرجعه هذه الأدوات وما يستدعي الدالة فعليًا هي مصدر أعطال الإنتاج.
إنّ تعداد المُستدعين المُصمّم خصيصًا للغات والمستودعات المختلفة ليس مجرد تحسين لسير عمل المطورين لإزالة الدوال، بل هو شرط أساسي لتنفيذ هذا السير بأمان في أي نظام معقد بما يكفي لتراكم علاقات استدعاءات بين الأنظمة، كما هو الحال مع الدوال المهملة في قواعد بيانات المؤسسات. كل دالة مهملة تُزال دون جرد كامل للمُستدعين تُشكّل إصدارًا يحتوي على عدد غير معروف من حالات الفشل أثناء التشغيل، والتي تنتظر مسار التنفيذ المحدد للوصول إلى التعريف المفقود. والهدف من اكتشاف المُستدعين المُهيكل هو التخلص من هذا المجهول.