مقاييس جودة الكود

مقياس CRAP: كشف التعقيد غير المُختبَر

CRAP اختصار لـ Change Risk Anti-Patterns. يضرب المقياس مدى تشابك الدالة في مدى افتقارها للاختبارات، ويجيب على سؤال لا يجيب عنه أي مقياس منفرد: أي كود يكون تغييره خطرًا؟ وقد صار هذا السؤال أكثر إلحاحًا بكثير بعد أن بدأت الوكلاء تكتب الكود.

أداة خشنة بشكل متعمد

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

الاسم يقوم بعمل متعمد. يُناقَش مقياس اسمه Change Risk Anti-Patterns مرة واحدة، أما مقياس يخبرك بأن هذه الدالة سيئة فيُصلَح.

ما الغرض منه فعلًا

CRAP ليس زينة في لوحة متابعة. فكل درجة تُترجم إلى واحد من أربعة قرارات، وهذا هو سبب حسابه كله.

إلى أين يذهب الاختبار التالي

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

ما يُعاد هيكلته قبل لمسه

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

أي تغيير يستحق قراءة بشرية متأنية

الفرق البرمجي الذي يرفع CRAP في الدوال التي يلمسها يستحق مراجعة سطرًا سطرًا، والذي يخفضه يمكن تمريره سريعًا. وقرار التوجيه هذا هو حيث يسدّ المقياس ثمنه من وقت المراجعة.

ما يُختبَر انحدارًا قبل الإصدار

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

الصيغة، ولماذا الأُسُس غير متساوية

التعقيد مرفوع للأس اثنين، والنسبة غير المغطاة مرفوعة للأس ثلاثة. هذا اللاتناظر هو التصميم كله.

CRAP(m) = CC² × (1 − cov)³ + CC
  • • CC هو التعقيد الدوري للدالة، أي عدد المسارات المستقلة التي تمر بها
  • • cov هو تغطية الدالة كنسبة من 0 إلى 1، فيكون (1 − cov) هو الجزء غير المُختبَر
  • • رفع الجزء غير المُختبَر للأس ثلاثة يجعل الحد الأول ينهار سريعًا كلما ارتفعت التغطية، فيعود اختبار دالة قبيحة بمردود فوري
  • • عند التغطية الكاملة يصبح الحد الأول صفرًا ويساوي CRAP قيمة CC، وهي المخاطرة المتبقية التي لا تستطيع الاختبارات إزالتها
  • • وجود ‎+ CC‎ في النهاية هو ما يمنع المقياس من التظاهر بأن التعقيد بلا ثمن

ما تطلبه الأرقام فعلًا

بالعتبة المتعارف عليها وهي 30، هذه هي التغطية التي يحتاجها كل مستوى تعقيد لينزل تحت الخط.

CC 0% cov 50% cov 100% cov To clear 30
5 30.0 8.1 5 لا شيء. الكود البسيط يمرّ بلا اختبارات.
10 110.0 22.5 10 نحو 42٪
15 240.0 43.1 15 نحو 60٪
20 410.0 70.0 20 نحو 71٪
25 650.0 103.1 25 80٪ بالضبط
30 930.0 142.5 30 100٪، وتحلّ على الخط تمامًا
31+ 961.0 155.2 31 غير قابل للتحقق. الاختبارات لا تنقذ هذه.

ما يقوله لك المنحنى

بعد التعقيد 30 تتوقف التغطية عن المساعدة

بعتبة 30، تبقى دالة تعقيدها 31 فوق الخط حتى بتغطية 100٪. هذا ليس خللًا في الصيغة بل رسالتها: الخطوة الوحيدة المتبقية هي تقسيم الدالة.

عند التغطية الكاملة يصبح CRAP هو التعقيد نفسه

الاختبارات لا تغفر التعقيد أبدًا، بل تكتفي بوقف تضخيمه. فالدالة المعقدة المغطاة جيدًا لا تزال تحمل تعقيدها كمخاطرة معروفة ومُدارة لا مخاطرة مخفية.

الكود البسيط يُترك عن قصد

دالة بتعقيد 5 تقع عند 30 بالضبط بلا أي اختبار. المقياس يوجّه الانتباه إلى الكود المتشابك بدلًا من توليد عمل بلا قيمة على الـ getters والـ mappers.

التغطية هي الساق الضعيفة

يقيس cov التنفيذ لا التأكيد. ومجموعة اختبارات بلا تأكيدات ترفع التغطية وتخفض CRAP دون تغيير المخاطرة الحقيقية. وهذه الثغرة بالتحديد هي ما وُجد اختبار التطفير لسدّه، وهي الثغرة التي يسقط فيها النموذج اللغوي مباشرة.

كيف يستخدمه فريق ضمان الجودة

هذا هو الجزء الذي تُهمله كتابات المقاييس عادةً. CRAP أداة تسديد، وضمان الجودة هو من يوجّهها.

سدّد مجموعة الانحدار بدلًا من تكبيرها

  • - رتّب الدوال بحسب CRAP مضروبًا في تواتر الكوميتات، وابنِ مرور انحدار الإصدار من أعلى تلك القائمة
  • - الوحدات ذات CRAP المنخفض وبلا تغيير لا تحتاج مرورًا جديدًا كل إصدار، ومن هنا يأتي الوقت اللازم لأعلى القائمة
  • - أعد الترتيب في كل إصدار بدلًا من صيانة مجموعة ثابتة تكبر بلا نهاية ولا تُشذَّب

قرّر بين الأتمتة والاستكشاف

  • - التعقيد العالي مع تغطية منخفضة فجوة اختبارات وحدة لا فجوة استكشاف — وطلب تغطية 20 فرعًا يدويًا يهدر مُختبِرًا جيدًا
  • - التعقيد العالي مع تغطية عالية هو حيث يثبت الاختبار الاستكشافي قيمته، لأن المسارات تُنفَّذ لكن المتطلبات قد تبقى خاطئة
  • - السلوك غير المحدد يتوارى في الفروع غير المغطاة للدوال المعقدة، فهناك تُصمّم أولًا الحالات السلبية والحدّية

اجعل بوابات الإصدار قابلة للنقاش بالأرقام لا بالآراء

  • - بوابة تقول: لا دالة متغيرة فوق العتبة، قابلة للإلزام وللمراجعة ومن الصعب مجادلتها في نهاية السبرنت
  • - اعرض المُدخلين إلى جانب الدرجة ليكون الإصلاح واضحًا: قسّمها أو اختبرها
  • - تابع العيوب المتسرّبة مقابل درجة CRAP للدالة التي جاءت منها — فهذا الارتباط هو كيف تعاير عتبتك الخاصة بدل أن ترث عتبتنا

استخدمه لغةً مشتركة مع التطوير

  • - الاعتراض على الجودة بصيغة هذه الدالة فوضوية يخسر أمام المواعيد، وبصيغة هذه الدالة بتعقيد 24 وتغطية 30٪ لا يخسر عادةً
  • - يمنح ضمان الجودة سببًا مشروعًا ومتفقًا عليه مسبقًا لطلب إعادة هيكلة قبل وصول ميزة
  • - ويحمي المطورين أيضًا من العمل بلا قيمة، لأن المقياس يقول صريحًا إن الكود البسيط لا يحتاج اختبارات

استخدام CRAP للحكم على كود كتبه نموذج لغوي

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

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

قيّم الفرق البرمجي لا المستودع

  • - احسب CRAP على الدوال التي لمسها الوكيل قبل وبعد، وانشر الفارق على طلب الدمج
  • - ارتفاع التعقيد مع ثبات التغطية هو بصمة سلوك رُكِّب على دالة قائمة، وهي الطريقة الافتراضية التي يضيف بها الوكيل ميزة
  • - انخفاض التعقيد مع ارتفاع التغطية هو شكل التشغيل الجيد، ويستحق المكافأة بمراجعة أسرع

ضع سقفًا للتعقيد في تعليمات الوكيل نفسه

  • - سرب أنكل بوب يفعل هذا بالضبط: دور cleaner يشغّل أداة CRAP أولًا ويهبط بالتعقيد إلى 6 أو أقل قبل أي شيء آخر
  • - وهذا استخدام أذكى للمقياس من وضع بوابة على الدرجة، لأن التعقيد 6 يجعل الرقم غير قادر على الارتفاع كثيرًا مهما فعلت التغطية
  • - أعطِ الوكيل العتبة والأمر لا فقرةً عن الكود النظيف، وسيلتزم لأن للفحص رمز خروج

أعِد مُخرَج الأداة إلى الحلقة

  • - تقرير CRAP في لوحة متابعة لا يغيّر شيئًا؛ أما التقرير نفسه مدفوعًا إلى دور الوكيل التالي كفحص فاشل فيُصلَح
  • - شغّله في خطوة منفصلة عن التي كتبت الكود، حتى لا يقيّم الوكيل واجبه بنفسه
  • - حُدّ من المحاولات — فالوكيل الذي لا ينزل تحت العتبة في محاولتين يخبرك أن التصميم خاطئ، لا أنه يحتاج جولة أخرى

لا تدع وكيلًا واحدًا يرفع التغطية ويبلّغ المقياس

  • - التغطية هي النصف القابل للتلاعب، والوكيل المطلوب منه تحسين درجته سيجد أرخص طريق إلى الرقم
  • - اقرن كل بوابة CRAP بتشغيل تطفير يسأل إن كانت تلك الاختبارات الجديدة تؤكد أي شيء
  • - تعامل مع شرح الوكيل لدرجةٍ كأنه نص إعلاني؛ فالدليل هو مُخرَج الأداة

قراءة الإشارة في كود كتبه وكيل

الأرقام نفسها تعني أشياء محددة يمكن التعرف عليها عندما يكون المؤلف نموذجًا.

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

العمل به

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

المقياس نفسه

js

أي تقرير تغطية مع أي أداة تعقيد يعطي كل ما تحتاجه الصيغة.

// crap.js — coverage is a fraction, 0..1
export function crap(complexity, coverage) {
	const untested = 1 - coverage;
	return complexity ** 2 * untested ** 3 + complexity;
}

crap(15, 0);    // 240
crap(15, 0.5);  // 43.125
crap(15, 1);    // 15

ضع البوابة على التغيير لا على قاعدة الكود

sh

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

# CI: score only the methods this branch touched
git diff --name-only origin/main... -- '*.js' \
  | xargs node ./tools/crap-report.mjs --threshold 30 --changed-only

# exit non-zero when a touched method crosses the line;
# print the untouched offenders as a report, not a failure

إعادة الهيكلة التي تطلبها الدرجة

js

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

// Before: complexity climbs with every rule anyone adds.
function validate(order) {
	if (!order.id) return 'missing id';
	if (order.items.length === 0) return 'no items';
	if (order.total < 0) return 'negative total';
	if (order.currency !== 'USD' && order.currency !== 'EUR') return 'bad currency';
	if (order.customer && !order.customer.email) return 'customer without email';
	return null;
}

// After: each rule is trivially testable, the driver stays at 2.
const RULES = [
	[(o) => !o.id, 'missing id'],
	[(o) => o.items.length === 0, 'no items'],
	[(o) => o.total < 0, 'negative total'],
	[(o) => !['USD', 'EUR'].includes(o.currency), 'bad currency'],
	[(o) => Boolean(o.customer) && !o.customer.email, 'customer without email']
];

function validate(order) {
	for (const [fails, message] of RULES) if (fails(order)) return message;
	return null;
}

البوابة التي لا يستطيع وكيل تجاوزها بالكلام

sh

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

# 1. structural: is this changeable code?
crap-report --changed-only --threshold 30 || exit 1

# 2. behavioural: do the new tests assert anything?
stryker run --incremental --mutate "$(git diff --name-only origin/main...)"

# report both on the PR. one number is about the code,
# the other is about the tests that claim to cover it.

كيف تستخدمه دون إغضاب الجميع

اقرأ الرافعتين على حدة

  • - الدرجة المرتفعة بدافع التعقيد مهمة إعادة هيكلة لا مهمة اختبار
  • - الدرجة المرتفعة بدافع نقص التغطية مهمة اختبار، ورخيصة
  • - اعرض دائمًا CC والتغطية بجانب الدرجة، لأن الرقم وحده لا يقول أيّهما السبب

رجّح بحسب معدل التغيير

  • - مخاطرة التغيير لا تهم إلا حيث يحدث التغيير
  • - درجة 200 في ملف لم يُلمس منذ أربع سنوات أقل إلحاحًا من 60 في ملف يُعدَّل كل أسبوع
  • - رتّب بحسب الدرجة مضروبة في تواتر الكوميتات، واعمل من أعلى القائمة

اجعل الاستثناءات صريحة

  • - الكود المُولَّد والمحوّلات وعبارات switch الشاملة تنفخ التعقيد دون أن تنفخ المخاطرة الحقيقية
  • - استثنِها في الإعدادات مع تعليق يوضح السبب، بدلًا من رفع العتبة بصمت
  • - أعد النظر في قائمة الاستثناءات عندما يتوقف توليد الكود الذي تحميه

قراءات خاطئة شائعة

التعامل معه كدرجة جودة

يقدّر CRAP مخاطرة تغيير الكود. ولا يقول شيئًا عن صحته أو حسن تسميته أو جودة تصميمه. فالدالة النظيفة المغطاة جيدًا ذات التعقيد المجالي الحقيقي تحصل على الدرجة نفسها التي تحصل عليها دالة مزعجة.

التلاعب به بالتغطية

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

إطلاق حملة تنظيف كبرى

تقرير CRAP على مستوى المستودع كله لكود قديم ينتج رقمًا ضخمًا إلى حد يجعله مهملًا. ضع السقاطة على الكود المتغير، واترك الأجزاء الخطرة يصلحها من كان سيلمسها أصلًا، إنسانًا كان أو وكيلًا.

توقّع أداة مصانة

أداة Crap4j الأصلية خامدة منذ سنوات. يحمل NDepend المقياس على .NET، وهناك تنفيذات مجتمعية لـ Rust و.NET وGroovy، ويصون أنكل بوب crap4j وcrap4go وcrap4clj لسرب وكلائه. لكن في معظم البيئات تحسبه بنفسك من بيانات تجمعها أصلًا.

رقم واحد وسؤال واحد

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

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

الأسئلة الشائعة

ما الذي يُعدّ درجة CRAP سيئة؟

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

هل CRAP مفيد لمراجعة الكود المولّد بالذكاء الاصطناعي؟

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

أي عتبة نضع للفروع التي يكتبها وكيل؟

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

هل يستطيع الوكيل إصلاح درجة CRAP الخاصة به؟

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

لماذا التعقيد للأس اثنين والجزء غير المُختبَر للأس ثلاثة؟

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

هل ينبغي أن يُفشل CRAP عملية البناء؟

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

© 2026 Ryware Solutions Ltd. · رقم الشركة 516681764