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.
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
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
