מדד CRAP: איתור מורכבות לא נבדקת
CRAP הוא קיצור של Change Risk Anti-Patterns. המדד מכפיל את מידת הסבך של מתודה במידת חוסר הבדיקות שלה, ועונה על שאלה שאף מדד בודד לא עונה עליה: איזה קוד מסוכן לשנות? השאלה הזו נעשתה דחופה בהרבה מאז שסוכנים כותבים את הקוד.
כלי גס במכוון
אלברטו סבויה ובוב אוונס הציגו את CRAP ב-2007 יחד עם Crap4j, כלי ג'אווה שנתן ציון לכל מתודה בבנייה. ההנחה הייתה שלא מורכבות ולא כיסוי אומרים הרבה בנפרד. מתודה מסובכת עם בדיקות יסודיות היא ברת-קיום. מתודה טריוויאלית בלי בדיקות היא בסדר. מה שכן כואב זו מורכבות שאף אחד לא כיסה, כי שם עריכה קטנה מייצרת הפתעה שאף אחד לא תופס. CRAP מכניס את שני האיברים לביטוי אחד, כך שהשילוב המסוכן מקבל ציון גרוע והשילובים הבלתי מזיקים לא.
השם עושה עבודה במכוון. על מדד שנקרא Change Risk Anti-Patterns מדברים פעם אחת; מדד שאומר לכם שמתודה היא זבל מתקנים.
בשביל מה הוא באמת
CRAP הוא לא קישוט בדשבורד. כל ציון נפתר לאחת מארבע החלטות, וזו כל הסיבה לחשב אותו.
לאן הולכת הבדיקה הבאה
בסידור יורד, הדוח הוא תור עבודה למאמץ הבדיקות. בראש הרשימה נמצא המקום שבו בדיקה קונה את הפחתת הסיכון הגדולה ביותר לשעת עבודה, וזו תשובה טובה בהרבה מרדיפה אחרי אחוז כיסוי גלובלי.
מה לשנות מבנית לפני שנוגעים
ציון גבוה שנובע ממורכבות הוא אזהרה שהמתודה תילחם בכם. לפצל אותה לפני שמוסיפים התנהגות זול בדרך כלל מלהוסיף התנהגות ואז לנסות לבדוק את התוצאה.
איזה שינוי דורש קריאה אנושית זהירה
diff שמעלה CRAP במתודות שהוא נוגע בהן ראוי לסקירה שורה-שורה. diff שמוריד אותו אפשר לרפרף. החלטת הניתוב הזו היא המקום שבו המדד מחזיר את עצמו בזמן סקירה.
מה לבדוק ברגרסיה לפני שחרור
סיכון שינוי יחד עם תדירות שינוי עדכנית אומרים אילו מודולים הרוויחו מעבר רגרסיה בשחרור הזה, ועל אילו אפשר לסמוך כי שום דבר בהם לא זז.
הנוסחה, ולמה המעריכים לא שווים
המורכבות בריבוע. החלק הלא מכוסה בשלישית. האסימטריה הזו היא כל התכנון.
- • 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 בלי לשנות דבר בסיכון האמיתי. בדיוק את הפער הזה בדיקות מוטציה נועדו לסגור, וזה הפער שמודל שפה נכנס אליו ישר.
איך צוות QA משתמש בזה
זה החלק שנוטים להשמיט מכתבות על מדדים. CRAP הוא כלי כיוון, ומי שמכוון אותו זה QA.
לכוון את מערך הרגרסיה במקום להגדיל אותו
- - דרגו מתודות לפי CRAP כפול תדירות קומיטים, ובנו את מעבר הרגרסיה של השחרור מראש הרשימה
- - מודולים עם CRAP נמוך ובלי שינויים לא צריכים מעבר חדש בכל שחרור, ומשם מגיע הזמן לראש הרשימה
- - דרגו מחדש בכל שחרור במקום לתחזק מערך סטטי שגדל לנצח ואף פעם לא נגזם
להחליט בין אוטומציה ובין בדיקות חוקרות
- - מורכבות גבוהה עם כיסוי נמוך היא פער בבדיקות יחידה ולא פער בבדיקות חוקרות — לבקש מ-QA ידני לכסות 20 הסתעפויות ביד זה בזבוז של בודק טוב
- - מורכבות גבוהה עם כיסוי גבוה היא בדיוק המקום שבו בדיקות חוקרות משתלמות, כי המסלולים מורצים אבל הדרישות עוד יכולות להיות שגויות
- - התנהגות לא מוגדרת מסתתרת בהסתעפויות לא מכוסות של מתודות מורכבות, ולכן שם מעצבים קודם מקרים שליליים ומקרי קצה
להפוך שערי שחרור לוויכוח על מספרים ולא על דעות
- - שער של אין מתודה משונה מעל ערך הסף הוא אכיף, ניתן לסקירה, וקשה להתווכח איתו בסוף ספרינט
- - הציגו את שני הקלטים ליד הציון כדי שהתיקון יהיה חד-משמעי: לפצל, או לבדוק
- - עקבו אחרי פגמים שנשרו מול ה-CRAP של המתודה שממנה הגיעו — המתאם הזה הוא הדרך לכייל ערך סף משלכם ולא לרשת את שלנו
להשתמש בזה כשפה משותפת עם הפיתוח
- - התנגדות איכות בנוסח המתודה הזו מבולגנת מפסידה לדדליין; בנוסח המתודה הזו במורכבות 24 עם 30% כיסוי בדרך כלל לא
- - זה נותן ל-QA סיבה לגיטימית ומוסכמת מראש לבקש שינוי מבנה לפני שפיצ'ר נכנס
- - זה גם מגן על מפתחים מעבודה מיותרת, כי המדד אומר במפורש שקוד טריוויאלי לא צריך בדיקות
להשתמש ב-CRAP כדי לשפוט קוד שמודל שפה כתב
סוכנים שינו את הכלכלה של המדד הזה. הם מפיקים קוד משכנע מהר משאיש יכול לקרוא, והם ישמחו לכתוב את הבדיקות שמודדות אותם. מורכבות מול כיסוי היא אחת האותות הכנות הזולות ביותר שנשארו לכם.
הסיכון המדויק הוא שמודל שפה ממטב את המטרה הנראית. תבקשו בדיקות ותקבלו בדיקות; תבקשו כיסוי ותקבלו כיסוי. שני חצאי CRAP הם דברים שסוכן יכול להזיז, אבל רק אחד מהם הוא יכול להזיז בכנות. מורכבות היא תכונה מבנית של הקוד שהוא כתב — אין דרך לדבר אותה למעלה או למטה. כיסוי הוא מספר שהסוכן יכול לנפח בכך שיריץ שורות בלי לטעון עליהן דבר. לכן הצירוף אבחנתי: המורכבות אומרת מה הסוכן בנה, הכיסוי אומר מה הוא טוען על זה, והפער ביניהם הוא המקום להסתכל בו.
לתת ציון ל-diff, לא למאגר
- - חשבו CRAP על המתודות שהסוכן נגע בהן, לפני ואחרי, ופרסמו את הדלתא ב-pull request
- - מורכבות שעולה עם כיסוי שטוח היא החתימה של התנהגות שהוברגה לפונקציה קיימת, וזו הדרך שבה סוכן מוסיף פיצ'ר כברירת מחדל
- - מורכבות שיורדת עם כיסוי שעולה היא איך נראית ריצה טובה של סוכן, ושווה לתגמל אותה בסקירה מהירה יותר
לכתוב תקרת מורכבות בהוראות של הסוכן עצמו
- - הנחיל של אנקל בוב עושה בדיוק את זה: תפקיד ה-cleaner מריץ קודם את כלי ה-CRAP ומוריד את המורכבות ל-6 ומטה לפני כל דבר אחר
- - זה שימוש חכם יותר במדד מלשים שער על הציון, כי במורכבות 6 המספר כמעט לא יכול לעלות מה שלא יקרה לכיסוי
- - תנו לסוכן את ערך הסף ואת הפקודה, לא פסקה על קוד נקי, והוא יציית כי לבדיקה יש קוד יציאה
להחזיר את פלט הכלי אל הלופ
- - דוח CRAP בדשבורד לא משנה דבר; אותו דוח שנשפך לתור הבא של הסוכן כבדיקה שנכשלה מתוקן
- - הריצו את זה בשלב נפרד מזה שכתב את הקוד, כדי שהסוכן לא ידרג את שיעורי הבית של עצמו
- - הגבילו ניסיונות חוזרים — סוכן שלא מצליח לרדת מתחת לערך הסף בשני ניסיונות אומר לכם שהעיצוב שגוי, לא שהוא צריך עוד הזדמנות
לא לתת לסוכן אחד גם להעלות כיסוי וגם לדווח על המדד
- - כיסוי הוא החצי שאפשר לשחק בו, וסוכן שמבקשים ממנו לשפר את הציון שלו ימצא את הדרך הזולה למספר
- - צמדו כל שער CRAP לריצת מוטציות, ששואלת אם הבדיקות החדשות בכלל טוענות משהו
- - התייחסו להסבר של הסוכן על ציון כאל טקסט שיווקי; ההוכחה היא פלט הכלי
לקרוא את האות בקוד שסוכן כתב
אותם מספרים אומרים דברים מסוימים וניתנים לזיהוי כשהמחבר הוא מודל.
| אות | מה זה אומר בדרך כלל | מה לעשות |
|---|---|---|
| המורכבות עלתה, הכיסוי שטוח | התנהגות חדשה נוספה כהסתעפויות נוספות בתוך פונקציה קיימת. | בקשו חילוץ לפני הסקירה. זו הצורה הנפוצה ביותר של גדילת קוד של סוכן. |
| הכיסוי זינק, המורכבות לא השתנתה | נוספו בדיקות. אם הן טוענות משהו — זה עוד לא הוכח. | הריצו בדיקות מוטציה על הקבצים שהשתנו לפני שאתם מאמינים למספר. |
| מתודה אחת גבוה מעל ערך הסף | המודל המשיך להוסיף מקרים למקום שמצא ראשון, פרומפט אחר פרומפט. | פצלו לפי כלל ומדדו מחדש. הציון בדרך כלל מתמוטט בלי אף בדיקה חדשה. |
| מורכבות בקוד שאף אחד לא ביקש | הסתעפויות ספקולטיביות, מסלולים הגנתיים ואפשרויות בלי דרישה מאחוריהן. | למחוק. מורכבות לא נבדקת שאף דרישה לא מנסחת היא הדבר הזול ביותר להסיר. |
| ציון טוב, אבל הכיסוי מבדיקות snapshot | המערך מריץ הכול ובודק כמעט כלום. | המדד משקר לכם דרך קלט הכיסוי שלו. תקנו את הבדיקות, לא את הציון. |
לעבוד עם זה
הנוסחה היא שתי שורות קוד, וזה רוב הסיבה שהיא ממומשת מחדש שוב ושוב בסביבות חדשות.
המדד עצמו
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 הוא לא ציון איכות ולא נועד להיות כזה. הוא עונה על שאלה צרה ושימושית יותר: אם מישהו יערוך את המתודה הזו בשבוע הבא, מה הסיכוי שמשהו יישבר בשקט? המורכבות אומרת בכמה דרכים אפשר לטעות, הכיסוי אומר על כמה מהן מישהו משגיח, והנוסחה משקללת את השני חזק יותר מהראשון.
זה הופך את זה לכלי כיוון עבור QA ולכלי ביקורת עבור עבודה בסיוע AI. כוונו אותו ל-diff, שימו שער על קוד חדש ומשונה בערך סף, דרגו את השאר לפי ציון מול תדירות שינוי, והשאירו את שני הקלטים גלויים כדי שהמספר יתרגם לפעולה: לפצל את המתודה, או לבדוק אותה. רק זכרו איזה חצי מחבר יכול לזייף. מורכבות היא מה שהקוד הוא; כיסוי הוא רק מה שהבדיקות טוענות, ובדיקות מוטציה הן הדרך לבדוק את הטענה.
מאמרי הנדסה קשורים
חצי הכיסוי של הנוסחה הזו הוא בדיוק מה שבדיקות מוטציה חוקרות, ולשני המדדים כבר יש תפקידים משלהם בנחיל סוכנים שעובד בפועל.
בדיקות מוטציה הן שליליות. בדיקות TDD הן חיוביות.
למה ריצות מוטציה מדווחות רק על מה שמערך מפספס, איך להריץ אותן בעלות סבירה, ואיך לשפוט בדיקות שמודל שפה כתב לקוד של עצמו.
SwarmForge לעומק: איך נחיל הסוכנים של אנקל בוב עובד באמת
ניתוח מעמיק של צינור התפקידים, דמון ההעברות ושערי האיכות הניתנים להרצה של SwarmForge, עם פסק דין מפורש על מה לאמץ.
שאלות נפוצות
מה נחשב ציון CRAP גרוע?
שלושים הוא ערך הסף המקובל כברירת מחדל, בירושה מ-Crap4j ובשימוש חוזר ברוב המימושים שבאו אחריו. הנמיכו אותו בקוד חדש שבו עלות שמירת הקו קטנה, ושמרו עליו בקוד מדור קודם בזמן שאתם מהדקים את המחגר, במקום להעלות אותו כדי שדוח ייראה טוב יותר.
האם CRAP שימושי לסקירת קוד שנוצר בידי AI?
זה אחד האותות הזולים השימושיים ביותר, כי מורכבות היא עובדה מבנית על הקוד שהמודל הפיק ואי אפשר להתווכח איתה. תנו ציון ל-diff ולא למאגר, שימו לב למורכבות שעולה בעוד הכיסוי נשאר שטוח, וצמדו את זה תמיד לריצת מוטציות כדי שחצי הכיסוי לא ינופח בבדיקות שלא טוענות דבר.
איזה ערך סף לקבוע לענפים שסוכן כתב?
הדוק יותר מלאדם, ומנוסח כמורכבות ולא כציון. התבנית המעשית, וזו שהנחיל של אנקל בוב מפעיל, היא תקרת מורכבות קשה בסביבות 6 על מתודות שהשתנו: ברמה הזו ציון ה-CRAP כמעט לא יכול לעלות מה שלא יקרה לכיסוי, ולכן לא נשאר על מה להתמקח.
האם סוכן יכול לתקן את ציון ה-CRAP של עצמו?
בחצי המורכבות בדרך כלל כן, וזה שיפור אמיתי ששווה לאטמט. בחצי הכיסוי היזהרו: הדרך הזולה ביותר להעלות כיסוי היא להריץ קוד בלי לבדוק אותו. הריצו את השער בשלב שלא כתב את הקוד, ואמתו את הבדיקות החדשות בבדיקות מוטציה.
למה המורכבות בריבוע והחלק הלא נבדק בשלישית?
כדי שהכיסוי יזיז את הציון מהר יותר מהמורכבות. האיבר בשלישית מתמוטט לעבר אפס כשהכיסוי מתקרב למלא, וזה מתגמל מיד בדיקה של קוד מורכב, בעוד האיבר בריבוע יחד עם המורכבות בסוף משאירים רצפה שאף בדיקה לא מסירה.
האם CRAP צריך להפיל את הבנייה?
כמחגר על מתודות חדשות ומשונות, כן, כי זה אכיף ואף אחד לא צריך לתכנן את זה. כשער על כל המאגר בבסיס קוד קיים, לא. זה מייצר בקלוג ולא שינוי התנהגות.