Complexity Analysis with Radon
What Radon measures
Section titled “What Radon measures”- Cyclomatic complexity (branches/paths)
- Maintainability index
High complexity often means:
- harder to test
- more bugs
- slower changes
radon cc -a your_packageMaintainability:
radon mi your_packageHow to use results
Section titled “How to use results”- identify hotspots
- refactor into smaller functions
- add tests around risky logic first
One file, six tools
Section titled “One file, six tools”Every measurement on this page — and on the other five tools in this phase — comes from running the tool against this deliberately flawed file:
import os
import subprocess
import hashlib
def process(items, user_input, flag = False):
unused_var = 42
result=[]
for i in items:
if i > 0:
if flag:
if i % 2 == 0:
result.append(i*2)
else:
result.append(i)
else:
result.append(i)
password = "hunter2"
h = hashlib.md5(password.encode()).hexdigest()
os.system("echo " + user_input)
subprocess.call("ls " + user_input, shell=True)
return result
def add(a: int, b: int) -> int:
return a + b
x = add("1", 2)| tool | what it reported on sample.py |
|---|---|
| flake8 | 5 style and dead-code issues. No security findings. |
| pylint | 4 issues, score 8.10/10 |
| mypy | 1 type error, which neither linter saw |
| bandit | 5 security issues, 3 of them HIGH |
| radon | complexity A (5), maintainability A (56.30) |
The headline is that no tool subsumes another. flake8 read the whole file and reported nothing about the shell injection on line 20. bandit read the same file and said nothing about the type error on line 29. Running one and concluding the code is clean is the mistake this phase exists to prevent.
Cyclomatic complexity counts decisions
Section titled “Cyclomatic complexity counts decisions”Every if, for, while, and, or, except and comprehension adds one path through
a function. Radon counts them:
F 6:0 process - A (5)
F 25:0 add - A (1)add has a single path. process has five, from one loop and three nested conditions.
Measured on a deliberately tangled grading function:
F 1:0 grade - C (15)flowchart LR A["1 path to start"] --> B["+1 per if / elif"] B --> C["+1 per for / while"] C --> D["+1 per and / or"] D --> E["+1 per except"] E --> S["the score is the number of
independent paths through the function"]
| rank | complexity | reading |
|---|---|---|
| A | 1-5 | simple |
| B | 6-10 | fine |
| C | 11-20 | worth a look |
| D | 21-30 | hard to test |
| E / F | 31+ | rewrite it |
The practical value is testing: a function with complexity 15 needs roughly 15 test cases to cover its paths. That is the honest cost of leaving it alone, and it is a better argument for splitting it than any appeal to elegance.
Maintainability index is a different number
Section titled “Maintainability index is a different number”messy.py - A (48.99)The same file whose function scored C scored A for maintainability, because MI combines complexity with volume and lines of code — and the file is short. A small tangled function and a long simple one can land in the same place.
Read them together: MI for a file-level trend over time, CC to find the specific function worth splitting.
Formatting does not change either
Section titled “Formatting does not change either”before: F 1:0 grade - C (15)
after : F 1:0 grade - C (15)Measured. This is the most useful thing radon tells you about the rest of the toolchain: black makes code readable, radon measures whether it is simple, and those are different properties.
Using it in practice
Section titled “Using it in practice”radon cc -s -a myapp/ # per-function scores, plus the average
radon cc -s -nc myapp/ # only rank C and worse
radon mi -s myapp/ # maintainability per file
xenon --max-absolute C myapp/ # fail CI above a thresholdradon cc -nc is the one to run on an unfamiliar codebase: it lists only the functions
that are actually complicated, which is usually a short and very informative list.
What to do with a C
Section titled “What to do with a C”The usual fix is to extract the branches into named functions. The total complexity across the module barely changes; what changes is that each piece can be tested and named independently, and the top-level function reads as a description of the decision rather than an implementation of it.
Check yourself
Section titled “Check yourself”-
A function scores C (15) for cyclomatic complexity. What is the most concrete consequence?
That testing cost is the honest argument for splitting it, and a better one than an appeal to elegance.
pch.quizShowAnswer
B — it has about 15 independent paths, so covering it properly takes roughly 15 test cases — That testing cost is the honest argument for splitting it, and a better one than an appeal to elegance.
-
The same file scored C for a function's complexity and A (48.99) for maintainability. How can both be true?
Use MI as a file-level trend and CC to find the specific function worth splitting. A small tangled function and a long simple one can score alike.
pch.quizShowAnswer
B — they measure different things: MI combines complexity with volume and line count, and the file is short — Use MI as a file-level trend and CC to find the specific function worth splitting. A small tangled function and a long simple one can score alike.
-
Running black over the messy function left its complexity at C (15). What follows?
Black removes an argument from code review. It does nothing about how many paths run through a function.
pch.quizShowAnswer
B — formatting and complexity are independent properties; readable is not the same as simple — Black removes an argument from code review. It does nothing about how many paths run through a function.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading