Also called Change Risk Anti-Patterns, CRAP index, Risky untested code
In depth Untested hotspots: what to test first: ranking files by churn, dependents and coverage, then writing the tests that matter.
How the CRAP score is measured
For a method m, with comp(m) its cyclomatic complexity and cov(m) its test coverage in percent:
CRAP(m) = comp(m)^2 x (1 - cov(m)/100)^3 + comp(m)
Coverage enters cubed, so the penalty falls fast as tests are added. At 100% coverage the first term vanishes and the score equals the complexity. At 0% coverage the score is comp^2 + comp.
| Method | Complexity | Coverage | CRAP |
|---|---|---|---|
format_name | 3 | 0% | 12.0 |
apply_discount | 8 | 100% | 8.0 |
apply_discount | 8 | 60% | 12.1 |
route_payment | 12 | 80% | 13.2 |
route_payment | 12 | 20% | 85.7 |
reconcile | 35 | 100% | 35.0 |
Two rows carry the lesson. route_payment at 80% coverage is fine; at 20% it is far past the threshold of 30. reconcile is above 30 even fully tested, because complexity alone exceeds it. The original authors proposed 30 as the line, and with zero coverage that means any method of complexity 5 or more reaches it.
Why the CRAP score matters
Complexity and coverage each mislead alone. A complex method with thorough tests is manageable, and a simple method with no tests is cheap to get wrong. CRAP flags the combination: code that is hard to reason about and has no tests catching mistakes. It gives a single number to sort a backlog and a way to decide between "write tests" and "split this up".
It is a per-method formula and inherits the weaknesses of its inputs: line coverage can be high without asserting anything, and cyclomatic complexity counts branches, not how hard code is to read. It also ignores whether anyone changes the code, which is why it works best next to churn and hotspots.
How to measure the CRAP score yourself
You need complexity and coverage per method, then the formula.
- Java: crap4j, the original tool.
- PHP: PHPUnit's HTML coverage report shows a CRAP index per method.
- Python:
radon cc -s srcfor complexity andcoverage.pyfor coverage, then apply the formula in a short script. - JavaScript or TypeScript: ESLint's
complexityrule gives per-function complexity, and Istanbul/nyc or c8 give per-function coverage. - Other languages:
lizardreports complexity per function for many languages; pair it with your coverage tool.
Per-function coverage is the hard part. Line coverage reports are per file, so you must map covered lines back to each function's line range.
How repowise measures the CRAP score
repowise computes CRAP with the standard formula (crap_score in analysis/health/crap.py), and the threshold is 30. It is used in one detector, untested_complexity, which flags a function whose CRAP reaches 30: medium from 30, high from 50, critical from 100. It adds a function-level finding beside the file score in the code health analysis. The analysis is deterministic, with no LLM calls. See cyclomatic complexity for the input.
Coverage can be measured or estimated:
- Measured: after
repowise coverage add <report>, covered lines are mapped to each function's span and the percentage is real. The upper severity bands apply only to measured coverage. - Estimated: with no report, repowise places the function in a tier from the call graph (a test calls it, tests reach it, or neither) and says so. A tier never appears as a percentage.
Functions with complexity of 30 or more get a finding that asks for a refactor, since tests cannot bring them under the line. Test files and code that does not ship are exempt.
For a ranking at the file level, repowise also uses untested_hotspot (churn, dependents, coverage), covered in the guide to untested hotspots. See test coverage gap for the wider concept.