التجريد الضحل.
يقوم مساعد الذكاء الاصطناعي بتغليف الكود في دوال أو كلاسات أو واجهات (interfaces) إضافية تزيد من مستويات غير المباشرة (indirection) دون إخفاء أي تعقيد أو تمكين إعادة الاستخدام — وهي نماذج مجردة تقوم بمجرد تمرير الاستدعاء إلى موضع استدعاء واحد.
##العلامات والأعراض
يتعرف المراجع على التجريد الضحل عندما تضيف دالة أو كلاس أو واجهة (interface) جديدة اسماً وطبقة ولكن دون أي فائدة حقيقية: حيث يكرر متنها توقيعها (signature)، أو تمرر الاستدعاء مباشرة إلى استدعاء أساسي واحد، أو تُستدعى مرة واحدة بالضبط، أو لا تخفي أي قرار أو قاعدة ثابتة أو تنوع. يبدو الكود متعدد الطبقات و"بمستوى للمؤسسات"، ولكن يتعين عليك القراءة عبر كل طبقة لتفهم أي شيء — فالتجريد لا يغلف شيئاً.
العلامات المميزة:
- دوال/أساليب تمريرية (Pass-through) يتكون متنها من استدعاء تفويض واحد دون إضافة أي سلوك.
- كلاسات مغلفة (مثل
FooService←FooRepository←db) حيث تقوم كل طبقة بمجرد استدعاء الطبقة التالية. - واجهات ذات تطبيق واحد (مثل واجهة
IClockمع كلاسSystemClockفقط ككلاس مطبق لها) تم تقديمها بشكل تخميني "من أجل قابلية الاختبار" دون وجود تطبيق ثانٍ أو وهمي (fake). - دوال في ملفات
utils/helpersتُستخدم في مكان واحد بالضبط. - الأسماء المستعارة أو عمليات إعادة التسمية المكررة التي تمرر قيمة دون تغيير.
هذه هي الروائح الكلاسيكية الوسيط / الفئة الكسولة / العمومية التخمينية، والوجه الآخر لنتيجة OX Security عن الإفراط في التخصيص ("حلول شديدة التخصيص وأحادية الاستخدام لا يمكن تكييفها لأغراض أخرى").
// أضاف الذكاء الاصطناعي ثلاث "طبقات" تقوم كل منها بالتمرير إلى الطبقة التالية.
function getUserName(id: string) {
return fetchUserName(id); // لا يضيف شيئاً
}
function fetchUserName(id: string) {
return userRepository.getName(id); // لا يضيف شيئاً
}
class UserRepository {
getName(id: string) {
return db.query("SELECT name FROM users WHERE id = ?", [id]);
}
}
// getUserName هو موقع الاستدعاء الوحيد، ويستخدم مرة واحدة.
// اثنتان من الطبقات الثلاث لا تضيفان أي سلوك — وسيط خالص (pure Middle Man).
##أسباب المشكلة
لماذا تنتجها النماذج؟
- تقليد شكل البنية البرمجية الجيدة. تمتلئ مجموعات بيانات التدريب بالدروس والأكواد الهيكلية للمؤسسات — مثل طبقات الخدمة/المستودع/المصنع، وحقن التبعيات (DI)، والواجهات ذات التطبيق الواحد. يطابق النموذج المظاهر الخارجية للتجريد دون الجوهر الفعلي، لأن الدالة المساعدة المسماة تبدو مقبولة ومحترفة محلياً. يعمل التنبؤ بالرمز التالي على تحسين الكود ليتشابه مع كود جيد البنية، وليس للتحقق مما إذا كانت هذه الطبقة تؤدي وظيفة حقيقية.
- غياب سياق المستودع. عندما يولّد النموذج الكود محلياً، لا يرى أن الدالة المساعدة تُستدعى مرة واحدة، ولا أن تجريداً معتمداً موجود بالفعل في مكان آخر — فيخترع طبقات ضحلة جديدة بدلاً من إعادة الاستخدام. وقد قاس تحليل GitClear لعام 2025 الاتجاه نفسه: انخفضت الأسطر المرتبطة بإعادة الهيكلة ("المنقولة") من 25% من الأسطر المعدلة في 2021 إلى أقل من 10% في 2024، بينما ارتفعت الأسطر المنسوخة والملصقة من 8.3% إلى 12.3% — وهو ما تلخصه GitClear في عنوانها بنمو استنساخ الكود أربعة أضعاف. تضيف النماذج بنية لكنها نادراً ما تدمجها.
- التمسك الحرفي بالكتاب + الإفراط في التخصيص. يذكر تقرير OX Security عن أكثر من 300 مستودع نمط التمسك الحرفي بالكتاب (By-The-Book Fixation) — كود "يلتزم بصرامة بالأنماط القياسية حتى عندما تكون حلول أكثر ابتكاراً أكثر فعالية" — ونمط الإفراط في التخصيص (Over-Specification) — "حلول شديدة التخصيص وأحادية الاستخدام لا يمكن تكييفها لأغراض أخرى" — وكل منهما في 80–90% من الكود المولد بالذكاء الاصطناعي، إضافة إلى تجنب إعادة الهيكلة (Avoidance of Refactors، وأيضاً 80–90%): فالذكاء الاصطناعي "يتفوق في توليد كود جديد لكنه لا يعود أبداً لتحسين البنية". والأغلفة الضحلة هي الأثر المرئي لذلك.
- الحرفية في تنفيذ الأوامر والتملق. عندما يُطلب من النموذج "أضف طبقة خدمات" أو "اجعله نظيفاً/معيارياً"، يضيف طبقات حرفياً حتى عندما ينطبق مبدأ YAGNI — هندسة مفرطة بناءً على الطلب لا على الحاجة.
- تاريخ انتهاء التدريب (Training-cutoff staleness). يقوم النموذج بتغليف واجهة برمجة تطبيقات مهملة في محول ضيق بدلاً من الانتقال منها.
لماذا يسبب ضرراً؟
- قابلية الصيانة وعبء المراجعة. يجب أن يمر كل تغيير عبر طبقات ميتة؛ وعلى القارئ أن يعبر كل الوسيط ليصل إلى الاستدعاء الحقيقي. وتشبّه OX الذكاء الاصطناعي بـ "جيش من المطورين المبتدئين الموهوبين لكن عديمي الخبرة" يتجاوز إنتاجهم قدرة المراجعة — والكود الذي يبدو مهيكلاً لكنه بلا نقاط فصل حقيقية هو بالضبط ما يفلت منها.
- الصحة والأمان. كل غلاف وكل اسم مستعار هو قفزة إضافية بين المدخلات والاستدعاء المهم: على المراجع أن يتتبعها، وكذلك أي تحليل لتدفق البيانات أو لتتبع البيانات الملوثة (taint). والدالة الوسيطة التي "لا تضيف شيئاً" قد تبتلع خطأً مع ذلك أو تخفي نقطة استخدام خطرة (sink) خلف اسم بريء.
- تراكم الديون التقنية. تتصلب الواجهات ذات التطبيق الواحد التخمينية؛ ولأن كل تجريد ضحل يكون أحادي الاستخدام، فإن التعديل التالي يتم نسخه ولصقه بدلاً من جعله ذا معاملات — مما يغذي مباشرة نمو النسخ المكررة الذي رصدته GitClear. أنت تدفع ثمن هذا التعقيد دون الحصول على إعادة الاستخدام التي وُعدت بها.
##المعالجة
تكتيكات المراجعة وصياغة الأوامر
- طبّق مبدأ YAGNI وقاعدة الثلاثة: "لا تضف دالة/فئة/واجهة لإعادة الاستخدام حتى تحتاجها ثلاثة مواقع استدعاء فعلاً؛ وقبل ذلك يجب أن تخفي الطبقة الجديدة قراراً أو ثابتاً حقيقياً. ضمّن الدوال المساعدة أحادية الاستخدام."
- فرض إعادة الاستخدام: "قبل إضافة دالة مساعدة/خدمة/واجهة، ابحث في المستودع عن واحدة موجودة وأعد استخدامها". وجه النموذج إلى الوحدة المرجعية.
- اجعل النموذج يبرر وجود كل طبقة: "سرد كل دالة/كلاس جديد وبيّن التنوع أو القاعدة الثابتة التي يخفيها؛ واحذف أي منها يقوم بمجرد التمرير".
- اطلب منه تشغيل أداة الفحص (مثل قواعد
no-useless-constructor، وno-useless-rename) وفحص التكرار (jscpd) وإصلاح ما يحددونه من مشكلات.
تقنيات إعادة الهيكلة (سمّها): تضمين التابع (Inline Method)، وتضمين الفئة، وإزالة الوسيط (Remove Middle Man) للدوال الوسيطة؛ وطي التسلسل الهرمي أو حذف الواجهة للواجهات ذات التطبيق الواحد. وعندما يكون التكرار هو الدافع الحقيقي، طبّق استخراج الدالة مرة واحدة لتحصل على دالة مساعدة مشتركة فعلاً وذات معاملات — لا غلافاً لكل موقع استدعاء.
قبل — الوسيط:
function getUserName(id: string) { return fetchUserName(id); }
function fetchUserName(id: string) { return userRepository.getName(id); }
بعد — تضمين التابع (Inline Method) / إزالة الوسيط (Remove Middle Man) (موقع استدعاء واحد، ولا سلوك مضاف):
const name = userRepository.getName(id);
قبل — واجهة تخمينية بتطبيق واحد:
interface IClock { now(): number; }
class SystemClock implements IClock { now() { return Date.now(); } }
بعد — طي التسلسل الهرمي (استخدم النوع الملموس؛ وأعد تقديم الواجهة فقط عند وجود تطبيق ثانٍ فعلاً، مثل كائن وهمي للاختبار):
class SystemClock { now() { return Date.now(); } }
##أدوات الكشف
- ESLint no-useless-constructor — منشئ ممرر (Pass-through constructor) يقوم فقط بالتفويض إلى super أو يكون فارغاً
- ESLint no-useless-rename — استيراد/تصدير/تفكيك مكرر يقوم بإعادة تمرير الاسم دون تغيير
- PMD (Java) UselessOverridingMethod — دالة مفرطة (Overriding method) تقوم بمجرد استدعاء super مع نفس المعاملات
- SonarSource (Sonar S4144) S4144 — تمييز النماذج المجردة المكررة وأحادية الاستخدام ذات المتن المتطابق
- jscpd --threshold — يكتشف الدوال المساعدة أحادية الاستخدام وشبه المكررة التي تفشل التجريدات الضحلة في دمجها وتلخيصها