Code refactoring changes how existing source code is organized and leaves what it does exactly as it was. Fixing a bug changes behavior on purpose. Adding a feature does too. Performance tuning has its own goal, so it sits outside the definition as well.
The work happens in small steps, each one tested before the next. Rename a variable. Extract a function. Move a method to the class that owns the data it uses. Martin Fowler’s definition adds how you know it worked: the code gets easier to understand and cheaper to modify.
JetBrains Research surveyed 1,183 users of IntelliJ-based IDEs in 2021. 40.6% said they had restructured code almost every day in the month before the survey, and another 36.9% did it every week.
What Is Code Refactoring?

The idea is simple. You restructure existing source code to improve its internal design, and nothing a user or caller can observe changes.
Martin Fowler’s definition, published on refactoring.com, adds the purpose. The change should make software easier to understand and cheaper to modify.
The unit of work is tiny. A refactoring session is a long chain of small transformations that each preserve behavior, and each one gets checked before the next begins.
That’s what makes it safe on a codebase that already ships to users.
What Counts as Refactoring and What Does Not
Renaming a variable so it says what it holds is refactoring. So are these:
- Extracting a block into a named function
- Moving a method to the class whose data it uses
- Deleting code that nothing calls
Fixing a bug doesn’t count, and neither does adding a feature or tuning performance.
The test is the same each time. Behavior stays put, and the structure gets easier to read and change.
Fowler’s 2004 note on the refactoring boundary admits the edge is soft, because “observable behavior” can be read several ways.
A TU Munich paper on the roots of refactoring gives a concrete case. In a real-time system, execution time is observable, so a tidy-up that slows the code down is a behavior change.
Where the Term Comes From
William Opdyke’s 1992 PhD thesis at the University of Illinois at Urbana-Champaign, supervised by Ralph Johnson, defined a set of restructuring operations for object-oriented application frameworks. Each one preserves behavior as long as its preconditions hold, and the thesis centers on automating them (University of Illinois IDEALS record).
Fowler’s book followed in 1999, written with Kent Beck, John Brant, Opdyke and Don Roberts. The second edition arrived in 2018 with JavaScript examples and a reworked catalog.
Ward Cunningham and Kent Beck were among the first to see why it mattered. They worked in Smalltalk, which Fowler describes as an unusually friendly environment for the practice.
Strict Definition Versus Everyday Use
Among 328 Microsoft engineers surveyed, 46% never mentioned preserving behavior when asked to define refactoring (Kim, Zimmermann and Nagappan, FSE 2012).
Most described it as any transformation that makes code more readable, easier to maintain or faster. One respondent put it bluntly: strictly speaking behavior does not change, but in practice the work happens alongside feature additions and bug fixes.
Both uses are real. Fowler’s version is the stricter one, and it’s the one used from here on: a change counts when it leaves behavior alone and makes the code easier to understand or cheaper to modify.
A commit that also fixes a bug is two jobs, whatever its message says.
What Refactoring Delivers, According to the Data
Microsoft engineers credit refactoring with better readability, easier maintenance and more room to extend the code. The Windows modules refactored most often also lost more post-release defects than other changed modules. The evidence is correlational and covers one very large codebase, and the practice carries real costs in review and merge effort.
The Microsoft field study (Kim, Zimmermann and Nagappan, FSE 2012) has the most detailed numbers. 43% of the 328 surveyed engineers named improved readability as a benefit they had seen. A bigger share, 77%, said refactoring risks subtle bugs and functionality regression.
On the defect side, the 25% of Windows binaries refactored most often cut post-release defects by 12.2% more than other modified binaries between Vista and Windows 7. Engineers also self-reported about 13 hours a month on refactoring, close to 10% of their work.
The Windows refactoring team analyzed binary dependencies with an internal tool called MaX and assigned binaries to layers. A quality gate then blocked any commit that broke the layer map.
Correlation is the limit here. The authors state that confounders such as developer expertise and the features added in Windows 7 were not examined, so the study shows association, not cause.
The 2014 follow-up in IEEE Transactions on Software Engineering adds a wrinkle. The top 5% most-refactored modules cut inter-module dependencies and several complexity measures, yet grew in size more than the other 95%.
Earlier studies disagree with each other. Ratzinger and colleagues (2008) found defects fall when refactoring edits rise in the preceding period, while Weissgerber and Diehl (2006) found bug reports climbing after a high ratio of refactoring edits, as summarized by Kim et al.
Weissgerber and Diehl found that refactoring edits often occur together with other changes, and Kim et al. also cite earlier work linking incomplete or incorrect refactorings to bugs.
The economic case is older than the data. Ward Cunningham treated quick and dirty code as a form of technical debt, which charges interest as higher maintenance cost.
Fowler’s refactoring.com makes the matching point: refactoring lowers the cost of enhancements, because new work slows to a crawl without it.
Teams following Extreme Programming were told to refactor mercilessly through every phase of a project, a claim few studies had measured before 2012.
The costs are real too. Engineers complained about bigger check-ins that burden reviewers and merge conflicts after files move. Thin regression suites were another worry, since they leave no safety net.
Code Smells and Other Signals That Code Needs Refactoring

Teams usually spot trouble through code smells such as long methods, duplicated code, large classes and feature envy. A smell is only a surface sign of a deeper problem, so it starts an investigation, not a rewrite.
Named Smells
Kent Beck coined the term while helping Fowler with the 1999 book, according to Fowler’s 2006 note on code smells.
- Fowler gets suspicious of a Java method that runs to more than a dozen lines
- Duplicated code means the same logic lives in several places, so every fix gets repeated
- A large class is doing the work of two
- Feature envy shows up when a method uses another class’s data more than its own
- Data classes hold fields and no behavior, which raises the question of what behavior belongs there
Smells do not always mean trouble. Some long methods are fine, and Fowler’s advice is to look deeper before acting.
Complexity and Coupling Metrics
Static analysis puts numbers on what a reviewer senses. SonarQube’s documentation (version 10.4) defines cyclomatic complexity as a count of paths through a function. The counter goes up by one each time control flow splits, and every function starts at 1.
The same documentation defines cognitive complexity separately, as a measure of how hard the control flow is to understand. That is why the two scores can diverge.
Coupling counts how tightly modules depend on each other, and cohesion asks whether a module’s parts belong together. The Windows team measured coupling directly, as inter-module dependencies.
Even so, a high score only ranks a candidate. It can’t tell you whether changing it will pay off.
Moments That Justify Refactoring
The most common trigger is a feature the current structure makes awkward. Fowler calls that preparatory refactoring, after Kent Beck’s line “make the change easy, then make the easy change” (Fowler, 2015).
Then there’s the third copy of similar code. This is the rule of three, credited to Don Roberts. Do the thing once, wince at the duplication the second time, refactor on the third. Waiting for that third copy also keeps a team from locking in an abstraction too early.
Comprehension refactoring happens while you’re reading code you don’t understand yet. You restructure it as you work out what it does, so the understanding stays in the code instead of in someone’s head.
Code review is another natural checkpoint, and the review stage before a merge is where a reviewer can ask for tangled logic to be restructured before it ships.
Common Refactoring Techniques and the Smells They Fix

Fowler’s catalog gives each smell a matching move. The pairing turns “something is off here” into a concrete next step.
| Smell | Technique | What changes |
|---|---|---|
| Long method | Extract Function | A block becomes a named function |
| Large class | Extract Class | Part of the class moves to a new one |
| Feature envy | Move Function | The method moves to the data it uses |
| Long parameter list | Introduce Parameter Object | Related arguments become one object |
| Type-based conditionals | Replace Conditional with Polymorphism | Branches become subclasses or variants |
Some names changed in the 2018 second edition. Extract Method became Extract Function, Move Method became Move Function, and Rename Method, Add Parameter and Remove Parameter all fold into Change Function Declaration (martinfowler.com, 5 September 2018).
The second edition also added entries such as Rename Variable and Remove Dead Code, plus Replace Loop with Pipeline. Older tutorials still use the first-edition names, so search both.
Most Microsoft survey respondents said these low-level moves are rarely done alone, because a bigger architectural change usually sits behind them (Kim et al.).
Here is Extract Function plus Replace Loop with Pipeline on a small invoice routine. The output stays identical, which is exactly what a test suite would confirm. The original version comes first.
function printInvoice(order) {
let total = 0;
for (const item of order.items) {
total += item.price * item.qty;
}
if (order.coupon === "SAVE10") {
total = total - total * 0.1;
}
console.log("Customer: " + order.customer);
console.log("Total: " + total.toFixed(2));
}And here it is after both refactorings.
function printInvoice(order) {
const total = applyCoupon(subtotal(order.items), order.coupon);
console.log("Customer: " + order.customer);
console.log("Total: " + total.toFixed(2));
}
function subtotal(items) {
return items.reduce(function (sum, item) {
return sum + item.price * item.qty;
}, 0);
}
function applyCoupon(amount, coupon) {
return coupon === "SAVE10" ? amount - amount * 0.1 : amount;
}Things go wrong when several goals get bundled into one change. Kim et al. report larger check-ins that burden reviewers and collide with other people’s work.
Keep each commit to one named refactoring, so a reviewer can verify it as behavior-neutral.
How to Refactor Code Without Breaking It
Start from a passing test suite, apply one named refactoring at a time, rerun the tests after each step, and commit on green. When a step turns the suite red, revert it and take a smaller step instead of debugging forward.
- Run the full suite and confirm every test passes
- Commit that clean baseline
- Pick one named refactoring aimed at one smell
- Apply it in the smallest step that still compiles
- Rerun the tests, and revert if any fail
- Commit on green, naming the refactoring in the message
- Repeat, then open a pull request so a reviewer can read the sequence
What Must Be in Place First
In Fowler’s book, the first step in refactoring is a solid suite of tests around the code you are about to change (as summarized on the c2 wiki).
That means self-checking tests, a unit test suite (JUnit in the Java world) that reports pass or fail from one command. Git matters just as much, with a commit after every green run so any step can be undone. The suite also has to run often enough that a failure points at the last small change.
Fowler calls this self-testing code: one command, and the whole project checks itself.
A continuous integration server that reruns the suite on every push catches the failures a local run missed.
InfoQ’s 2014 report on Fowler’s Workflows of Refactoring counts the red-green-refactor loop of test-driven development as the first of his refactoring workflows. The refactor step comes only after a failing test has gone green.
Moves and renames can scramble file history in some version control setups (Kim et al.). Commit them apart from behavior edits so the history stays readable.
If there are no tests yet, write a few around the section you are about to change, refactor a little, and repeat (c2 wiki, The First Refactoring Step).
Skip that and the change is a guess, not a verified refactoring.
Which Tools Automate Refactoring?
Rename, extract, move and inline are the mechanical refactorings, and IDEs handle them well. Static analysis tools such as SonarQube find the candidates. Neither replaces a test run.
IDE Refactoring Commands
The menus in these tools still use Extract Method, the first-edition name.
- IntelliJ IDEA offers Extract Method, Extract Variable, Rename and Safe Delete, with a preview window and a conflicts dialog (help, version 2024.3)
- Eclipse JDT has Extract Method, Inline Local Variable, Rename and Move, which update dependent files, with preview, undo and redo
- Visual Studio covers Extract Method for C# and Visual Basic and Extract local function for C#, reached through the Quick Actions menu on Ctrl+. (Microsoft Learn)
- ReSharper adds a group of Move refactorings, including Move to Folder and Move Instance Method, plus Extract Superclass for .NET code (documentation dated 23 September 2024)
VS Code works differently. Refactorings arrive as Code Actions from the language service, so the list depends on the language.
Rename Symbol (F2) works across files in languages that support it, and a Refactor Preview panel shows the edits before they land.
Tools are not a safety guarantee. Soares, Gheyi and Massoni (IEEE Transactions on Software Engineering, 2013) pushed 153,444 transformations through 29 refactorings in Eclipse JDT 3.7, NetBeans 7.0.1 and the JastAdd Refactoring Tools, and found 57 bugs that broke compilation and 63 that changed behavior.
IntelliJ IDEA and Visual Studio were not part of that study.
Developers mostly refactor by hand anyway. Engineers in the Microsoft survey did 86% of their refactoring manually on average (Kim et al.).
Rename was the exception. For the other refactorings in the survey, more than half of respondents worked without the tool.
My rule of thumb is to let the IDE handle local, mechanical moves such as rename and extract. Check cross-file moves and signature changes by hand, and run the suite either way.
Static Analysis Tools
SonarQube reports complexity metrics for every function (documentation version 10.4), which makes it a ranking tool for candidates.
A drop in cyclomatic complexity after an Extract Function is a useful check, not proof. The tests still decide whether behavior held.
Refactor or Rewrite: How to Decide, and When Refactoring Fails

Refactor when the existing code works and can be tested, and the product still has to ship. Rewrite a piece of it when the design itself blocks every change.
Refactoring Versus Rewriting
A rewrite replaces the codebase, so it does not count as refactoring. The two differ in what they risk and when they pay back.
| Criterion | Refactor | Rewrite |
|---|---|---|
| Tests needed | Around each section you touch | Around the whole replacement |
| Release impact | Ships with normal releases | Old version keeps shipping meanwhile |
| Embedded knowledge | Stays in the code | Has to be rediscovered |
| First payoff | The next green commit | Parity with the old product |
Refactoring pays in small amounts early. A rewrite pays everything late, if it pays at all.
Documented Risks
Netscape went almost three years between version 4.0 and the first beta of version 6.0 after deciding to rewrite from scratch, and there was never a version 5.0 (Joel Spolsky, 2000).
Meanwhile the old code kept shipping, and the company could not react to what the market wanted because it had no shippable new code.
Spolsky’s rule is absolute. Netguru’s rewrite guide takes a narrower line: a rewrite beats refactoring only once technical debt has compounded to the point where developers spend more time patching than shipping features.
Even Spolsky’s essay concedes the point at small scale. Netscape’s slow rendering code touched only part of the product and could be rewritten without rewriting everything, so the real disagreement is about scale, not about whether a rewrite is ever right.
Refactoring carries its own risks. Breaking callers of a public interface is one. Dig and Johnson (2005), as cited by Kim et al., found that 80% of the changes that break client applications are API-level refactorings. Over-engineering is the other, and engineers described building architecture that no feature needed.
A rewrite throws away the fixes and edge cases embedded in the old code, and it leaves two versions to keep alive until the new one catches up. Users also learn to rely on old behavior, even the odd parts.
That last point is why regression testing against the old build is one way to see where a replacement drifts.
When Refactoring Does Not Apply
- The code has no tests and no documented scenarios to check against
- Callers outside your repository depend on the interface you would change
- A release deadline is close (Fowler’s 1997 lecture slides list this as a time not to refactor)
- The design needs replacing, not tidying
A rewrite promises the whole maintainability gain at once, and delivers it only if the new code reaches parity.
The short version: refactor if the code passes tests or can get them, callers tolerate small continuous change, and the next feature is blocked by structure rather than by the platform. If any condition fails, replace that module and leave the rest standing.
Code Refactoring FAQ
How much does refactoring cost?
No single figure applies, since cost depends on codebase size, age and test coverage. One measured data point comes from a 2020 case study of 185 junior developers (Lenarduzzi and colleagues). Refactoring items flagged by SonarQube never took more than 50% of the tool’s time estimate.
Can AI tools refactor code?
Yes, and the same rule applies as for IDE commands: the output counts as a refactoring only if behavior stays the same. An assistant such as GitHub’s Copilot can propose the edit, but the test suite decides whether to keep it.
How often should a team refactor?
Continuously, in small doses. Fowler’s refactoring.com describes it as part of day-to-day programming.
Larger problem areas get planned refactoring, and replacing a big module becomes long-term refactoring spread across several iterations (InfoQ, 2014).
Is refactoring the same as code cleanup, restructuring or reengineering?
Not quite. Cleanup is the casual word, while refactoring is the disciplined version with named steps, each checked by tests.
Restructuring is the broader family, and refactoring is the subset that guarantees behavior stays the same. Reengineering reshapes a whole system and is free to change what it does.
How Much Refactoring Is Enough Before Feature Work?
Code refactoring has done its job once the next change is easy to make. Restructuring beyond that point adds review time and merge risk without a payoff the team can point to.
Spend the effort on the code the pending feature will touch first, then on the files that change most often. Everything else can wait until a smell blocks a task.
The order follows where the payoff lands. A restructured file nobody edits repays nothing, while one edited every sprint repays on every change.
The trade-off is a slower first feature in exchange for cheaper later ones.
Target code with no safety net adds a second up-front cost: writing unit tests before the first move.
- Go Cheat Sheet - November 9, 2026
- Build or White-Label Software? How Software Teams Decide What to Resell in 2026 - November 9, 2026
- Notepad++ Column Mode: Edit 100 Lines at the Same Time - November 7, 2026



