08

التقييم

معادلة Clean Code Score، والتقليص البايزي، ومستويات الثقة، والترتيب، ومثال محسوب

4 دقائق قراءة8 أقسام

هذا الملف يشرح بالضبط كيف تُحسب Clean Code Score. الكود موجود في src/scoring/؛ وكل ثابت موجود في scoringConfig.ts.

المدخلات لكل مطوّر

من محرّك النسب (07):

  • analyzedLines: الأسطر التي أضافها أو غيّرها في الملفات القابلة للتحليل، ضمن الـ commits المحللة؛
  • weighted[category]: مجموع أوزان خطورة المخالفات التي أدخلها بثقة high/medium، للفئات duplication وstructure وhygiene.

فقط المخالفات التي أُدخلت تُحسب. المخالفات الموجودة سابقاً والمُصلحة والمستثناة وغير المنسوبة لا تخفض الدرجة أبداً.

المعادلة

لكل فئة:

text
kloc          = analyzedLines / 1000
adjusted(cat) = ( weighted(cat) + projectDensity(cat) × priorKLOC )
                / ( kloc + priorKLOC )
subScore(cat) = 100 × e^( −adjusted(cat) / scale(cat) )

ثم:

text
score = subScore(duplication) × 0.30
      + subScore(structure)   × 0.45
      + subScore(hygiene)     × 0.25

النتيجة تُحصر بين 0 و100 وتُقرَّب. والقيمة غير المقرّبة تُحفظ في scoreExact.

لماذا الكثافة؟

الأعداد الخام للمخالفات تعاقب المطورين المنتجين: من يكتب 20,000 سطر ستكون لديه مخالفات أكثر ممن يكتب 500. القسمة على KLOC تقيس مدى نظافة الكود الذي يكتبه، وليس كميته.

لماذا الدالة الأسية؟

e^(−density / scale) تحوّل أي كثافة ≥ 0 إلى قيمة بين 100 و0:

  • الكثافة 0 ← 100؛
  • الكثافة = scale ← حوالي 37 (مضاعف "e" واحد)؛
  • تقترب من 0 بسلاسة ولا تصبح سالبة أبداً.

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

لماذا التقليص البايزي؟

بدونه، المطوّر الذي كتب 50 سطراً نظيفاً سيحصل على 100 كاملة، ومن كتب 50 سطراً فيها سرّ واحد سيحصل على قريب من 0. ولا واحدة من النتيجتين تعني الكثير.

التقليص يضيف لكل مطوّر priorKLOC ‏(= 1 KLOC) من "الكود الافتراضي" بكثافة متوسط المشروع:

  • المطوّر الذي كتب كوداً قليلاً يُسحب بقوة نحو متوسط المشروع؛
  • المطوّر الذي كتب كوداً كثيراً بالكاد يتأثر؛ بياناته هو هي الغالبة.

كثافة المشروع لكل فئة:

text
projectDensity(cat) = Σ over all developers of weighted(cat)
                      / (total analyzed lines of all commits / 1000)

إذا كان المشروع كله نظيفاً، تكون القيمة المسبقة 0 ولا توجد قسمة على صفر.

الثوابت

الثابتالافتراضيالمعنىقابل للضبط من ملف الإعدادات؟
SEVERITY_WEIGHTlow ‏1، medium ‏3، high ‏5، critical ‏8وزن كل مخالفةانظر الملاحظة أدناه
CATEGORY_SCALEduplication ‏15، structure ‏45، hygiene ‏38الكثافة التي تنخفض عندها الدرجة الفرعية إلى حوالي 37✅ scoring.categoryScales
CATEGORY_WEIGHTduplication ‏0.30، structure ‏0.45، hygiene ‏0.25حصة كل درجة فرعية في الدرجة النهائية✅ scoring.weights
PRIOR_KLOC1قوة التقليص❌ من الكود فقط
DEFAULT_MIN_LINES_FOR_RANKING1000الأسطر المطلوبة لدخول الترتيب✅ scoring.minLinesForRanking
ENGINE_VERSION2.0.0مفتاح إلغاء الذاكرة المؤقتة❌ من الكود فقط

المقاييس مُعايَرة على مستودعات حقيقية (CleanLens نفسها، ومستودع Django + React موحّد، وأداتا سطر أوامر) بحيث يحصل الكود المُعتنى به، مع القواعد الافتراضية، على حوالي 70–85، والكود الذي عليه دَين تقني حقيقي على حوالي 40–55.

أوزان الخطورة

ماذا يعني المقياس عملياً

الكثافة (نقاط موزونة لكل KLOC) التي تصل عندها الدرجة الفرعية إلى قيمة معينة:

الدرجة الفرعيةduplication (مقياس 15)structure (مقياس 45)hygiene (مقياس 38)
901.64.74.0
803.310.08.5
705.416.113.6
5010.431.226.3
37154538

المعادلة: density = −scale × ln(subScore / 100).

مثلاً، درجة structure ‏80 تسمح بحوالي 10 نقاط موزونة لكل 1,000 سطر، أي تقريباً ثلاث نتائج بنية بخطورة medium ‏(3 × 3 = 9) لكل KLOC.

مثال محسوب

متوسطات المشروع: duplication ‏4، وstructure ‏20، وhygiene ‏8 نقاط موزونة لكل KLOC.

المطوّرة Alice كتبت 2,000 سطر (2 KLOC) وأدخلت:

  • duplication: نسختان مكررتان بخطورة medium ← 6 نقاط
  • structure: عشر نتائج medium ← 30 نقطة
  • hygiene: نتيجة low واحدة + ثلاث medium ← 10 نقاط
text
duplication: adjusted = (6 + 4×1)  / (2 + 1) = 3.33  → 100·e^(−3.33/15) = 80.1
structure:   adjusted = (30 + 20×1)/ (2 + 1) = 16.67 → 100·e^(−16.67/45) = 69.1
hygiene:     adjusted = (10 + 8×1) / (2 + 1) = 6.00  → 100·e^(−6/38)     = 85.4

score = 80.1×0.30 + 69.1×0.45 + 85.4×0.25 = 24.0 + 31.1 + 21.3 = 76.4 → 76

يعرض التقرير 76/100 (duplication 80 · structure 69 · hygiene 85). الدرجات الفرعية تخبر Alice أن تركز على البنية.

مستويات ثقة الدرجة

مبنية على analyzedLines فقط:

المستوىالأسطر المحللةكيف تقرؤه
insufficientأقل من 300غالباً هي متوسط المشروع؛ لا تقارن
provisional300 – 999استرشادية فقط
reliable1,000 – 4,999مقبولة للمقارنة
highly_reliable5,000 أو أكثرمؤشر قوي

هذه الحدود موجودة في confidenceLevel() ولا يمكن تغييرها إلا من الكود.

الترتيب

  • rankable = المطوّر لديه درجة و analyzedLines ≥ minLinesForRanking (افتراضياً 1,000).
  • المطورون غير القابلين للترتيب يحصلون على درجة، لكنهم يُعرضون منفصلين ("Not ranked — insufficient analysed code").
  • المطوّر الذي لديه 0 أسطر محللة يحصل على score = null ("no analysed code")، وهذا يختلف عن الدرجة الكاملة.
  • الترتيب (في سطر الأوامر وMarkdown ولوحة المعلومات): المطورون القابلون للترتيب حسب الدرجة، الأدنى أولاً، ثم غير القابلين للترتيب، ثم من ليست لهم درجة.

أرقام أخرى لكل مطوّر (للعرض فقط)

الحقلالمعادلةفي الدرجة؟
violationDensityمجموع النقاط الموزونة ÷ KLOC، قبل التقليصلا
contributionPercentأسطر المطوّر المحللة ÷ كل الأسطر المحللةلا — تُعرض منفصلة عن قصد
netQualityImpactالموزون المُدخل − الموزون المُصلحلا (الموجب = أضاف دَيناً أكثر مما أزال)
activeDays، وأول/آخر مساهمةمن git logلا

الاختبارات

test/scoring.test.ts يتحقق من: null عند صفر أسطر؛ والعينة الصغيرة النظيفة تأخذ درجة أقل من العينة الكبيرة النظيفة؛ والحجم لا يُعاقَب؛ وفئة واحدة تنخفض باستقلال؛ والدرجات تبقى بين 0 و100؛ ولا قسمة على صفر؛ والأوزان 0.30/0.45/0.25؛ وحدود الثقة؛ وحد ترتيب قابل للضبط.