Every codebase has one: a module nobody wants to touch, poorly tested, tangled logic, and just risky enough that it keeps getting deprioritized sprint after sprint in favor of anything with a clearer, safer path to done. This quarter we used AI assistance throughout a refactor of exactly that kind of module, a pricing calculation engine that had accumulated six years of special cases, and want to share how it actually helped, where it fell short, and what surprised us about the process.
Why this module had been avoided for years
The module in question handled pricing logic for a product with a genuinely complicated set of business rules, regional pricing variations, promotional overlays, grandfathered legacy pricing for long-standing customers, and each rule had been added by a different engineer at a different time, usually under deadline pressure, with essentially no test coverage protecting the existing behavior. Nobody currently on the team had written the original version, and the handful of people who understood any given corner of it had learned that understanding through painful trial and error rather than documentation, because none existed.
That combination, business-critical, poorly understood, untested, is exactly the profile of code that accumulates risk the longest specifically because touching it feels dangerous. Every sprint planning conversation where this module came up ended the same way: acknowledged as a problem, deprioritized in favor of something with a clearer scope and a safer blast radius.
Where AI assistance actually helped
Generating a first pass of test coverage for the existing, untested behavior before touching any logic was the single most valuable use of the assistant, since writing tedious characterization tests by hand is exactly the kind of unglamorous work people avoid even when they know it's the responsible first step. We fed the assistant individual functions along with some sample inputs and asked it to generate tests that captured current behavior, bugs and all, without judging whether that behavior was correct. That distinction mattered enormously; the goal wasn't to write tests for what the code should do, it was to write a safety net for whatever it currently did, so we'd know immediately if a refactor accidentally changed observable behavior.
Asking the assistant to explain unfamiliar sections of the code before refactoring them surfaced assumptions the original author had left undocumented, some of which turned out to be outdated. In one case, a conditional branch that looked load-bearing turned out to be handling a promotional pricing scheme that had been discontinued three years earlier; the assistant's explanation of what the code appeared to be doing prompted someone to check whether the business rule was even still active, which it wasn't, and we removed an entire branch of logic that had been silently dead weight.
- Generating a first pass of test coverage for the existing, untested behavior before touching any logic was the single most valuable use of the assistant, since writing tedious characterization tests by hand is exactly the kind of work people avoid.
- Asking the assistant to explain unfamiliar sections of the code before refactoring them surfaced assumptions the original author had left undocumented, some of which turned out to be outdated.
- Generating a plain-language summary of each function's behavior before refactoring gave the team a shared, quickly reviewable reference that didn't previously exist anywhere.
Where it fell short
The actual refactoring suggestions needed heavy editing; the assistant was good at local improvements, cleaner variable names, extracting a repeated block into a helper function, but had no sense of the module's broader architectural intent or the business context behind why certain logic existed in a particular shape. Suggestions that looked clean in isolation sometimes flattened a distinction that mattered, collapsing two conditionals that happened to produce the same output for the test cases at hand but represented genuinely different business rules that could diverge under inputs the tests hadn't covered.
This meant every suggested change still needed a human who understood the domain to evaluate it, which is roughly what we expected going in but is worth stating plainly: the assistant accelerated the mechanical parts of the work considerably but did not reduce the need for domain expertise in reviewing the result. A team without anyone who deeply understood the pricing rules would have been in a worse position trying to lean on the assistant's suggestions uncritically.
The real payoff
Having AI-generated characterization tests in place gave the team enough confidence to actually start the refactor, which is arguably the bigger win than any specific line of suggested code. The psychological barrier to touching this module had been the primary blocker for years, not a lack of available engineering time, and removing that barrier is what actually got a long-deprioritized backlog item done. Once the safety net existed, the refactor itself, done manually with the assistant helping generate first-draft suggestions along the way, took about three weeks, comfortably within a single quarter, after years of the same work being estimated at "too risky to schedule."
What we'd do differently
AI assistance didn't do the refactor for us, but it removed the biggest psychological barrier: the fear of touching untested, poorly understood code. That alone made a years-old backlog item finally happen, and we'd now recommend the same characterization-tests-first approach as a default starting point for any similarly risky legacy module. If we were doing this again, we'd budget explicit time for domain experts to review generated tests for correctness gaps rather than assuming test generation alone was sufficient, since we did find a small number of generated tests that encoded a bug as expected behavior, which is exactly the failure mode you'd predict from a tool that captures existing behavior rather than correct behavior, and which underscores why a human who understands the business logic remains essential to the process.
How we structured the work day to day
Rather than treating the assistant as a tool one engineer used privately, we made the refactor a paired effort where one engineer drove the assistant interactively while a second, more senior engineer with deeper pricing-domain knowledge reviewed suggestions in near real time. That pairing structure mattered more than we initially expected. The driving engineer could move quickly through mechanical suggestions, while the domain expert caught the subtler issues, a suggested simplification that looked correct but silently changed rounding behavior on promotional discounts, before they ever reached a pull request. Splitting the two roles across two people rather than asking one engineer to hold both the tool-driving and domain-verification responsibilities simultaneously reduced the rate of subtly wrong suggestions slipping through review.
Rebuilding documentation alongside the tests
A secondary benefit we hadn't fully planned for going in: generating plain-language summaries of each function's behavior, as part of the characterization-test-writing process, produced the first real documentation this module had ever had. We collected those summaries into a short internal reference document rather than letting them live only in code comments, which has already been useful independent of the refactor itself, new engineers onboarding onto the pricing module now start from that document rather than reverse-engineering behavior from scratch the way every previous engineer had to.
Deciding what not to touch
Not every tangled corner of the module got refactored, and deciding where to draw the line took real judgment. A section handling a legacy pricing scheme for a small number of long-standing enterprise contracts was technically eligible for cleanup, but the business risk of subtly changing behavior for those specific, high-value accounts outweighed the code-quality benefit, especially given how rarely that code path actually executes. We left it alone, well-documented but structurally unchanged, and flagged it explicitly as a deliberate exception rather than an oversight, so a future team revisiting this code understands why it looks different from the rest of the now-refactored module.
Looking ahead to more autonomous tooling
The assistance we used this quarter was fundamentally interactive: a human directing focused requests and reviewing every suggestion before accepting it, rather than an agent independently making multi-file changes across the module on its own. That felt like the right level of autonomy for a module this risky, and we're watching how quickly more autonomous, multi-step tooling matures before considering it for anything with this much business-critical surface area. For now, the model that worked, an assistant accelerating specific, human-directed tasks rather than owning the refactor end to end, is the one we'd recommend to any team facing their own version of this problem.