מדדי איכות קוד

מדד CRAP: מורכבות וכיסוי במספר אחד

CRAP הוא קיצור של Change Risk Anti-Patterns. המדד מכפיל את מידת הסבך של מתודה במידת חוסר הבדיקות שלה, והתוצאה עונה על שאלה שאף מדד בודד לא עונה עליה: איזה קוד מסוכן לשנות?

כלי גס במכוון

אלברטו סבויה ובוב אוונס הציגו את CRAP ב-2007 יחד עם Crap4j, כלי ג'אווה שנתן ציון לכל מתודה בבנייה. ההנחה הייתה שלא מורכבות ולא כיסוי אומרים הרבה בנפרד. מתודה מסובכת עם בדיקות יסודיות היא ברת-קיום. מתודה טריוויאלית בלי בדיקות היא בסדר. מה שכן כואב זו מורכבות שאף אחד לא כיסה, כי שם עריכה קטנה מייצרת הפתעה שאף אחד לא תופס. CRAP מכניס את שני האיברים לביטוי אחד, כך שהשילוב המסוכן מקבל ציון גרוע והשילובים הבלתי מזיקים לא.

השם עושה עבודה במכוון. על מדד שנקרא Change Risk Anti-Patterns מדברים פעם אחת; מדד שאומר לכם שמתודה היא זבל מתקנים.

הנוסחה, ולמה המעריכים לא שווים

המורכבות בריבוע. החלק הלא מכוסה בשלישית. האסימטריה הזו היא כל התכנון.

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 בלי לשנות דבר בסיכון האמיתי. בדיוק את הפער הזה בדיקות מוטציה נועדו לסגור.

לעבוד עם זה

הנוסחה היא שתי שורות קוד, וזה רוב הסיבה שהיא ממומשת מחדש שוב ושוב בסביבות חדשות.

המדד עצמו

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;
}

איך להשתמש בזה בלי לעצבן את כולם

לקרוא את שני המנופים בנפרד

  • - ציון גבוה שנובע ממורכבות הוא מטלת שינוי מבנה, לא מטלת בדיקות
  • - ציון גבוה שנובע מכיסוי חסר הוא מטלת בדיקות, וזולה
  • - תמיד להציג את CC ואת הכיסוי ליד הציון, כי המספר לבד לא אומר איזה מהשניים

לשקלל לפי תדירות שינוי

  • - סיכון שינוי חשוב רק שם שבו שינוי בפועל קורה
  • - ציון 200 בקובץ שלא נגעו בו ארבע שנים דחוף פחות מציון 60 בקובץ שנערך כל שבוע
  • - מיינו לפי ציון כפול תדירות קומיטים, ועבדו מלמעלה למטה

להפוך חריגות למפורשות

  • - קוד מיוצר, אדפטרים ומשפטי switch ממצים מנפחים מורכבות בלי לנפח סיכון אמיתי
  • - החריגו אותם בהגדרות, עם הערה שמסבירה למה, במקום להעלות את ערך הסף בשקט
  • - בדקו מחדש את רשימת החריגות כשהקוד שהיא מגנה עליו מפסיק להיות מיוצר

טעויות קריאה נפוצות

להתייחס לזה כציון איכות

CRAP מעריך את הסיכון שבשינוי קוד. הוא לא אומר דבר על נכונות, על שמות טובים או על עיצוב טוב. מתודה נקייה ומכוסה היטב עם מורכבות עסקית אמיתית תקבל אותו ציון כמו מתודה לא נעימה.

לשחק עם הכיסוי

בדיקות שמצלמות הכול בסנאפשוט ומעברים בלי אסרשנים מזיזים את המספר ולא את הסיכון. אם CRAP הוא שער, צריך משהו שישמור על כנות הבדיקות: סקירה, בדיקות מוטציה, או שניהם.

לפתוח אפוס ניקוי

דוח CRAP על כל המאגר בקוד מדור קודם מייצר מספר כה גדול שהוא נזנח. במקום זה שימו מחגר על הקוד שהשתנה, ותנו לחלקים המסוכנים להיתקן על ידי מי שכבר עמד לגעת בהם.

לצפות לכלי מתוחזק

ה-Crap4j המקורי רדום כבר שנים. NDepend נושא את המדד ב-.NET, ויש מימושי קהילה ל-Rust, ל-.NET ול-Groovy, אבל ברוב הסביבות תחשבו אותו בעצמכם מנתונים שאתם כבר אוספים.

מספר אחד, שאלה אחת

CRAP הוא לא ציון איכות ולא נועד להיות כזה. הוא עונה על שאלה צרה ושימושית יותר: אם מישהו יערוך את המתודה הזו בשבוע הבא, מה הסיכוי שמשהו יישבר בשקט? המורכבות אומרת בכמה דרכים אפשר לטעות, הכיסוי אומר על כמה מהן מישהו משגיח, והנוסחה משקללת את השני חזק יותר מהראשון.

זה מספיק כדי לחבר את זה לבנייה. שימו שער על קוד חדש ומשונה בערך סף, מיינו את השאר לפי ציון מול תדירות שינוי, וקראו את שני הקלטים בנפרד כדי שהמספר יתרגם לפעולה: לפצל את המתודה, או לבדוק אותה. רק זכרו שחצי הכיסוי של הנוסחה שווה בדיוק כמה שהאסרשנים שלכם נותנים לו לשוות.

שאלות נפוצות

מה נחשב ציון CRAP גרוע?

שלושים הוא ערך הסף המקובל כברירת מחדל, בירושה מ-Crap4j ובשימוש חוזר ברוב המימושים שבאו אחריו. הנמיכו אותו בקוד חדש שבו עלות שמירת הקו קטנה, ושמרו עליו בקוד מדור קודם בזמן שאתם מהדקים את המחגר, במקום להעלות אותו כדי שדוח ייראה טוב יותר.

למה המורכבות בריבוע והחלק הלא נבדק בשלישית?

כדי שהכיסוי יזיז את הציון מהר יותר מהמורכבות. האיבר בשלישית מתמוטט לעבר אפס כשהכיסוי מתקרב למלא, וזה מתגמל מיד בדיקה של קוד מורכב, בעוד האיבר בריבוע יחד עם המורכבות בסוף משאירים רצפה שאף בדיקה לא מסירה.

האם מדד CRAP עוד רלוונטי?

כלי הג'אווה המקורי רדום כבר זמן רב, אבל הנוסחה חוזרת ומופיעה בסביבות חדשות כי היא טריוויאלית למימוש ועונה על שאלה שדשבורדים של מדד בודד לא עונים עליה. בכל מקום שבו אתם כבר אוספים כיסוי ומורכבות, אתם שתי שורות ממנה.

האם ציון CRAP נמוך אומר שבטוח לשנות את הקוד?

הוא אומר שהמורכבות מכוסה, וזה לא אותו דבר כמו נבדקת היטב. כיסוי סופר הרצה ולא טענה, כך שציון נמוך שנשען על בדיקות חלשות הוא מספר נוח מעל מציאות לא נוחה. בדיקות מוטציה הן הדרך המקובלת לבדוק אם הכיסוי אמיתי.

האם CRAP צריך להפיל את הבנייה?

כמחגר על מתודות חדשות ומשונות, כן, כי זה אכיף ואף אחד לא צריך לתכנן את זה. כשער על כל המאגר בבסיס קוד קיים, לא. זה מייצר בקלוג ולא שינוי התנהגות.

© 2026 - Ryware.