15 min read · 9 self-checks · Updated June 2026

XP / Engineering Practices

Refactoring

The disciplined practice of improving internal code structure without changing external behaviour, keeping the codebase maintainable and reducing technical debt.

Junior Senior Test Lead

What it is

Refactoring is the process of restructuring existing code without changing its observable behaviour. It is not rewriting; it is not adding features; it is cleaning the kitchen while dinner is being served. The goal is to make the code easier to understand, cheaper to modify, and safer to extend.

Refactoring is a continuous activity, not a project. The Boy Scout Rule states: leave the code cleaner than you found it. When you touch a file to add a feature, spend a few minutes improving its structure. Over months, this compounds into a codebase that stays healthy rather than decaying.

Effective refactoring requires a test safety net. Without tests, you cannot prove that behaviour has not changed. With tests, refactoring becomes a low-risk, mechanical process of small, verifiable steps.

Refactoring is not rewriting. Rewriting discards working code and starts over. Refactoring preserves behaviour while improving structure. If you are deleting files and rebuilding from scratch, you are not refactoring.

When to use it

  • Continuously: Every time you read code that is harder to understand than it should be, refactor it.
  • Before adding features to complex areas: Clean the structure first, then add the new behaviour. This is the preparatory refactoring pattern.
  • After code review: When reviewers flag unclear names or convoluted logic, refactor rather than documenting around the mess.
  • When fixing bugs: Bugs cluster in confusing code. Refactoring the surrounding structure often reveals the root cause.

Tip: If a method takes you more than a minute to understand, it is telling you it wants to be refactored. Listen to the code.

Key concepts

Code Smells

Code smells are surface indicators of deeper problems: long methods, duplicated logic, mysterious names, classes that do too much. They are not bugs, but they signal that the design is resisting change. Martin Fowler’s Refactoring catalogues dozens of smells and the refactorings that cure them.

Extract Method

The most common refactoring: take a block of code that does one thing, give it a descriptive name, and turn it into a method. This reduces duplication, improves readability, and makes the code self-documenting.

Rename Variable

Do not underestimate the power of a good name. A variable called data tells you nothing. A variable called unprocessedInvoiceLines tells you exactly what it contains and what state it is in. Renaming is refactoring, and it is often the highest-impact change you can make.

Boy Scout Rule

Leave every file cleaner than you found it. This does not mean rewriting the whole file. It means: if you see a misleading name, rename it. If you see duplication, extract it. Small acts, repeated, prevent rot.

Test Safety Net

Refactoring without tests is like juggling flaming torches blindfolded. Tests prove that behaviour is preserved. If your codebase lacks tests, write them around the area you intend to refactor before touching the structure.

Smell Refactoring Result
Long Method Extract Method Smaller, named methods that read like prose
Duplicated Code Extract Class / Method Single source of truth, easier maintenance
Poor Names Rename Variable / Method Self-documenting code, faster onboarding

Common pitfalls

  • Refactoring without tests. This is the fastest way to introduce regressions. Write tests first, then refactor.
  • Large “refactoring projects.” Declaring a two-week sprint for refactoring usually means the codebase is already in crisis. Refactor continuously instead.
  • Changing behaviour. If you fix a bug while refactoring, you have changed behaviour. Finish the refactoring first, then fix the bug in a separate commit.
  • Gold plating. Refactoring is not an excuse to add features, switch frameworks, or chase perfection. Stop when the code is clean enough.
  • Not explaining value to stakeholders. Non-technical stakeholders see refactoring as “rewriting working code.” Explain it as reducing future cost and risk, not as optional engineering hygiene.

Anti-pattern: A pull request titled “Refactoring” that touches fifty files and adds three features is not refactoring. It is a rewrite in disguise. Keep refactoring commits small and focused.

NZ context

New Zealand has a strong project-based consultancy culture. A team builds a system, delivers it, and moves on to the next client. In this model, neglected refactoring becomes a significant cost for the client who inherits the codebase. What was a quick delivery for the consultancy becomes a maintenance burden for the in-house team.

Professional NZ consultancies differentiate themselves by leaving clean, well-factored code behind. This builds reputation, generates follow-on work, and reduces the risk of being blamed for a “legacy” system six months later. For in-house teams, continuous refactoring protects the organisation’s investment in software assets.

Career level guidance

Level Focus Milestones
Junior Rename variables; extract small methods; follow the Boy Scout Rule Can perform Extract Method safely; understands when to stop; writes tests before refactoring
Senior Refactor across module boundaries; remove duplication at architecture level; mentor juniors Leads codebase-wide clean-ups; identifies systemic smells; balances refactoring velocity with feature delivery
Test Lead Ensure test coverage supports refactoring; measure technical debt; advocate for time allocation Tracks code churn and defect rates pre/post-refactoring; secures stakeholder buy-in for continuous refactoring; audits test safety nets

Tip for test leads: Measure refactoring success by how quickly new developers can make their first safe change to a module, not by lines of code cleaned. Maintainability is the metric that matters.

Industry Reality

🏭 What you actually encounter on the job
  • Most teams refactor reactively rather than continuously — debt accumulates until a new feature becomes nearly impossible to add, then management allocates a "clean-up sprint" that is still never quite enough.
  • In NZ consultancy projects, refactoring is often negotiated out of scope during delivery. The senior dev knows the code needs it; the client wants the feature by Friday. The result is clean-looking features sitting on a fragile foundation.
  • Pull requests labelled "refactoring" that also fix bugs, add logging, or switch libraries are common. This conflation makes code review harder and regression risk invisible — experienced practitioners learn to split these into separate commits regardless of the pressure to ship fast.
  • Automated refactoring tools (IDE rename, extract method, move class) are underused by juniors who do edits manually and introduce subtle errors. Seniors rely on tooling and run tests after every single mechanical step, not just at the end.
  • Technical debt conversations in NZ SME environments are often won by framing refactoring as insurance rather than craft — "this will cut the cost of the next three features by 40%" lands better than "the code is messy."

Context guide

How the right level of Refactoring effort changes based on team context.

Context Priority Why
NZ government agency (e.g. Revenue NZ, Benefits NZ, TransitNZ) inheriting a vendor-built system Essential Vendor handover often lacks documentation and tests. An in-house team must establish characterisation tests then refactor incrementally to make compliance rule changes safe — legislation does not wait for a clean codebase.
Consultancy delivering a fixed-scope project for an NZ SME High The client's in-house team inherits the codebase after delivery. Refactoring during build protects the consultancy's reputation and reduces warranty-period support calls — clean handover is a differentiator in a small market.
Fintech or insurtech team (e.g. KiwiSaver provider, CoverNZ-integrated platform) adding regulatory features Essential Financial calculation logic that is hard to read is dangerous. Preparatory refactoring before each compliance change reduces the risk of a mis-implemented rule causing incorrect payments or audit failures under the Financial Markets Conduct Act.
Start-up team under pressure to ship an MVP Medium Speed is legitimate here, but Boy Scout Rule discipline (leave each file slightly cleaner) avoids accumulating debt that will block the second product iteration. Large refactoring sprints rarely survive the next investor milestone — embed the habit instead.
Stable, low-change internal tool with no active development Low Refactoring delivers value when code is touched. If a tool is rarely modified, the risk of introducing a regression via refactoring outweighs the benefit. Prioritise test coverage and documentation instead.
Agile team approaching a large feature sprint with known high-churn modules High Preparatory refactoring before a sprint that will heavily modify a module reduces merge conflicts, shortens code review time, and makes the feature work faster. The investment in the sprint before the sprint consistently pays back within the same quarter.

Trade-offs

What you gain and what you give up when you adopt Refactoring.

Advantage Disadvantage Use instead when…
Reduces the cognitive load of every future change — clean code is faster to read, debug, and review Upfront time cost is real and visible to stakeholders; benefits compound invisibly over months, making it hard to justify to non-technical product owners The module is being deprecated within one or two sprints — invest in the replacement rather than cleaning code that will be deleted
Makes the codebase safer to extend — well-named, single-responsibility modules are less likely to harbour hidden bugs when new behaviour is added Requires an adequate test safety net to be safe; attempting it without tests in place can introduce regressions that are invisible until production Test coverage is below a safe threshold and there is no time to write characterisation tests first — document the technical debt, raise it in sprint planning, and schedule it rather than refactoring blind
Improves onboarding speed — new team members contribute faster when the codebase communicates intent rather than requiring tribal knowledge Structural changes in shared modules cause merge conflicts for teammates working in parallel — coordination overhead can be higher than the refactoring value in a busy sprint Multiple developers have active branches touching the same module — defer to after feature branches merge, or use feature flags to isolate the structural change from behaviour changes
Surfaces hidden bugs — complex code that is clarified during refactoring often exposes assumptions and edge cases that were silently wrong Temptation to fix bugs mid-refactor is high and must be actively resisted — mixed commits destroy the audit trail and make post-deploy diagnosis much harder The underlying design is fundamentally wrong (wrong abstraction, wrong data model) — a targeted rewrite of a small, bounded module may be faster and cleaner than iterative refactoring toward a correct structure

Enterprise reality

How refactoring changes at 200–300-developer scale in NZ enterprise — banks, government agencies, and telcos.

  • Refactoring safety nets are automated at scale: large NZ banks such as Harbour Bank run mandatory static-analysis gates (SonarQube or equivalent) in CI that block merges if complexity metrics, duplication thresholds, or test-coverage floors are breached — the quality signal that a small team gets from a code review is enforced by tooling before a human even looks at the PR.
  • Governance requirements introduce a hard constraint absent from small teams: under the New Zealand Information Security Manual (NZISM) and PCI DSS, every structural change to a system in scope must produce an audit trail linking the refactoring commit to a change-management ticket, and a separate approver must sign off before deployment — meaning a "quick rename" can take two days end-to-end if the change-management queue is busy.
  • Tooling that handles volume: enterprise teams rely on automated IDE refactoring (IntelliJ structural-search-and-replace, ReSharper, Eclipse JDT) combined with mutation-testing frameworks (PIT, Stryker) to verify that the test suite actually detects behaviour changes — at 300+ developers, manual test review cannot scale, and a regression in a shared library can cascade across dozens of dependent services in the same afternoon.
  • Cross-squad coordination becomes the dominant cost: when a shared module is touched across 10+ squads working in parallel — common in TeleNZ or HealthNZ platform teams — a structural refactoring must be scheduled, announced, and feature-flagged so active branches can merge before the rename lands; teams that skip this step routinely face three to five days of merge-conflict resolution spread across the organisation, with a real dollar cost in delayed sprint velocity.

What I would do

Professional judgment — when to adopt Refactoring, when to adapt it, and what to watch for.

If…
I was QA lead on an Revenue NZ (Revenue NZ) PAYE recalculation project and the team wanted to add new tax-bracket rules into a 400-line method full of nested conditionals and magic numbers
I would…
Block the feature branch from starting until we had written characterisation tests for the existing method output against known tax-year fixtures, then lobby for a one-day preparatory refactoring task to extract each tax rule into a named function. The argument to the product owner: one mis-implemented bracket rule affects every employer in the country — the day spent cleaning is insurance, not indulgence.
If…
I was a senior tester on a Pacific Bank mobile banking project and a developer raised a PR labelled "refactoring" that also silently changed the rounding behaviour on fee calculations because they spotted the bug while cleaning the code
I would…
Request the PR be split immediately: one commit for the structural refactoring (no behaviour change, existing tests remain green), a separate commit for the rounding fix with a regression test added. In a financial context, a behaviour change mixed into a refactoring commit can mask a real defect in audit logs and makes it impossible to confirm whether the rounding change was intentional. This is not pedantry — it is the audit trail that protects both the developer and the bank.
If…
I was test lead on an CoverNZ injury claims platform and the team proposed a two-week dedicated refactoring sprint to modernise the legacy claims-routing engine, with no feature work planned
I would…
Approve the sprint but insist on two conditions: first, an automated regression suite covering all claimant-facing routing rules must exist and be green before a single line of structure changes; second, no PR may touch more than three files at once. I would also reframe the sprint goal as "make the next claims-rule change take one day instead of a week" — measurable and business-relevant — rather than "clean the code," which invites scope creep and gold plating.

The bottom line: Refactoring is professional discipline, not a luxury — but it earns trust by being disciplined itself: separate commits, green tests first, and a specific named goal for every session rather than open-ended "cleanup."

Best Practices

✓ What experienced practitioners do
  • ✓ Write (or confirm) tests around the area before touching a single line of structure — a green suite is your permission slip to start.
  • ✓ Make refactoring commits separate from feature and bug-fix commits so blame history stays useful and rollbacks are clean.
  • ✓ Use IDE-level automated refactorings (rename, extract method, inline variable) instead of manual edits — they are semantically aware and far less error-prone.
  • ✓ Timebox each refactoring session: agree on a small, named goal ("extract payment validation into its own class") and stop when that goal is met, not when the code is "perfect."
  • ✓ Follow the preparatory refactoring pattern — clean the structure before adding new behaviour, not after. The new feature becomes trivial once the surrounding code is clear.
  • ✓ Track code churn metrics (files changed most often per sprint) — high-churn files are your highest-value refactoring targets and the most likely source of production defects.
  • ✓ Socialise refactoring intent with the team before a session that touches shared modules; surprise structural changes in shared areas cause painful merge conflicts.
  • ✓ Document the "why" in commit messages — "extract calculateGST() — duplicated in 6 places, needed by upcoming returns feature" justifies the change for future reviewers who have no context.

Senior engineer insight

Teams that refactor well treat it as an embedded discipline — every feature branch includes a few minutes of structural clean-up on files they touch, not a separate calendar event. What separates them from teams that struggle is not skill but habit: they have internalised the preparatory refactoring pattern, cleaning before adding, so new behaviour lands on stable ground. The specific pattern that works reliably in NZ consultancy contexts is the strangler fig approach for larger restructures — introduce the new cleaner structure alongside the old, route traffic incrementally, and remove the old code only when the new code is proven.

The most common mistake: conflating refactoring with opportunistic bug-fixing. The moment you also fix a defect mid-refactor, you can no longer tell whether a failing test caught a regression or just revealed the bug you introduced. Keep the commits separate — always.

From the field

A Wellington-based insurance platform team inherited a monolith with five years of accumulated NZ compliance logic — GST calculations, CoverNZ levy rules, and policy renewal conditions all tangled together in 3,000-line service classes. They declared a "refactoring sprint" without first establishing a test safety net, assuming the codebase's manual regression pack would catch anything critical. Two weeks in, a renamed internal method broke the premium calculation in a way the regression testers didn't catch until a customer reported it — the manual pack covered happy paths but not edge-case policy types. The team rebuilt their approach: wrote characterisation tests around every module before touching structure, ran the CI suite after every individual extract-method step, and adopted a rule that no refactoring PR could touch more than two files. Subsequent refactoring sprints ran without production incidents, and the time-to-add-new-compliance-rules dropped by 60% over the next quarter.

Common Misconceptions

❌ Myth: Refactoring is something you do in a dedicated sprint when the codebase gets bad enough.

Reality: Refactoring is a continuous, incremental habit — like washing dishes after each meal rather than once a month. Dedicated "refactoring sprints" are a symptom that the continuous habit was never established. They also frequently get cancelled when the next urgent feature arrives.

❌ Myth: You need permission from the product owner before refactoring.

Reality: Small, local refactorings (renaming a variable, extracting a method within a file you already own) are part of professional craftsmanship and require no approval — they are no different from choosing to write a clear comment. Approval is sensible only for multi-file or cross-module restructuring that affects team members or delivery timelines.

❌ Myth: Refactoring is risk-free because you are not changing behaviour.

Reality: Refactoring intends not to change behaviour, but without a comprehensive test suite it absolutely can. Subtle bugs can hide in poorly-covered code paths, and a well-intentioned rename can break callers in other modules. The safety net — automated tests, IDE tooling, small incremental steps — is what makes refactoring low-risk, not the act itself.

Why teams fail here

  • Refactoring without a green test suite — teams convince themselves small structural changes are safe without tests; a renamed method breaks a caller in a module nobody checked, and the regression surfaces in production days later.
  • Scope creep in the refactor sprint — what starts as "extract the payment service" becomes a full architectural overhaul mid-sprint; the PR grows to 50 files, reviewers can't tell what changed, and the risk of introducing a regression multiplies with every unreviewed file.
  • Mixing behaviour changes with structural changes — fixing bugs or adding logging inside a refactoring commit destroys the audit trail; when a defect appears post-merge, nobody can determine whether it came from the refactor or the behaviour change.
  • Refactoring shared modules without team coordination — restructuring a utility class that three other developers have open branches against causes painful merge conflicts and often results in the refactoring being reverted just to unblock delivery, leaving the debt exactly where it was.

Key takeaway

Refactoring done well is not a clean-up task you schedule when the code gets painful — it is the professional habit of leaving every module slightly more navigable than you found it, backed by a test suite that makes "I didn't break anything" a provable statement rather than an optimistic guess.

Self-Check

Q: Your team is about to add a KiwiSaver withdrawal eligibility feature to a five-year-old financial portal. The surrounding calculation code is deeply nested and uses single-letter variable names. Should you refactor first, add the feature, or do both at the same time, and why?

A: Refactor first using the preparatory refactoring pattern — clean the structure, then add the new behaviour. Doing both at once conflates two concerns, making code review harder and regression risk invisible. Once the eligibility logic is clearly named and the structure is readable, the new KiwiSaver rules become a predictable extension rather than a guess in tangled code.

Q: An Revenue NZ tax filing API module is failing under load. Your team lead suggests a refactoring sprint to fix it. Should you accept this plan, and what is the key difference between refactoring and performance tuning?

A: Challenge the plan — refactoring preserves observable behaviour and does not change throughput or resource usage by definition. Performance tuning is a separate activity that changes runtime characteristics: optimising queries, caching responses, adjusting concurrency. Conflating the two in one sprint obscures what is actually being done and makes it impossible to attribute any improvement. Agree on which problem you are solving first, then choose the right tool.

Q: What is the key difference between refactoring and rewriting, and when is rewriting the correct choice?

A: Refactoring preserves working behaviour incrementally — you improve structure one small step at a time, with tests confirming nothing breaks. Rewriting discards existing code and rebuilds from scratch, losing accumulated bug-fixes and edge-case handling. Rewriting is justified only when the existing codebase is so structurally incoherent that refactoring paths cannot safely be found — for example, undocumented legacy code with no tests and no living authors. In most NZ agency and in-house contexts, this bar is rarely met, and a full rewrite is a higher-risk option than it appears.

Q: A developer on your Benefits NZ case management project says: "We don't need tests before refactoring — we're just renaming variables and extracting methods, so nothing can break." What is wrong with this reasoning, and how do you respond?

A: This is the most common and dangerous refactoring misconception. Even mechanical changes like rename can break callers in other modules that the developer has not seen; extract method can subtly alter scope if the extracted block references shared mutable state. Tests are not a safety measure for when you expect something to break — they are proof that nothing broke. The correct response is: write or confirm a passing test suite first, use IDE-level automated refactoring tools (which are semantically aware), and run the suite after each individual step, not just at the end.

How this has changed

The field moved. Here is how Refactoring evolved from its origins to current practice.

1990s

Refactoring is an informal practice — developers clean up code as part of feature work. Martin Fowler systematises and names common refactoring patterns while working with Ward Cunningham and Kent Beck.

1999

Fowler publishes "Refactoring: Improving the Design of Existing Code" with a catalogue of named patterns (Extract Method, Rename Variable, Move Field). The practice gains a shared vocabulary.

2001

IDE refactoring support in IntelliJ IDEA enables automated refactoring — renaming a variable updates all references instantly. Refactoring becomes low-risk and fast.

2010

The Boy Scout Rule (leave the campsite cleaner than you found it) enters engineering culture. Refactoring becomes a continuous practice rather than a dedicated phase.

Now

AI coding tools suggest refactoring automatically — detecting code smells and generating refactored versions for review. Testing remains the safety net: comprehensive tests make refactoring safe regardless of who or what does it.

← Back to Agile Techniques