Fundamentals 30 min read

Mastering Code Refactoring: When, Why, and How to Clean Up Your Java Projects

This article explains the concept of refactoring, distinguishes it from performance optimization, outlines the purposes, timing, and challenges of refactoring, and provides concrete techniques, patterns, and IDE tools—complete with code examples—to help developers improve code structure, readability, and maintainability.

Architect
Architect
Architect
Mastering Code Refactoring: When, Why, and How to Clean Up Your Java Projects

Discussion

Refactoring is the process of changing a code’s internal structure without altering its external behavior, aiming to improve design while minimizing the risk of errors. This article focuses on small‑ to medium‑scale refactoring in typical Java projects.

Refactoring improves readability and maintainability; performance optimization targets execution speed. The two can overlap, but they are not identical and some refactorings may even degrade performance.

Purpose, Timing, and Difficulties of Refactoring

Goals of Refactoring

Improve code structure and readability.

Increase extensibility.

Reduce the risk of bugs when modifying code.

When to Refactor

Typical moments include:

Small refactoring when adding new features : after a piece of code has been duplicated 3‑5 times, consider renaming, extracting variables or methods, simplifying parameters, or removing duplicate logic. Apply immediately for low‑risk changes; for higher‑risk changes, record the task and refactor later.

During code review : experienced teammates can share knowledge and inspire improvements.

Planned, purposeful refactoring : insert small‑scale refactoring into regular tasks; allocate dedicated time and design for large‑scale refactoring.

When production issues appear : incidents often expose coupling problems; use them as an opportunity to decouple.

When NOT to refactor : if rewriting is easier than refactoring, or the code is stable behind an interface and rarely changed.

Refactoring Challenges

Convincing product owners : explain long‑term benefits such as faster future development and reduced hidden risks.

Feature duplication during refactoring : assess whether a new feature can wait until after refactoring; adopt incremental, small‑step refactoring to minimise impact.

Incomplete or abandoned refactoring : plan compatibility between old and new logic; decide whether to keep parallel implementations or retire old logic.

Controlling refactoring risk :

Ensure behavior consistency before and after refactoring using safe refactor tools, unit tests, functional tests, regression tests, and traffic replay.

Reduce impact of potential issues with canary releases, feature toggles, and monitoring alerts.

Refactoring that involves database schema : design migration and rollback plans in advance.

Common Refactoring Scenarios and Techniques

Long Parameter Lists

Example of a method with 23 parameters that is hard to reuse:

public List<WorkAuditDetail> workCal(Long groupID, Long orgID, Long startDate, Long endDate, Long month,
    Map<Long, WorkEmpDto> empMap, List<Long> festivalDates, Map<String, String> holidayItemMap,
    Multimap<Long, WorkOrderDto> works, List<Long> employeeIdList, Multimap<Long, WorkEmpDto> employeeMultimap,
    Map<Long, Map<String, Integer>> empHolidayRemainNumMap, Multimap<Long, HolidayInfoDto> holidayInfoMultimap,
    Multimap<Long, CheckTimeDto> checkTimeMultimap, List<WorkAuditDetail> workAudits,
    List<WorkAuditDetail> workAuditsMonth, List<DataValue> checkInRuleIds,
    List<DataValue> restItemRuleIds, Map<Long, EmpLendRestDto> empLendRestDtoMap,
    Map<Long, List<HolidayItemDto>> empHolidayRuleMap, List<HolidayInfoDto> holidayInfoYearList,
    Map<Long, BigDecimal> empRemainStoreDateMap) {
    // ...
}

Optimization 1: Reduce parameters by extracting retrieval logic

public List<WorkAuditDetail> workCal(Long groupID, Long orgID, Long startDate, Long endDate, Long month,
    List<Long> employeeIdList) {
    List<Long> festivalDates = workerDataService.getFestivalDates(groupID);
    Map<String, String> holidayItemMap = workerDataService.getHolidayItemMap(startDate, endDate, employeeIdList);
    // ...
}

Pros: fewer parameters, complex retrieval logic encapsulated. Cons: fetching data each call may affect performance. Suitable when parameter logic is simple.

Optimization 2: Encapsulate parameters into an object

public List<WorkAuditDetail> workCal(EmployeeDataContext employeeDataContext) {
    List<Long> festivalDates = employeeDataContext.getFestivalDates();
    Map<String, String> holidayItemMap = employeeDataContext.getHolidayItemMap();
    // ...
}

@Data
public class EmployeeDataContext {
    private Long groupID;
    private Long orgID;
    private Long startDate;
    private Long endDate;
    private Long month;
    private String operator;
    private List<Long> festivalDates;
    private Map<String, String> holidayItemMap;
    // ...
}

Pros: adds parameters without changing all callers. Cons: the object can become bulky over time.

Optimization 3: Rich domain model with lazy loading

// Build context first
EmployeeDataContext ctx = new EmployeeDataContext(groupID, employeeIdList, endDate, endDate);
List<WorkAuditDetail> result = workCal(ctx);

public List<WorkAuditDetail> workCal(EmployeeDataContext ctx) {
    List<Long> festivalDates = ctx.queryFestivalDates();
    Map<String, String> holidayItemMap = ctx.queryHolidayItemMap();
    // ...
}

public class EmployeeDataContext {
    private Long groupID;
    private Long orgID;
    private Long startDate;
    private Long endDate;
    private Long month;
    private List<Long> festivalDates;
    private Map<String, String> holidayItemMap;
    // lazy getters omitted for brevity
}

Pros: convenient reuse. Cons: may introduce hidden lazy‑loading costs.

Optional or Default Parameters

public void pushGoods() {
    Integer goodsSource = goodsService.getGoodsSource(goods, null, null);
    // ...
}

public class GoodsService {
    public int getGoodsSource(Goods goods, Set<Long> labelIdSet, PriceConfig priceConfig) {
        // ...
    }
}

Refactor by overloading the method and delegating to the full‑parameter version:

public void pushGoods() {
    Integer goodsSource = getGoodsSource(goods);
    // ...
}

public class GoodsService {
    public int getGoodsSource(Goods goods) {
        return getGoodsSource(goods, Collections.emptySet(), getDefaultPriceConfig());
    }
    public int getGoodsSource(Goods goods, Set<Long> labelIdSet, PriceConfig priceConfig) {
        // ...
    }
}

Simplify Conditional Logic

Extract functions from complex conditions

// before
if (data.before(SUMMER_START) || date.after(SUMMER_END)) {
    charge = quantity * _winterRate + _winterServiceCharge;
} else {
    charge = quantity * _summerRate;
}
// after
if (notSummer(date)) {
    charge = winterCharge(quantity);
} else {
    charge = summerCharge(quantity);
}

Merge multiple guard clauses

if (employee.seniority < 2) return 0;
if (employee.monthsDisabled > 12) return 0;
if (employee.isPartTime) return 0;
// after refactor
if (isNotEligibleForDisability()) return 0;

private boolean isNotEligibleForDisability() {
    return employee.seniority < 2 || employee.monthsDisabled > 12 || employee.isPartTime;
}

Guard clauses instead of deep nesting

public static String checkEntry(boolean hasTicket, int age, boolean hasID, boolean isEvening) {
    if (!hasTicket) return "You need a ticket first.";
    if (age < 18) return "You must be 18+ to attend.";
    if (isEvening && !hasID) return "Bring ID for evening events.";
    return "Welcome!";
}

Strategy pattern to replace conditionals

public interface AccessStrategy { String checkAccess(); }

public class AdminAccessStrategy implements AccessStrategy { public String checkAccess() { return "Admin can access all features."; } }
public class UserAccessStrategy implements AccessStrategy { public String checkAccess() { return "User can access limited features."; } }
public class GuestAccessStrategy implements AccessStrategy { public String checkAccess() { return "Guest can only view home page."; } }

public class AccessStrategyFactory {
    public static AccessStrategy getStrategy(String role) {
        switch (role) {
            case "admin": return new AdminAccessStrategy();
            case "user":  return new UserAccessStrategy();
            case "guest": return new GuestAccessStrategy();
            default: throw new IllegalArgumentException("Unknown role: " + role);
        }
    }
}

public class AccessManager {
    public String getAccessLevel(String role) {
        return AccessStrategyFactory.getStrategy(role).checkAccess();
    }
}

Using an enum to bind role codes to strategies further simplifies the factory:

public enum UserRole {
    ADMIN("admin", new AdminAccessStrategy()),
    USER("user", new UserAccessStrategy()),
    GUEST("guest", new GuestAccessStrategy());
    private final String code;
    private final AccessStrategy strategy;
    UserRole(String code, AccessStrategy strategy) { this.code = code; this.strategy = strategy; }
    public static UserRole getByCode(String code) {
        for (UserRole r : values()) if (r.code.equalsIgnoreCase(code)) return r;
        throw new IllegalArgumentException("Unknown role code: " + code);
    }
    public AccessStrategy getStrategy() { return strategy; }
}

public class AccessStrategyFactory {
    public static AccessStrategy getStrategy(String roleCode) {
        return UserRole.getByCode(roleCode).getStrategy();
    }
}

Shotgun Surgery (Scattered Modifications)

Occurs when a single change requires modifications in many classes or modules, indicating excessive coupling.

Consolidate duplicated logic into a utility class or service.

Introduce an intermediate layer (e.g., a Redis wrapper) to centralise operations.

Use configuration‑driven behaviour for large conditional blocks (e.g., payment channel rules).

Long Functions

Short functions improve readability, sharing, and flexibility.

Use IDE extract method to split large functions into smaller ones.

Extract reusable logic into separate methods, apply AOP, or pass functional interfaces (Runnable, Consumer, Function) to encapsulate common steps.

Define clear boundaries to lower coupling: Java Observer, Spring events, MQ messaging, or job schedulers like XXL‑Job for asynchronous processing.

Dead Code

Dead code adds mental overhead without affecting runtime. Detect and safely delete it using IDEA’s built‑in inspection “Unused declaration”.

Refactoring Tools

IDEA Native Features

Rename (Shift+F6)

Extract Method (Ctrl+Alt+M)

Inline Variable/Method (Ctrl+Alt+N / Alt+Shift+I)

Change Signature (Ctrl+F6)

Extract Interface/Class

Move Refactoring

Pull Up / Push Down

Safe Delete

Introduce Variable / Constant / Field (Ctrl+Alt+V, Ctrl+Alt+C, Ctrl+Alt+F)

Optimize Imports (Ctrl+Alt+O)

Structure Search & Replace

IDEA Refactor Menu
IDEA Refactor Menu

Other IDEA Plugins

Lombok – reduces boilerplate via annotations.

Alibaba Java Coding Guidelines – enforces coding standards and provides auto‑fixes.

CodeGlance – adds a minimap for quick navigation.

SequenceDiagram – generates UML sequence diagrams from code.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSoftware Engineeringbest practicescode qualityrefactoringIDE tools
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.