What’s New – Digma 1.0 2023
The entire Digma team has been working out on our first official release! Here are some of the highlights we’ve added to Digma over the past few months
By following specific steps, techniques, and the correct tooling, you can sidestep potential refactoring pitfalls, preventing the emergence of “horror stories” during refactoring.
What Is Java Code Refactoring?
Java Code Refactoring Techniques.
Renaming Variables and Methods.
Remove Magic Numbers and Strings.
13 code refactoring best practices.
Java Code refactoring is the practice of restructuring and improving existing code by making small incremental changes that do not impact the external behavior of the code. Code refactoring involves changing the internal structure of the code to improve readability, performance, maintainability, and efficiency without breaking the code’s functionality.
Martin Fowler is a software developer and author who has written extensively about design and refactoring code. In his book titled “Refactoring Improving the Design of Existing Code” He describes refactoring as follows
“Refactoring is a controlled technique for improving the design of an existing code base. Its essence is applying a series of small behavior-preserving transformations, each of which “too small to be worth doing”. However the cumulative effect of each of these transformations is quite significant.” Martin Fowler.
Some of the common changes you can make when refactoring your Java code include:
Choosing meaningful variable and method names is key to making your code more readable.
Code readability is arguably one of the most important aspects of any good codebase. Readable code clearly communicates the intent to the reader, while less readable code increases the likelihood of bugs during refactoring. Using meaningful variable and method names reduces the need for comments and makes collaboration easy.
// Before Refactoring int d = 30; // days int h = 24; // hours in a day // After Refactoring int daysInMonth = 30; int hoursInDay = 24;
The extract method is one of the most common Java code refactoring techniques. The extract method is useful when you have a long, complex method that performs multiple responsibilities. Extracting tasks to separate methods makes the original method less complex and readable. This method also allows you to refactor your code to be more reusable and easily maintainable. Suppose you have a simple class that processes orders and calculates subtotal cost, tax rate, and total cost.
public class OrderProcessor { private List<Item> items; private double taxRate; public double processOrder() { double subtotal = 0; for (Item item : items) { subtotal += item.getPrice(); } double totalTax = subtotal * taxRate; double totalCost = subtotal + totalTax; return totalCost; } }
You can refactor the code above such that the code related to calculating the subtotal, tax, and total cost has been extracted into separate methods: calculateTax, calculateSubtotal, and calculateTotalCost as shown below. This makes the class more readable, modular, and reusable.
public class OrderProcessor { private List<Item> items; private double taxRate; public double processOrder() { double subtotal = calculateSubtotal(); double totalTax = calculateTax(subtotal); return calculateTotalCost(subtotal, totalTax); } private double calculateSubtotal() { double subtotal = 0; for (Item item : items) { subtotal += item.getPrice(); } return subtotal; } private double calculateTax(double subtotal) { return subtotal * taxRate; } private double calculateTotalCost(double subtotal, double totalTax) { return subtotal + totalTax; } }
Magic numbers and strings are values hardcoded into your code. Using such values in your code makes the code less maintainable and increases inconsistencies and errors due to mistypes. Instead of using hardcoded values, refactor your Java code to use constants with descriptive names.
// Before Refactoring if (status == 1) { // ... code for active status ... } // After Refactoring public static final int ACTIVE_STATUS = 1; if (status == ACTIVE_STATUS) { // ... code for active status ... }
Duplicate codes are similar code fragments that appear in multiple places in the codebase. Duplicate code is dreaded in software development due to its negative effects on quality, maintainability, and efficiency. It also increases bug intensity, increases inconsistencies, and bloats the size of your codebase.
Before refactoring
public class NumberProcessor { public int calculateTotal(int[] numbers) { int total = 0; for (int i = 0; i < numbers.length; i++) { total += numbers[i]; } return total; } public double calculateAverage(int[] numbers) { int total = 0; for (int i = 0; i < numbers.length; i++) { total += numbers[i]; } double average = (double) total / numbers.length; return average; } }
After refactoring
public class NumberProcessor { public int calculateSum(int[] numbers) { int total = 0; for (int i = 0; i < numbers.length; i++) { total += numbers[i]; } return total; } public int calculateTotal(int[] numbers) { return calculateSum(numbers); } public double calculateAverage(int[] numbers) { int total = calculateSum(numbers); double average = (double) total / numbers.length; return average; } }
In the refactored example, we have extracted the duplicate logic for summing up the number into the calculateSum method. Both calculateTotal and calculateAverage methods now utilize the calculateSum method to calculate the sum.
Over time, your code gets more dated and maintained by different developers it is easy to end up with complex and convoluted methods. This Java code refactoring technique lets you keep your methods easy to understand, maintain, and extend.
The simplification method may involve identifying overly complex methods that contain nested logic and too many responsibilities before applying the steps below to simplify the method.
Here is a simple Java code refactoring example to illustrate the method simplification technique.
Before simplification
public class ShoppingCart { private List<Item> items; public double calculateTotalCost() { double total = 0; for (Item item : items) { if (item.isDiscounted()) { total += item.getPrice() * 0.8; } else { total += item.getPrice(); } } if (total > 100) { total -= 10; } return total; } }
We can simplify the example above by extracting the calculateItemPrice logic to calculateItemPrice and applyDiscount and simplify the conditionals by using ternary operators instead.
After simplification
public class ShoppingCart { private List<Item> items; public double calculateTotalCost() { double total = 0; for (Item item : items) { total += calculateItemPrice(item); } total -= applyDiscount(total); return total; } private double calculateItemPrice(Item item) { return item.isDiscounted() ? item.getPrice() * 0.8 : item.getPrice(); } private double applyDiscount(double total) { return total > 100 ? 10 : 0; } }
Source: Codecademy
The red-green refactoring method is also commonly called Test Driven Development (TDD). This Java code refactoring technique emphasizes writing tests before writing the actual code that passes the tests already written. This is a repetitive cycle, and on each iteration, you should write new tests before implementing just enough code to pass those tests.
The red-green Java code refactoring technique involves the following three steps.
Red: At this step in the cycle, you have no actual code. You should start by writing failing tests(Red) because there is no implementation to satisfy the test cases.
Green: In this phase, you should write the minimal code necessary to pass the failing test cases. The goal is not to write perfect or optimized code at this stage but to make the test pass (turn the test from red to green).
Refactor: Once you are confident that the code you have implemented passes the tests, you can now focus on refactoring the code to improve performance, structure, and other aspects without breaking the functionality lest the test cases fail.
The cycle repeats itself once you are done with a specific test case. In the new cycle, you can move to the next test case, which involves writing a new test case and just enough code implementation to pass the new test case before refactoring the code to add improvements.
You’ve probably heard about the SOLID principles in object-oriented programming. SOLID is an acronym for the five principles, and one of these is the Single Responsibility principle, represented by the first letter. According to this principle, every class should have one and only one reason for change. In other words, a class should have only one responsibility.
The single responsibility principle is one of the most important aspects of keeping your code maintainable, readable, flexible, and modular. Here is an example of an OrderProcessing class that violates the single responsibility principle since it is also responsible for logging information to a file.
Before refactoring.
public class OrderProcessor { public void processOrder(Order order) { // Processing logic // Logging Logger logger = new Logger(); logger.log("Order processed: " + order.getId()); } }
According to the single responsibility principle, we can refactor this class such that we have and OrderProcessor class responsible for processing and ordering and OrderLogger class responsible for logging.
After refactoring
public class OrderProcessor { public void processOrder(Order order) { // Processing logic // Logging OrderLogger orderLogger = new OrderLogger(); orderLogger.logOrderProcessed(order); } } public class OrderLogger { public void logOrderProcessed(Order order) { Logger logger = new Logger(); logger.log("Order processed: " + order.getId()); } }
Java code refactoring is an important practice that holds the potential to elevate the quality of your code, among other benefits we have highlighted above. However, you should also take care, especially when refactoring a large codebase or working with an unfamiliar one, lest you inadvertently alter software functionality or introduce unforeseen flaws.
Refactoring is an essential practice that contributes to the long-term success of software projects. By incorporating refactoring using the techniques we have discussed above alongside diligent adherence to the best practices into your development cycle, you can turn any complex and tangled codebase into a readable, maintainable, and extensible software solution. However, keep in mind that Java code refactoring is not a one-off activity but an ongoing process that can be integrated into your development cycle.
When should I refactor my Java code?
Code refactoring can be done throughout the software development lifecycle. It’s often a good idea to refactor when you’re adding new features, fixing bugs, or working on a piece of code that’s hard to understand. Regularly setting aside time for refactoring prevents technical debt from accumulating and helps maintain a high-quality codebase.
How do I decide which code to refactor?
You can start by identifying areas of the codebase that are difficult to understand, contain duplicated logic, or are prone to bugs. Look for long methods, complex conditionals, and instances where following design principles like the Single Responsibility Principle would improve code organization.
How can I ensure that refactoring doesn’t introduce bugs?
A strong suite of automated tests is essential to minimize the risk of introducing bugs to your code. Before refactoring, ensure that your code has good test coverage to help you catch any regressions and ensure the code’s functionality remains unaffected.
How to refactor classes in Java?
Begin by identifying the target class that requires improvement, understanding its behavior and analyzing its dependencies. Break down large methods, move methods to suitable classes, and leverage inheritance and interfaces for cleaner structures. Rename, reorganize, and simplify conditionals to boost readability. Thoroughly test changes to ensure that functionality is not broken.
Install Digma for free: Here