What Is a CRAP Score? Why It Matters for AI Generated Code

The CRAP score is one number that tells you how risky a function is to change. It looks at two things: how complex the code is, and how much of it your tests check.

Think about a normal day with an AI coding assistant. You ask for a discount function. You get 60 lines in ten seconds. The code compiles. The tests pass. You approve the pull request. Three weeks later, a teammate changes one line in that function. Checkout breaks for one type of customer. Nobody knows why, because no test covered that path.

The code was not “wrong”. It was risky. The CRAP score gives that risk a number, so you can see it before it causes a problem.

What is the CRAP score?

CRAP means Change Risk Anti-Patterns. It is a code metric that gives each function (or method) a risk score. A high score tells you: “This code is difficult to understand, and the tests do not protect it.” Alberto Savoia and Bob Evans made the metric in 2007. The first tool was Crap4j, for Java. Yes, the name is a joke on purpose. Developers often say “this code is crap” with no data. Savoia wanted to give that feeling a real number.

The score uses two inputs.

  1. Cyclomatic complexity (CC). This is the number of different paths through a function. Each if, else if, loop, case or && adds a path. A function with no decisions has a CC of 1. A good quick rule: count the decisions, then add 1.
  2. Code coverage. This is the percentage of the function that your automated tests run. If the tests run every line and branch, coverage is 100%. If no test calls the function, coverage is 0%.

Complex code is not bad by itself. Untested code is not always dangerous by itself. The danger starts when you have both at the same time. The CRAP score finds that combination.

The formula is short. It looks harder than it is.

CRAP(m) = comp(m)2 × (1 − cov(m) ⁄ 100)3 + comp(m)

Here, m is one method or function, comp(m) is its cyclomatic complexity, and cov(m) is its test coverage as a percentage. Two facts make the formula easy to remember:

  • At 100% coverage, the CRAP score is equal to CC. The first part becomes zero. Only the complexity stays.
  • At 0% coverage, the CRAP score is CC² + CC. The score grows very fast as complexity goes up.

What is a good CRAP score?

The original Crap4j tool uses 30 as the limit. A function above 30 is “crappy”. Many tools, such as NDepend and cargo-crap, use the same default. A practical rule of thumb:

A practical rule of thumb:

  • 1 to 5 (low risk): Nothing to do. Keep it this way.
  • 6 to 30 (acceptable, but watch it): Add tests when you next touch it.
  • Above 30 (high change risk): Add tests or simplify before the next change.

Example 1: A tiny function

def is_adult(age):
    return age >= 18

This function has no if and no loop. CC = 1. One test checks it, so coverage = 100%. CRAP = 1² × 0³ + 1 = 1. This is the lowest possible score. Nothing to worry about.

Example 2: A shipping fee function with no tests

def shipping_fee(weight, country, express, member):
    if member:              # decision 1
        return 0
    fee = 50
    if weight > 5:          # decision 2
        fee += 30
    if country != "IN":     # decision 3
        fee += 200
    if express:             # decision 4
        fee *= 2
    if fee > 500:           # decision 5
        fee = 500
    return fee

There are 5 decisions, so CC = 5 + 1 = 6. This is a very normal function. You see code like this every day. Now look at what tests do to the score:

  • 0% coverage: 6² × 1³ + 6 = 36 + 6 = 42. Above 30. High risk.
  • 50% coverage: 36 × 0.5³ + 6 = 36 × 0.125 + 6 = 10.5. Acceptable.
  • 100% coverage: 36 × 0 + 6 = 6. Low risk.

The code did not change at all. Only the tests changed. The score dropped from 42 to 6. That is the main idea of the CRAP score: tests make complexity safe to change.

Example 3: A big function that grew over time

Imagine an order validation function with 11 decisions. CC = 12.

  • 0% coverage: 144 + 12 = 156. Very high risk.
  • 80% coverage: 144 × 0.2³ + 12 = 144 × 0.008 + 12 = 13.15. Acceptable.

Good tests bring this function back to a safe level. But you need many tests to cover 12 paths.

Example 4: A monster function

Now imagine a function with CC = 35. You write tests for every path. Coverage = 100%.

CRAP = 35. It is still above 30.

This is not a mistake in the formula. It is a message: “Tests alone cannot save this function. Break it into smaller functions.” Any function with a CC above 30 always fails, even with full coverage.

Why the CRAP score matters in AI-assisted coding

AI tools write code faster than humans can review it. The CRAP score gives you an objective check that does not get tired, does not trust the code because it “looks right”, and does not care who wrote it.

The metric is almost 20 years old, but in 2026 it got a second life. Developers started to use it as a guard rail for code from AI agents. The Rust tool cargo-crap, for example, was built for this purpose.

1. AI code often adds extra paths

An AI assistant likes to be safe. It adds a null check here, a fallback there, a try/except “just in case”, and one more if for an edge case you did not ask about. Each one adds a path. Each path adds cyclomatic complexity.

One change at a time, the function becomes harder to understand. The code still compiles. The old tests still pass. But the function is now harder to change safely.

2. AI tests can raise coverage without checking much

Ask an AI to “add tests”, and you sometimes get tests that call the function but check very little. Coverage goes up. Real protection does not.

This puts AI code in the most dangerous zone: high complexity and weak tests. That is exactly the zone the CRAP score measures. Managed Code makes the same point: the metric maps almost directly onto how AI code fails.

3. Human review trusts code that looks plausible

AI code is usually clean, well named and well formatted. A busy reviewer reads it and thinks, “Looks fine.” Linters pass. The build passes.

None of these checks ask, “Is this complex path tested?” The CRAP score does. As the Better Stack guide explains, a CRAP limit in CI catches untested complexity whether a human or a tool wrote it.

4. A score is a perfect prompt for the AI

This is the part many teams miss. A CRAP report is small and exact: the file, the function, the line number, the score. You can give it straight back to the AI agent as a task.

For example:

The function shipping_fee in pricing.py has a CRAP score of 42 (complexity 6, coverage 0%). Write unit tests that cover every branch. Each test must assert the returned fee. Do not change the function.

The AI now has a clear, measurable goal. You run the check again and see if the score went down. No long discussion needed.

Tip for old codebases: do not block every function above 30 on day one. You will stop all feature work. Instead, save today’s scores as a baseline. Then block only new functions above 30, and any function whose score goes up. This is called a “ratchet”. The code can only get better over time.

How to lower a high CRAP score

Good ways to simplify a function:

  • Extract small functions. Move each separate job into its own function with a clear name.
  • Use guard clauses. Return early for the special cases. This removes deep nesting.
  • Replace long if/else chains with a lookup table. A dictionary of rates is easier to read and test than ten elif lines.
  • Delete “just in case” code. AI often adds fallbacks for cases that can never happen. Remove them.

Final thoughts

AI has changed how fast we write code. It has not changed what makes code safe to change: simple logic and tests that really check it.

The CRAP score puts those two ideas into one honest number. It is old, simple and a little rude. That is exactly why it works so well today. Add it to your CI, give its reports back to your AI assistant, and let the number do the arguing for you.

Start small. Pick one service, run the check this week, and look at your top five scores. You will probably be surprised by what you find.