Churn vs Complexity: Find Your Real Hotspots
Complex code is only dangerous when it changes. Cross churn against complexity to find the hotspots in your repo that actually deserve a refactor first.
Complexity alone does not tell you where to refactor. A gnarly, tangled file that nobody has touched in three years is not hurting you. It sits there, ugly and stable, and it costs you nothing. The file that costs you is the one that is both complex and changes constantly, because every change is expensive, risky, and slow. Cross churn against complexity and the real hotspots in your repo light up. That intersection is where your refactor budget should go, and almost nowhere else.
Why complexity by itself is a bad signal
Static complexity metrics, cyclomatic complexity, nesting depth, function length, all measure how hard code is to understand in the abstract. That is useful, but it is only half the equation. A complexity score treats a frozen legacy module and a daily-churning core file as equally urgent. They are not. Refactoring the frozen one is vanity. You spend a week making stable code prettier, ship a chance of new bugs, and change nothing about your delivery speed. I made the broader version of this argument in Why Code Metrics Mislead You About Quality. A number without context points you at the wrong file.
What churn adds to the picture
Churn is how often a file changes, measured straight from your version control history. It is a proxy for something you cannot get from the code itself: how central this file is to the work you actually do. High churn means this is where features land, where bugs get fixed, where your team spends its days. Now overlay complexity. A file that is high churn and high complexity is a tax on every single change you make. That is the definition of a hotspot, and it is the single most actionable output of running repo intelligence before you refactor.
The four quadrants tell you what to do:
- Low churn, low complexity. Fine. Leave it.
- Low churn, high complexity. Ugly but stable. Ignore it until it starts changing.
- High churn, low complexity. Healthy core. This is what good code under active development looks like.
- High churn, high complexity. Your hotspots. Refactor here first.
That last quadrant is usually a small number of files. In most repos, a handful of files account for a wild share of both change activity and bug density. Find those and you have found where a week of refactoring actually pays back.
How to build the hotspot map
You need two data sources and a way to multiply them.
Churn comes from git log. Count commits per file over a meaningful window, say the last six to twelve months, not all history. Recent churn matters more than ancient churn because it predicts near-future change. Weight by lines changed if you want to be precise, but raw commit count per file is a strong start.
Complexity comes from static analysis. Cyclomatic complexity per function, aggregated to the file, is the standard. Add nesting depth and file length if you want a richer score.
The hotspot score is roughly churn times complexity. Rank descending. The top of that list is your work order. This is exactly the kind of prioritization I mean when I say tech debt is a balance sheet, not a confession. You are allocating a limited budget against the debts with the highest carrying cost.
Doing this by hand is tedious and easy to get wrong, which is why a tool like ReformCode computes the churn-by-complexity map across the whole repo and ranks the hotspots for you. The value is not the two metrics in isolation. Anyone can get those. The value is the crossing, ranked, so you argue about the top five files instead of the whole codebase.
Using hotspots to prioritize, not just to know
A hotspot map changes the refactor conversation from taste to evidence. Instead of a senior engineer insisting the auth module is a mess based on vibes, you have a ranked list showing that the auth module is your number one hotspot: it changes weekly and it is the most complex file in the tree. That is a case a non-technical stakeholder can approve. It ties refactor effort directly to delivery cost.
It also protects you from the opposite mistake, refactoring the thing that annoys you instead of the thing that costs you. The most annoying code is often the low-churn, high-complexity file, because you remember the one time you had to touch it and it hurt. But you only touch it once a year. The quiet high-churn file that everyone edits and nobody complains about is the one silently slowing every release.
Refactor prioritization is a scheduling problem, not a quality problem. You will never refactor everything. You should not try. The churn-versus-complexity hotspot map tells you the exact order that returns the most delivery speed per hour spent, and it does it from data you already have sitting in your git history.