Code refactoring (Refactoring.Guru, JetBrains) - Yash-777/MyWorld GitHub Wiki
Code refactoring jetbrains.com
Refactoring is a process of improving your source code without creating a new functionality. Refactoring helps you keep your code solid , dry, and easy to maintain.
Refactoring.Guru makes it easy for you to discover everything you need to know about refactoring, design patterns, SOLID principles, and other smart programming topics.
Diagrammatic refers to something represented with diagrams. | Theoretical refers to something based on theory or abstract reasoning. |
---|---|
![]() |
Refactoring is a systematic process of improving code without creating new functionality. Refactoring transforms a mess into clean code and simple design. Why Refactor? The more lines found in a method, the harder it’s to figure out what the method does. This is the main reason for this refactoring. Besides eliminating rough edges in your code, extracting methods is also a step in many other refactoring approaches. |
Refactoring Process Performing refactoring step-by-step and running tests after each change are key elements of refactoring that make it predictable and safe.
Refactoring should be done as a series of small changes, each of which makes the existing code slightly better while still leaving the program in working order.
Checklist of refactoring done right way
- The code should become cleaner.
- New functionality shouldn’t be created during refactoring.
- All existing tests must pass after refactoring. Use BDD-style tests.
Code Smells
Code smells are indicators of problems that can be addressed during refactoring. Code smells are easy to spot and fix, but they may be just symptoms of a deeper problem with code.
Diagrammatic representation | Theoretical refers to something based on theory or abstract reasoning. |
---|---|
![]() |
Bloaters are code, methods and classes that have increased to such gargantuan proportions that they are hard to work with. Usually these smells do not crop up right away, rather they accumulate over time as the program evolves (and especially when nobody makes an effort to eradicate them).
|
![]() |
Object-Orientation Abusers All these smells are incomplete or incorrect application of object-oriented programming principles.
|
![]() |
Change Preventers These smells mean that if you need to change something in one place in your code, you have to make many changes in other places too. Program development becomes much more complicated and expensive as a result.
|
![]() |
Dispensables A dispensable is something pointless and unneeded whose absence would make the code cleaner, more efficient and easier to understand.
|
![]() |
Couplers All the smells in this group contribute to excessive coupling between classes or show what happens if coupling is replaced by excessive delegation.
|
Refactoring Techniques
Refactoring techniques describe actual refactoring steps. Most refactoring techniques have their pros and cons. Therefore, each refactoring should be properly motivated and applied with caution.
👍 Composing Methods
Diagrammatic representation | Theoretical refers to something based on theory or abstract reasoning. |
---|---|
![]() |
Composing Methods Much of refactoring is devoted to correctly composing methods. In most cases, excessively long methods are the root of all evil. The vagaries of code inside these methods conceal the execution logic and make the method extremely hard to understand—and even harder to change. The refactoring techniques in this group streamline methods, remove code duplication, and pave the way for future improvements. |
Problem | Solution |
---|---|
Extract Method | |
You have a code fragment that can be grouped together.void printOwing() {
printBanner();
// Print details.
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
} |
Move this code to a separate new method (or function) and replace the old code with a call to the method.void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
} |
Inline Method | |
When a method body is more obvious than the method itself, use this technique.class PizzaDelivery {
// ...
int getRating() {
return moreThanFiveLateDeliveries() ? 2 : 1;
}
boolean moreThanFiveLateDeliveries() {
return numberOfLateDeliveries > 5;
}
} |
Replace calls to the method with the method’s content and delete the method itself.class PizzaDelivery {
// ...
int getRating() {
return numberOfLateDeliveries > 5 ? 2 : 1;
}
} |
Extract Variable | |
You have an expression that’s hard to understand.void renderBanner() {
if ((platform.toUpperCase().indexOf("MAC") > -1) &&
(browser.toUpperCase().indexOf("IE") > -1) &&
wasInitialized() && resize > 0 )
{
// do something
}
} |
Place the result of the expression or its parts in separate variables that are self-explanatory.void renderBanner() {
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIE = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize > 0;
if (isMacOs && isIE && wasInitialized() && wasResized) {
// do something
}
} |
Inline Temp | |
You have a temporary variable that’s assigned the result of a simple expression and nothing more.boolean hasDiscount(Order order) {
double basePrice = order.basePrice();
return basePrice > 1000;
} |
Replace the references to the variable with the expression itself.boolean hasDiscount(Order order) {
return order.basePrice() > 1000;
} |
Replace Temp with Query | |
You place the result of an expression in a local variable for later use in your code.double calculateTotal() {
double basePrice = quantity * itemPrice;
if (basePrice > 1000) {
return basePrice * 0.95;
}
else {
return basePrice * 0.98;
}
} |
Move the entire expression to a separate method and return the result from it. Query the method instead of using a variable. Incorporate the new method in other methods, if necessary.double calculateTotal() {
if (basePrice() > 1000) {
return basePrice() * 0.95;
}
else {
return basePrice() * 0.98;
}
}
double basePrice() {
return quantity * itemPrice;
} |
Split Temporary Variable | |
You have a local variable that’s used to store various intermediate values inside a method (except for cycle variables).double temp = 2 * (height + width);
System.out.println(temp);
temp = height * width;
System.out.println(temp); |
Use different variables for different values. Each variable should be responsible for only one particular thing.final double perimeter = 2 * (height + width);
System.out.println(perimeter);
final double area = height * width;
System.out.println(area); |
Remove Assignments to Parameters | |
Some value is assigned to a parameter inside method’s body.int discount(int inputVal, int quantity) {
if (quantity > 50) {
inputVal -= 2;
}
// ...
} |
Use a local variable instead of a parameter.int discount(int inputVal, int quantity) {
int result = inputVal;
if (quantity > 50) {
result -= 2;
}
// ...
} |
Replace Method with Method Object | |
You have a long method in which the local variables are so intertwined that you can’t apply Extract Method.class Order {
// ...
public double price() {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// Perform long computation.
}
} |
Transform the method into a separate class so that the local variables become fields of the class. Then you can split the method into several methods within the same class.class Order {
// ...
public double price() {
return new PriceCalculator(this).compute();
}
}
class PriceCalculator {
private double primaryBasePrice;
private double secondaryBasePrice;
private double tertiaryBasePrice;
public PriceCalculator(Order order) {
// Copy relevant information from the
// order object.
}
public double compute() {
// Perform long computation.
}
} |
Substitute Algorithm | |
So you want to replace an existing algorithm with a new one?String foundPerson(String[] people){
for (int i = 0; i < people.length; i++) {
if (people[i].equals("Don")){
return "Don";
}
if (people[i].equals("John")){
return "John";
}
if (people[i].equals("Kent")){
return "Kent";
}
}
return "";
} |
Replace the body of the method that implements the algorithm with a new algorithm.String foundPerson(String[] people){
List candidates =
Arrays.asList(new String[] {"Don", "John", "Kent"});
for (int i=0; i < people.length; i++) {
if (candidates.contains(people[i])) {
return people[i];
}
}
return "";
} |
👍 Moving Features between Objects
Diagrammatic representation | Theoretical refers to something based on theory or abstract reasoning. |
---|---|
![]() |
Moving Features between Objects Even if you have distributed functionality among different classes in a less-than-perfect way, there’s still hope. These refactoring techniques show how to safely move functionality between classes, create new classes, and hide implementation details from public access. |
Problem | Solution |
---|---|
Move Method | |
A method in a class is used more in another class than in its own class, indicating that it would be better placed in the class where it is used more frequently.// File: OrderService.java
public class OrderService {
public double calculateDiscount(Order order) {
// Some complex discount calculation logic
return order.getTotal() * 0.1;
}
}
// File: InvoiceService.java
public class InvoiceService {
OrderService order = new OrderService();
public void generateInvoice(Order order) {
// Uses the method from OrderService more
// frequently than OrderService itself
double discount
= order.calculateDiscount(order);
// Additional invoice generation logic
}
} |
The method is moved to the class where it is used more. The original method is either removed or replaced with a call to the new method in the other class.// File: OrderService.java
public class OrderService {
// No longer has calculateDiscount(),
// since it's moved to InvoiceService
}
// File: InvoiceService.java
public class InvoiceService {
double calculateDiscount(Order order) {
// Moved the discount calculation logic here
return order.getTotal() * 0.1;
}
public void generateInvoice(Order order) {
// Now directly calls cD() in the same class
double discount = calculateDiscount(order);
// Additional invoice generation logic
}
} |
Move Field | |
A field is used more in another class than in its own class. | Create a field in a new class and redirect all users of the old field to it. |
Extract Class This refactoring method will help maintain adherence to the Single Responsibility Principle. The code of your classes will be more obvious and understandable. | |
When one class does the work of two, awkwardness results.
public class Invoice {
private String customerName;
private List items;
private List prices;
public Invoice(String customerName) {
this.customerName = customerName;
items = new ArrayList<>();
prices = new ArrayList<>();
}
public void addItem(String item, double price) {
items.add(item);
prices.add(price);
}
public double getTotalAmount() {
return prices.stream().mapToDouble(Double::doubleValue).sum();
}
public void printInvoice() {
//...
}
} |
Instead, create a new class and place the fields and methods responsible for the relevant functionality in it.
// InvoiceItems.java
public class InvoiceItems {
private List items = new ArrayList<>();
private List prices = new ArrayList<>();
public void addItem(String item, double price) {
items.add(item);
prices.add(price);
}
public double getTotalAmount() {
return prices.stream().mapToDouble(Double::doubleValue).sum();
}
}
// Invoice.java
public class Invoice {
private String customerName;
private InvoiceItems invoiceItems = new InvoiceItems();
public void addItem(String item, double price) {
invoiceItems.addItem(item, price);
}
public double getTotalAmount() {
return invoiceItems.getTotalAmount();
}
public void printInvoice() {
//...
}
} |
Inline Class Simplifies the design: The Address class was only used by Employee, so it doesn't need to exist on its own. Reduces complexity: Fewer classes mean easier code maintenance and understanding | |
The Inline Class refactoring is used when a class is no longer useful on its own and can be merged into another class to simplify the design. The class is small, used in only one place, and does not add enough value to justify its existence. In the example below, Address is a small class that is only used in the Employee class. Since it's not doing much, it might be a candidate for inlining.
// File: Address.java
public class Address {
private String street;
private String city;
}
// File: Employee.java
public class Employee {
private String name;
private Address address;
public String getAddressDetails() {
return address.getStreet() + ", " + address.getCity();
}
} |
You can eliminate the class by moving its functionality into the class that uses it. We will inline the Address class into Employee by removing Address and directly adding its fields to Employee .
// File: Employee.java (after refactoring)
public class Employee {
private String name;
private String street;
private String city;
public Employee(String name, String street, String city) {
this.name = name;
this.street = street;
this.city = city;
}
public String getAddressDetails() {
return street + ", " + city;
}
} |
Hide Delegate Delegate in simple words means to assign or entrust a task or responsibility to someone else, usually someone who is more suited or specialized to handle it. | |
In some cases, a class delegates a lot of work to another class, and the client of the class is aware of this delegation. This can lead to unnecessary exposure of the delegation relationship to the client. The refactoring "Hide Delegate" aims to eliminate the need for the client to be aware of the intermediary delegate class.
// File: CustomerAddress.java
public class CustomerAddress {
public String getAddress() {
return "123 Main St";
}
}
// File: Customer.java
public class Customer {
private CustomerAddress address;
public Customer(CustomerAddress address) {
this.address = address;
}
public CustomerAddress getAddress() {
return address;
}
}
// File: Client.java
public class Client {
public static void main(String[] args) {
CustomerAddress customerAddress = new CustomerAddress();
Customer customer = new Customer(customerAddress);
// The client knows about CustomerAddress and directly accesses it
String address = customer.getAddress().getAddress();
System.out.println(address); // Output: 123 Main St
}
} |
Instead of making the client call methods on the delegate class directly, you can hide the delegate behind a method in the delegating class. This allows the delegating class to maintain control over the interaction, making the delegate class less visible to the client.
// File: CustomerAddress.java
public class CustomerAddress {
// No longer needs to be exposed
public String getAddress() {
return "123 Main St";
}
}
// File: Customer.java
public class Customer {
private CustomerAddress address;
public Customer(CustomerAddress address) {
this.address = address;
}
// Hiding the delegate method
public String getAddress() {
return address.getAddress(); // Delegate the call internally
}
}
// File: Client.java
public class Client {
public static void main(String[] args) {
CustomerAddress customerAddress = new CustomerAddress();
Customer customer = new Customer(customerAddress);
// Now the client interacts directly with the Customer class, without knowing about CustomerAddress
String address = customer.getAddress(); // Direct access to getAddress() method
System.out.println(address); // Output: 123 Main St
}
} |
Remove Middle Man JetBrains Removing the "middle man" by simplifying the method calls and eliminating unnecessary indirection. The "Remove Middleman" refactoring simplifies the design by eliminating unnecessary intermediary method calls. | |
A class has too many methods that simply delegate to other objects.// Original call hierarchy:
applicationServiceImpl.validate()
-> applicationDaoImpl.recordExists()
-> jdbcTemplateHelper.recordExists() |
Delete these methods and force the client to call the end methods directly.// After refactoring:
applicationServiceImpl.validate()
-> jdbcTemplateHelper.recordExists() By removing |
Introduce Foreign Method The Introduce Foreign Method refactoring is used when you want to move a method from one class to another class where it is more relevant. This is helpful when a method in a class doesn't logically belong to it but is frequently used in another class. | |
You have a method that logically belongs to another class, but it is currently located in the wrong class. Example Before Refactoring: Let’s say we have a Customer class that checks if the customer is eligible for a discount. But logically, this method should belong in the DiscountCalculator class. // File: Customer.java
public class Customer {
private String name;
private int age;
private boolean isPremium;
// The method should logically belong to DiscountCalculator, not Customer
public boolean isEligibleForDiscount() {
return age > 60 || isPremium; // Discount rule
}
}
// File: DiscountCalculator.java
public class DiscountCalculator {
// This class should have the method to calculate discounts, but it's not there
} |
Move the method to the correct class and change the calling code accordingly. After Refactoring (Introduce Foreign Method): We will move the isEligibleForDiscount() method from Customer to DiscountCalculator class. // File: DiscountCalculator.java (after refactoring)
public class DiscountCalculator {
public boolean isEligibleForDiscount(Customer customer) {
return customer.getAge() > 60 || customer.isPremium();
// Discount rule
}
}
// File: Customer.java (after refactoring)
public class Customer {
private String name;
private int age;
private boolean isPremium;
} |
Introduce Local Extension | |
|
|
Design Patterns are typical solutions to commonly occurring problems in software design. They are blueprints that can be taken and customized to solve a particular design problem in your code.
- Design Patterns in Java - https://github.com/RefactoringGuru/design-patterns-java
- All code should meet the Google Java Style Guide