JAVA • SOFTWARE CRAFT AND COMMUNICATION

Refactoring — Refactor to reduce duplication and improve clarity

Learn how to reshape existing Java code so it is easier to read, maintain, and extend without changing what it does.

Historical Context & Motivation

Every programmer eventually faces a moment when their code works perfectly, yet reading it feels like untangling a ball of yarn. The practice of refactoring — restructuring existing code without changing its observable behavior — grew out of the realization that software is never truly "done." As programs evolve, quick fixes and copied-and-pasted blocks pile up, making future changes slow and error-prone. Refactoring gives developers a disciplined way to clean up that mess.

Before refactoring had a name, programmers simply called it "cleaning up code." The concept matured through several key moments in software engineering history.

1984
Stepwise Refinement
Niklaus Wirth and others promoted the idea of improving a program's internal structure in small, verifiable steps, laying the intellectual groundwork for refactoring.
1992
Opdyke's PhD Thesis
William Opdyke formally defined "refactoring" in his dissertation, cataloging behavior-preserving transformations for object-oriented code.
1999
Fowler's Refactoring Book
Martin Fowler published "Refactoring: Improving the Design of Existing Code," which became the industry standard reference and popularized the catalog of named refactorings.
2001
Agile Manifesto & XP
Extreme Programming made refactoring a core practice alongside test-driven development, embedding it into everyday workflows rather than treating it as a rare event.
2010s
IDE Automation
Modern IDEs like IntelliJ IDEA and Eclipse introduced one-click refactoring tools—Extract Method, Rename, Inline Variable—making safe transformations almost effortless.

The central question refactoring answers is straightforward: how can you make code clearer and less repetitive while guaranteeing it still does exactly the same thing? The rest of this lesson walks you through the principles, techniques, and hands-on Java examples that answer that question.

Core Principles & Definitions

Refactoring rests on a handful of guiding ideas. Understanding them will help you decide when to refactor, what to change, and — just as important — what to leave alone.

1

Behavior Preservation

The program must produce the same outputs for the same inputs after every refactoring step. Tests are your safety net — run them after each change.
2

DRY — Don't Repeat Yourself

Every piece of knowledge should exist in exactly one place. Duplicated code means duplicated bugs and duplicated effort when requirements change.
3

Small, Safe Steps

Refactor in tiny increments — rename a variable, extract one method — and verify correctness after each step rather than rewriting large chunks at once.
4

Meaningful Names

Variables, methods, and classes should describe their purpose. A name like calculateShippingCost() communicates more than calc() ever could.
5

Single Responsibility

Each method or class should do one thing well. If a method both validates input and saves to a file, split it into two focused methods.
✦ KEY TAKEAWAY
Think of refactoring like reorganizing your bedroom. You are not buying new furniture (no new features). You are rearranging what you already have so you can find things faster and move around without tripping. The room still holds the same stuff — it is just easier to live in.

Visual Explanation — Before & After Refactoring

The diagram below shows a common scenario: a Java class that calculates prices for two customer types. On the left, the duplicated logic is highlighted in red. On the right, the refactored version uses a single shared helper method, shown in green. Notice how the overall structure shrinks and becomes easier to follow.

The red dashed boxes on the left highlight identical calculations copied into two methods. The green dashed box on the right shows the single baseWithTax() helper method that both callers now share.

In the original version, the tax calculation appears twice. If the tax rate changes from 8 % to 9 %, you would need to remember to update both methods — and if you forget one, you introduce a sneaky bug. After the Extract Method refactoring, the shared logic lives in exactly one place. Both callers delegate to it, and a single edit is all that is needed for future changes.

How Refactoring Works — The Extract Method Technique

While refactoring is not a math-heavy topic, there is a precise, repeatable process behind the most common technique: Extract Method. This is the refactoring you will use most often to eliminate duplication. Below is the step-by-step mechanism.

Step-by-Step: Extract Method

  1. Identify the duplicated fragment. Look for blocks of code that appear in more than one place or that do a clearly separable task inside a long method.
  2. Determine inputs and outputs. List every variable the fragment reads (these become parameters) and every value it produces (this becomes the return type).
  3. Create a new method with a descriptive name. Give it the parameters and return type you just identified. A good name explains what the method does, not how.
  4. Move the code into the new method. Copy the fragment into the body of the new method and adjust local variable references.
  5. Replace every occurrence with a call. Everywhere the original code appeared, substitute a call to the new method.
  6. Run your tests. Confirm that all existing tests still pass. If you do not have tests, write at least a few before you start refactoring.
🧪 Why Tests Matter
Refactoring without tests is like performing surgery without an X-ray — you might fix the problem, but you might break something else without realizing it. Even a handful of simple assertEquals() checks can save you from introducing hidden bugs.

Beyond Extract Method, other common refactorings include Rename Variable (swapping a cryptic name like x for a descriptive one like totalPrice), Inline Variable (removing a temporary variable that adds no clarity), and Move Method (relocating a method to the class that owns its data). Each one follows the same pattern: small change, then verify.

Recognizing Code Smells — When to Refactor

Programmers use the term code smell to describe a surface-level pattern in code that hints at a deeper problem. A smell does not mean the code is broken; it means the code could be improved. The diagram below maps the most common smells to the refactoring technique that fixes each one.

Each red box names a common code smell, and the gold arrow points to the green-bordered refactoring that addresses it. Recognizing these smells is the first step toward cleaner code.

Not every smell needs immediate attention. A useful rule of thumb is the Rule of Three: the first time you write something, just write it. The second time you see a similar pattern, make a mental note. The third time, refactor. This prevents both premature optimization and runaway duplication.

Worked Example — Refactoring a Student Grade Calculator

Let us walk through a complete refactoring of a small Java program. Imagine you have a class that prints letter grades for two different assignments, with nearly identical logic in each method.

Refactoring a Grade Calculator
1
Step 1 — Identify the Original (Smelly) CodeWe start with two methods in a class called GradeReport. The method printMidtermGrade(double score) contains: if (score >= 90) return "A"; else if (score >= 80) return "B"; else if (score >= 70) return "C"; else return "F";. The method printFinalGrade(double score) contains the exact same if-else chain. This is the Duplicated Code smell.
Smell identified: identical grade-conversion logic in two methods.
2
Step 2 — Determine Inputs and OutputsThe duplicated fragment reads one variable (score, a double) and produces one result (a String letter grade). So the new method will take a double parameter and return a String.
Input: double score. Output: String letterGrade.
3
Step 3 — Create the New Helper MethodWe write a new private method with a clear name: private String toLetterGrade(double score). We move the if-else chain into it. The full body is: if (score >= 90) return "A"; else if (score >= 80) return "B"; else if (score >= 70) return "C"; else return "F";
New method: private String toLetterGrade(double score)
4
Step 4 — Replace Both OccurrencesIn printMidtermGrade we replace the entire if-else chain with return toLetterGrade(score);. We do the same in printFinalGrade. Each method is now a single, readable line.
Both callers now delegate to toLetterGrade().
5
Step 5 — Verify with TestsWe run our test suite. For example: assertEquals("A", report.printMidtermGrade(95)); and assertEquals("C", report.printFinalGrade(73)); both still pass. The behavior is unchanged, but the code is shorter and the grading logic lives in exactly one place.
All tests pass — refactoring complete!
💡 Bonus Improvement
Notice the magic numbers 90, 80, 70 in the if-else chain. You could refactor further by introducing named constants like private static final double A_THRESHOLD = 90;. Each small improvement compounds over time.

Strengths, Limitations & Tradeoffs

Refactoring is powerful, but it is not a silver bullet. Understanding when it helps — and when it can backfire — will make you a more thoughtful developer.

Refactoring strengths vs. limitations
AspectStrengthsLimitations
ReadabilityCleaner names and shorter methods make code much easier for teammates (and future-you) to understand.Over-extracting can scatter logic across many tiny methods, making the flow harder to follow.
Bug PreventionEliminating duplication means a bug fix in one place fixes it everywhere.Without tests, refactoring itself can introduce new bugs.
VelocityCleaner code is faster to extend with new features because you spend less time deciphering old logic.Refactoring takes time now. On a tight deadline, the short-term cost can feel high.
PerformanceClearer structure makes it easier to identify and optimize bottlenecks later.Extra method calls add negligible overhead, but in rare performance-critical loops it may matter.
✦ KEY TAKEAWAY
Refactoring is like sharpening a knife before cooking. It takes a minute up front, but it makes every cut afterward faster and more precise. Skip it too long and you will struggle with a dull blade — or in code terms, a tangled mess that nobody wants to touch.

Connection to Advanced Software Design

The refactoring skills you are building now lay the groundwork for more advanced software design concepts you will encounter in college-level courses or on the job. The table below shows how the beginner refactoring ideas in this lesson connect to their professional-grade counterparts.

Beginner refactoring → advanced design concepts
This Lesson's ConceptAdvanced Counterpart
Extract Method to remove duplicationDesign Patterns — Template Method and Strategy patterns generalize shared behavior across class hierarchies.
Rename Variable for clarityUbiquitous Language — Domain-Driven Design encourages naming code elements using the language of the business domain.
Single Responsibility per methodSOLID Principles — The Single Responsibility Principle (SRP) applies at the class level, not just the method level.
Replace Magic Number with ConstantConfiguration Management — In production systems, constants often come from config files or environment variables.
Run tests after each stepContinuous Integration (CI) — Automated pipelines run hundreds of tests on every code change before it reaches production.

As you advance, you will find that refactoring is not a one-time activity but an ongoing discipline woven into daily development. Professional teams practice continuous refactoring — improving code quality a little bit with every feature they add, much like keeping a kitchen clean while cooking rather than letting dishes stack up for a week.

Practice Problems

PROBLEM 1 — CONCEPTUAL
In your own words, explain what it means for a refactoring to be "behavior-preserving." Why is this property so important?
PROBLEM 2 — BASIC CALCULATION
A class has two methods that each contain the line double total = price * quantity * 1.07;. Name the code smell, identify the refactoring you would apply, and write the signature of the new helper method you would create.
PROBLEM 3 — INTERMEDIATE
Consider this Java method: public void report(Student s) { System.out.println(s.name + ": " + (s.exam1 + s.exam2 + s.exam3) / 3.0); System.out.println(s.name + " status: " + ((s.exam1 + s.exam2 + s.exam3) / 3.0 >= 60 ? "Pass" : "Fail")); } Identify all code smells and describe, step by step, how you would refactor this method.
PROBLEM 4 — APPLIED
You are building a small e-commerce app. The checkout method and the invoice-printing method both compute shipping cost as: if weight > 50, cost is weight × 0.75; otherwise, cost is weight × 0.50. Recently, the business changed the threshold from 50 to 40, but the developer only updated the checkout method, causing invoices to show wrong amounts. Explain how refactoring could have prevented this bug, and write the Java helper method that both callers should share.
PROBLEM 5 — CRITICAL THINKING
A classmate argues: "Refactoring is a waste of time. If the code works, why touch it? You could introduce bugs." Write a well-reasoned response that acknowledges their concern but explains why regular refactoring is a net positive. Use at least two specific concepts from this lesson to support your argument.

Lesson Summary

Refactoring is the practice of restructuring existing code without changing its observable behavior. Its primary goals are to reduce duplication and improve clarity. The DRY principle (Don't Repeat Yourself) reminds us that every piece of logic should live in one place. Common code smells — Duplicated Code, Long Method, Magic Numbers, Poor Naming, and Feature Envy — signal opportunities to refactor.

The most essential technique is Extract Method: identify duplicated or complex fragments, determine their inputs and outputs, create a new method with a meaningful name, move the code in, and replace every occurrence with a call to the new method. Always work in small, safe steps and run your tests after each change to guarantee behavior preservation. These habits form the foundation for professional software craft and will serve you well in every program you write.

Varsity Tutors • Java • Refactoring — Refactor to reduce duplication and improve clarity