← Back to blog

automated-refactoring-and-code-smells-detection

title: "I Ran Automated Refactoring Across a 200k Line Repo — Here's What Actually Broke and Fixed" description: "What I found running automated code smell detection and refactoring on a real production codebase, and where the tools genuinely helped versus where I still had to step in." date: "2026-07-28" slug: "automated-refactoring-code-smells-detection" tags: ["code smells", "refactoring", "static analysis", "CI/CD", "CodeLazr.com"]

I Ran Automated Refactoring Across a 200k Line Repo — Here's What Actually Broke and Fixed

I inherited a service last year with a process_order method that had grown to 140 lines over three years of "just add one more thing here." Nobody had touched the core structure because nobody wanted to be the one who broke checkout. That's the pattern I keep running into — code smells don't fail tests, so they just sit there accumulating until someone finally has to deal with them under pressure.

That codebase is what pushed me to actually build tooling around this instead of doing it by hand every quarter. Here's what I learned running detection and automated fixes across a real 200k LOC repo, including where it worked and where I had to override it.

What I Was Actually Looking For

A code smell isn't a bug — tests pass, deploys succeed, nothing's on fire. It's a signal that the design underneath is getting harder to work with. I still go back to Martin Fowler's Refactoring Catalog as the baseline vocabulary for this, because it names things precisely enough that a team can actually agree on what they're looking at:

Why Our PR Reviews Never Caught This

I went back through six months of our PR history looking for where these smells actually got flagged, and the answer was: almost never in review. Reviewers caught missing null checks, bad variable names, obvious logic errors — the stuff visible in a diff. Nobody flagged that three methods in a 300-line PR had nothing to do with each other structurally. That's not a knock on the reviewers; structural rot doesn't show up as a diff, it shows up as a shape, and humans are bad at spotting shapes across scrolling.

That gap is exactly where I ignored things too long on the order service. Every feature we bolted onto that class made the next feature slower to ship. By the time I actually measured it, a change that should've taken a day was taking most of a week, and it wasn't because the work was hard — it was because finding the right place to make the change was hard.

What I Ran and What It Actually Found

I already had SonarQube running in CI, and it was catching the obvious stuff — cyclomatic complexity, duplicated blocks, the rule-based patterns it's built for. What it wasn't doing was closing the loop. It told me process_order was too long. It did not fix process_order. I still had to open the file, decide how to split it, write the extraction, and make sure I hadn't broken the payment flow in the process.

That's the gap I built CodeLazr.com to close. When I ran it across the same repo, the difference wasn't detection — Sonar already gave me that — it was that CodeLazr generated an actual patch and staged it as a diff for me to review, instead of leaving me to do the extraction by hand.

Here's roughly what that method looked like before and after:

python

Before

def process_order(order): validate_customer(order.customer_id) check_inventory(order.items) calculate_total(order.items) apply_discounts(order) charge_payment(order) update_fulfillment(order) send_confirmation(order) log_audit(order)

After

def process_order(order): validate_and_prepare(order) charge_and_fulfill(order) notify_and_log(order)

Nothing exotic — it's the same Extract Method refactor I would've done manually. The value was in not having to do it manually forty more times across the rest of the codebase.

Where Multi-Model Fallback Actually Mattered

The thing I didn't expect: a single model didn't get the fix right on the first try most of the time. On one batch of refactoring tasks I ran against our Kanban service, a single model landed a correct first-try fix about a third of the time. That's not great on its own — a 33% success rate means you're babysitting two-thirds of the queue.

Chaining a second model as a fallback when the first one's patch didn't check out took that to 100% across the same batch. I don't want to oversell what that means — "success" here means the diff compiled, passed tests, and didn't change behavior, not that every refactor was elegant. But going from a coin flip to a sure thing on structural fixes changed how much I trusted the pipeline to run unattended overnight.

Every one of those fixes still landed as a diff, not a direct push to main. I've been burned before by tools that auto-commit, and I don't trust any automated refactor enough to skip review — the point isn't removing the human, it's removing the tedious 80% so the remaining 20% of judgment calls get my actual attention.

What I Set as Gates in CI

Once detection was reliable, I moved it into the pipeline instead of running it ad hoc. The thresholds I settled on, after some trial and error:

A PR that trips one of these doesn't get auto-rejected, it gets blocked until someone either fixes it or writes down why the exception is justified. That second part matters — without a documented reason, "just this once" becomes the norm within a month.

What the Cost Actually Looked Like

This is the part I was most skeptical of going in. I expected running automated detection and fixes on every single PR to be either too slow or too expensive to justify. In practice, individual fixes landed in the sub-penny range — a pricing-logic fix ran about $0.002, a debug-from-error-message task about $0.006. You can check current numbers on CodeLazr Pricing, but at that range, running checks on every PR costs less than the coffee someone buys during a fifteen-minute manual review.

The part I use more than I expected: the dashboard showing which files generate the most refactoring flags over time. It's not subtle — the order service showed up at the top for months, which was just confirmation of something I already suspected but hadn't quantified.

What I'd Actually Recommend

If you're already running SonarQube or a similar linter, don't rip it out — it's still doing real work on rule-based detection across 30+ languages, and that breadth is hard to match. What I'd add on top is something that closes the loop from "here's the problem" to "here's a reviewable fix," because the gap between those two is where most technical debt actually lives — not in the absence of detection, but in nobody having the time to act on what detection already told them.

The teams I've seen get real value out of this didn't replace human review. They just stopped spending review time on mechanical extraction and moved it to judging whether the extraction made sense. That's a better use of a senior engineer's attention than manually splitting a 140-line function for the fourth time this year.

automated-refactoring-and-code-smells-detection | codelazr.com