Refactoring: Beyond "it works" and when AI tools are most valuable

Recap from a Sopra Steria Bulgaria knowledge-sharing session

| minute read

Picture 10,000 lines of code that nobody fully understands. There is no documentation and no test suite. The developers who wrote it left years ago, and the new team has inherited a system that works, mostly, as long as nobody touches it.

Most engineers meet code like this sooner or later. At a recent internal session, Nina Nikolova, Senior Lead Software Developer at Sopra Steria Bulgaria, showed colleagues how to refactor large legacy projects without breaking them. Then she asked whether any of it still matters when AI can rewrite code in minutes, and tested it. Told to "clean up this code", a coding agent finished in two minutes for 46 cents and wrote no tests. With a plugin that made it plan, write tests and check its own work, the same job took ten minutes and cost five dollars.

Her conclusion: AI makes code discipline more important, because an agent copies whatever patterns it finds in the codebase.

Refactoring: what it is and why it matters 

Refactoring means improving the internal design of existing code without changing what the software does. Before and after, the system returns exactly the same results, and the end user notices nothing.

It can be as small as one line:

 

The behavior is identical. The difference is that the next developer can read it.

So why spend time on code that already works? Nina voiced the objection herself:

"Nobody pays us to write beautiful code. They pay us for code that works."

Her answer is that well-structured code is cheaper to live with. Features ship faster, fewer defects slip through, and new team members get productive sooner. The point of refactoring is to lower the cost of the next change.

Technical debt is the extra cost every future change carries because of shortcuts taken earlier. Refactoring is how teams pay it down.

The smells that keep coming back

Kent Beck coined the term code smell, and Martin Fowler's book Refactoring made it standard vocabulary: a pattern that is not a bug, but signals that the design could be better. Nina focused on the most common smells, which are also the easiest to fix and, somehow, the ones that keep returning.

A long method grows two lines at a time, because adding two more always feels quicker than creating a new method. Her rule of thumb: if a block of code needs a comment to explain it, it should become a method whose name does the explaining. Fowler calls this Extract Method.

Duplicated code looks harmless until someone finds a bug. By then the copied lines have been copied again, and the fix has to land in every copy.

The God Object does everything. Her demo class managed books, members, borrowing, files and reports, each of which deserved a class of its own. It is a textbook break of the single responsibility principle.

Long parameter lists are a daily sight. Nina regularly meets methods taking 10 or 12 arguments, often all strings, where nobody can tell which value goes where. Grouping related values into one object, such as a single shipping address in place of five separate strings, makes the code readable and much harder to misuse. Fowler's name for this fix is Introduce Parameter Object.

A switch statement that branches on type means every new type requires editing the same method. Fowler's fix is Replace Conditional with Polymorphism: separate classes behind a shared interface.

Nested conditions are her personal favorite. SonarQube, running inside Nina's IDE, flagged one of her demo methods with a cognitive complexity of 20. The code analysis tools she works with ask developers to bring a method's complexity down to "the permitted 12". Nina thinks 12 is far too generous: 12 possible paths through a method means 12 outcomes to understand and test. The fix is often to return early with guard clauses instead of stacking one condition inside another.

Some developers worry that all these small methods will hurt performance. In most cases, the difference is negligible, and the gain in readability is large.

How to refactor safely 

"It's one line, what could break?"

Quite a lot, in a codebase nobody knows well. The only real protection is a set of automated tests that runs every time the code changes.

Writing those tests has a second benefit. Recording what a method returns today is often the fastest way to learn what it actually does. From there, the workflow is deliberately unglamorous: check the tests are readable and passing, make one small change, run the tests, commit, repeat. If coverage is low, start with the code you are about to change. Extract two lines, write a test for them, and move on.

In systems with thousands of classes and millions of lines, unit tests alone are not enough. Services depend on one another across libraries, so integration tests matter just as much.

And since no client will pause a project so the team can clean up, refactoring belongs in daily work. Nobody will ever schedule a sprint for it.

Why any of this still matters with AI 

If AI increasingly writes our code, why talk about principles from books published decades ago? 

Nina described a loop now common in AI-assisted development, taken from a conference talk she found while preparing. Developers write specifications instead of code and let the AI generate the implementation without reading it. When something breaks, they adjust the specification and generate again. Each round produces slightly worse code, until nobody knows what the system does and the team scraps it.

The Pragmatic Programmer calls this software entropy: every system drifts toward disorder unless someone keeps it in order. Teams have always caused it by changing code without considering the whole system. With AI, a single prompt can change a hundred classes, so the same decay happens at machine speed.

The popular line today is that code is cheap. A feature that once took a month can ship in a week. Nina argues that bad code now costs more. An agent forgets everything when the session closes, and the next time it starts, its context is the codebase. If one problem has been solved five different ways, the agent picks one more or less at random and repeats it. "Our codebase is the AI's training set," she said. On a clean codebase, the agent does good work. On a messy one, it multiplies the mess.

Industry data points the same way. GitClear analyzed 211 million lines of code and found that in 2024, code blocks with five or more duplicated lines rose roughly eightfold, while moved lines, which GitClear treats as a sign of refactoring, fell by almost 40%. It was the first year in which copy-pasted code outnumbered moved code. GitClear links the shift to the spread of AI coding assistants.

Handing the work to an agent

Agentic tools take a goal rather than a single instruction. The goal can be concrete, such as upgrading a library to its latest version, or as open as "refactor this code".

Done safely, the process follows the same discipline as manual refactoring. The agent makes a plan, reads the code and any context it has been given, then searches the rest of the codebase for classes that depend on what will change. It sums up what it found in a report, ranked by importance and ease of change. A person then approves the plan or corrects it. Only after that does the agent take a snapshot it can roll back to, create a patch and verify the result.


The loop can even run daily inside a CI/CD pipeline, making AI one more team member whose only job is improving the code. For now, a human stays in the loop.

Is AI good at refactoring code?

On a small class, yes, provided it works the way a careful developer would: plan first, test first, one step at a time. Nina tested this on her God Object demo, a simple class of about 50 lines.

On a fresh installation of Claude Code, with no plugins, she typed one vague instruction: "Clean up this code." The agent worked for about two minutes and cost roughly 46 cents. It split the God Object into several classes and did it reasonably well, but wrote no tests. A developer using the IDE's built-in refactoring tools, she noted, could have done it faster.

She then added Superpowers, a plugin with ready-made development skills for planning, test-driven development and code review. First she asked only for a plan. That took four minutes and cost 93 cents. The plan listed every file the agent would create or change, the tests it would write, and one rule: every extraction step had to follow the red-green-refactor cycle, going from a failing test to a passing one before a commit. She could check and approve all of it before a single line changed.

The agent then offered two ways to carry out the plan: sub-agent-driven, where each task runs in a fresh context, which is slower and more expensive but safer on a large codebase, or inline, in a single session, which suits a small class. Nina chose inline. The agent created a feature branch, wrote the tests and ran them after every extraction. It took ten minutes and cost five dollars. "It turned out quite well," she said.

Five dollars sounds cheap next to an hour of a developer's time. But it bought the refactoring of one 50-line class. Nina would not run the same process on a large codebase: she had already extended her token allowance twice that month. A colleague compared tokens to RAM 25 years ago, when buying more was not always an option.

Her conclusion was to keep the codebase tidy every day, so that less work ever needs handing to an agent.

What developers can take away?

Make refactoring a daily habit. Nina closed with the Boy Scout rule: leave the code a little cleaner than you found it. Small improvements made today cost far less than large ones postponed for months.

Protect behavior before changing it. Write the test first, change one thing, run the tests and commit. AI tools can now write the tests too, so not liking test-writing is no longer an excuse.

Aim for balance. Perfect code doesn't exist, and chasing it wastes time that could go into features. If something small can be improved quickly, improve it now.

Remember who the codebase teaches. Every future AI session learns from the patterns already there. Each clean method, consistent approach and meaningful name improves what the next agent produces.

Why it matters for our clients

Many of the systems our teams support for clients in banking, insurance, energy and public services have been running for years and will keep running for many more. Changing them safely is a large part of what our engineers do every day.

As AI tools enter that work, the state of the codebase decides how much they deliver. Agents do good work on code that people have kept in order, and every messy corner costs time and tokens to untangle. Keeping these systems in order remains an engineering responsibility.

To find out how our teams build and modernize software for clients, explore our

 

 

 Technology and Software Development

 

Search