Katabench
Start free
← All posts

Clean code is a vibe until you measure it

"Clean code" is the most agreed-upon, least enforceable phrase in software. Everyone wants it. Everyone has a different idea of what it is. So code review becomes a negotiation: one reviewer calls a method "a bit much," the author disagrees, a senior person waves it through because it's Friday, and the method ships. Multiply that by a thousand merges and you get the codebase you have now.

The problem isn't that people don't care about quality. It's that "clean" is a feeling, and you can't hold a line on a feeling. You can hold a line on a number. So here are four properties of code that are genuinely worth caring about, that you can measure automatically, and that map to real maintenance pain.

Cyclomatic complexity: how many ways through

Cyclomatic complexity counts the number of independent paths through a method. Every if, for, while, case, &&, ||, and catch adds a branch, and each branch is another path you have to hold in your head and, ideally, write a test for.

It's the closest thing we have to a "how hard is this to reason about" number. A method with a complexity of 3 is a glance. A method of 25 is a maze, and it's also telling you it needs at least 25 tests to cover every path, which nobody ever writes.

To see what the number is counting, draw a small method as a graph, once as a four-branch conditional ladder and once refactored to a lookup: every decision diamond opens another path.

The conditional ladder

entry ? return ? return ? return ? return throw

decision points: 4, complexity 5

The table lookup

entry ? throw rate = table[key] return

decision points: 1, complexity 2

Same behavior, same tests. Five paths collapsed to two, and the graph is the receipt.

Reasonable threshold: keep methods under ~10. Treat anything above ~15 as a refactor candidate, not a style nit.

Nesting depth: the rightward drift

Nesting depth is how many levels of indentation you're under at the deepest point. Each level is another condition you have to keep true in your head to understand the line in front of you. Code that drifts steadily to the right is code that's accumulating context you can't offload.

The fix is almost always the same move: invert the condition and return early so the happy path stays flat. Guard clauses are cheap and they pay out every time someone reads the method.

Reasonable threshold: 3 levels is comfortable. Past 4, you're asking the reader to juggle too much.

Method length: the scroll test

Length is the bluntest metric, and it's still useful. A method you can't see without scrolling is a method you can't reason about as a single unit. Long methods also tend to do several things, which is the actual problem; length is just the symptom that's easy to count.

Reasonable threshold: most methods fit comfortably under ~25-30 lines. The number matters less than the trend, but the trend is hard to argue with.

Duplication: the same bug in three places

Duplication is the metric people underrate. Copy-pasted logic isn't just ugly, it's a maintenance liability: when the rule changes, you have to find and fix every copy, and you will miss one. The one you miss is the bug. Tooling can detect near-identical blocks above a token threshold, which catches the copy-paste-and-tweak pattern that hides from a naive string match.

Reasonable threshold: zero tolerance for non-trivial duplicated blocks. Small repeated literals are fine; repeated logic is a smell.

What this looks like in C#

Here's a method that scores badly on all four at once. It's the kind of thing that grows one if at a time and nobody notices until it's a fortress.

public decimal Price(Order order)
{
    decimal total = 0;
    if (order != null)
    {
        if (order.Items != null && order.Items.Count > 0)
        {
            foreach (var item in order.Items)
            {
                if (item.Quantity > 0)
                {
                    var line = item.UnitPrice * item.Quantity;
                    if (item.Category == "Books")
                    {
                        line = line * 0.9m; // 10% off books
                    }
                    else if (item.Category == "Electronics")
                    {
                        if (line > 1000)
                        {
                            line = line * 0.95m; // bulk electronics
                        }
                    }
                    total += line;
                }
            }
        }
    }
    return total;
}

Five levels of nesting, a complexity closing in on double digits, a category rule buried inside a loop, and it's already hard to test. Now the same behavior, flattened with guard clauses and the discount rule pulled into its own focused method.

public decimal Price(Order order)
{
    if (order?.Items is null)
    {
        return 0;
    }

    return order.Items
        .Where(item => item.Quantity > 0)
        .Sum(DiscountedLineTotal);
}

private static decimal DiscountedLineTotal(OrderItem item)
{
    var line = item.UnitPrice * item.Quantity;

    return item.Category switch
    {
        "Books" => line * 0.9m,
        "Electronics" when line > 1000 => line * 0.95m,
        _ => line,
    };
}

Same result, same edge cases. But complexity drops into the low single digits per method, nesting flattens to one level, each method is a screenful at most, and the pricing rules now live in one readable switch that a new teammate can scan in seconds. Crucially, none of that is a matter of opinion. The numbers moved, and you can prove it.

Why measuring is the whole game

Here's the thesis: quality you can't measure, you can't hold a line on. A threshold turns "this feels heavy" into "this method is at 18, the gate is 10, here's the diff that fixes it." The conversation stops being about taste and starts being about a number everyone can see. That's also what makes it teachable, because you get a tight feedback loop instead of a reviewer's mood.

The catch with the refactor above is the part metrics alone can't check: did I keep the behavior? A pricing method is a perfect trap. It's easy to flatten the nesting and silently drop the line > 1000 condition, ship cleaner code, and break the bill.

That two-sided constraint is exactly what Katabench's refactoring track is built around. Every submission runs the behavioral tests and re-measures the code with Roslyn against authored thresholds: cyclomatic complexity, nesting depth, method length, duplication. You don't pass by making it correct. You don't pass by making it tidy. You pass when the tests are green and every metric is under the line, which is the real job.

If you want to see exactly how the gates are scored and what the thresholds look like in practice, that's written up in the grading docs. And if you'd rather build the instinct than read about it, the refactoring track is on Pro. Reps with a number attached are how "clean" stops being a vibe and starts being a habit.

Get new puzzles and .NET tips in your inbox

A short note when fresh kata land, plus the C# and performance tricks behind the grading. No spam, unsubscribe anytime.