Lesson 1
Eliminating Duplicated Code: Extract Functions and Refactor Magic Numbers
Introduction and Context Setting

Welcome to our lesson on eliminating duplicated code and enhancing maintainability through method extraction and refactoring magic numbers. Writing clean and maintainable code is crucial in modern software development, and refactoring is a key practice that helps achieve these goals. This lesson focuses on identifying duplicate code segments and restructuring them to improve readability and maintainability.

Throughout this course, we will be using Java, a popular object-oriented programming language known for its portability and robustness. We'll leverage JUnit, a widely-used testing framework, for test-driven development (TDD), and Mockito, a flexible mocking library, to facilitate writing reliable tests and manage code evolution effortlessly.

This lesson picks up from an existing ShoppingCart example to concentrate on the Refactor step of the Red-Green-Refactor cycle. Let's dive into identifying and refactoring duplicated code to enhance the codebase's quality.

What are Code Smells and How Do You Eliminate Them?

Before we delve into specific "smells," it's important to understand what "code smells" are. Code smells are indicators in the code suggesting deeper problems, such as poorly structured code or potential defects, though they aren't bugs themselves. Common examples include duplicate code, long methods, and magic numbers. These signals can hinder code readability and maintainability and may eventually lead to significant issues.

Refactoring patterns provide structured techniques to address these code smells, improving the overall quality of the codebase while maintaining its behavior. By employing these patterns, we can systematically transform problematic sections into cleaner, more efficient code. In this lesson, we'll target duplications by leveraging refactoring patterns, such as method extraction and refactoring magic numbers, to enhance the clarity and adaptability of our code. We can rely on our tests to ensure that we don't break any existing functionality!

Understanding Code Duplication

"Code Duplication" is a code smell that occurs when similar or identical code segments are repeated across a codebase, leading to maintenance difficulties and bugs. Consider constant values spread throughout a codebase without clear labeling. This mirroring phenomenon complicates updates or adjustments, requiring changes in multiple places, and increases the risk of errors.

Adhering to the DRY (Don't Repeat Yourself) principle ensures each piece of knowledge has a single, unambiguous representation within the system. This leads to better maintainability and understandability, reducing troubleshooting and debugging efforts. Remember, refactoring to eliminate code duplication fits into our TDD workflow, ensuring minimal disturbance to existing functionalities.

Example: Extracting Methods

Let's explore how to extract a common logic block as a standalone method using our ShoppingCart class. Initially, we have repetitive logic calculating the total cost for each item, considering possible discounts. We start with a set of passing tests where there is clear duplication in the implementation.

Refactor - Optimize the Implementation

During refactoring, we integrate calculateItemCost into the ShoppingCart class for broader adoption across all methods needing this logic. With the TDD mindset, all functionality remains intact while elevating code quality.

Java
1import java.util.ArrayList; 2import java.util.List; 3 4public class ShoppingCart { 5 private List<CartItem> items = new ArrayList<>(); 6 7 public void addItem(String name, double price, int quantity) { 8 items.add(new CartItem(name, price, quantity)); 9 } 10 11 public double calculateTotal() { 12 double total = 0; 13 14 for (CartItem item : items) { 15 double itemTotal = item.getPrice() * item.getQuantity(); 16 17 if (item.getQuantity() >= 5) { 18 itemTotal *= 0.9; // 10% discount 19 } 20 21 total += itemTotal; 22 } 23 24 return total; 25 } 26 27 public double calculateTotalWithStudentDiscount() { 28 double total = 0; 29 30 for (CartItem item : items) { 31 double itemTotal = item.getPrice() * item.getQuantity(); 32 33 if (item.getQuantity() >= 5) { 34 itemTotal *= 0.9; // 10% discount 35 } 36 37 total += itemTotal; 38 } 39 40 // Apply student discount 41 return total * 0.85; // 15% student discount 42 } 43}

The repetitive logic resides within the calculateTotal and calculateTotalWithStudentDiscount methods.

Refactor: Extract Method

We'll extract a method called calculateSubtotal, which can be used by calculateTotal and calculateTotalWithStudentDiscount to eliminate duplication.

Java
1import java.util.ArrayList; 2import java.util.List; 3 4public class ShoppingCart { 5 private List<CartItem> items = new ArrayList<>(); 6 7 public void addItem(String name, double price, int quantity) { 8 items.add(new CartItem(name, price, quantity)); 9 } 10 11 private double calculateSubtotal() { 12 double total = 0; 13 14 for (CartItem item : items) { 15 double itemTotal = item.getPrice() * item.getQuantity(); 16 17 if (item.getQuantity() >= 5) { 18 itemTotal *= 0.9; // 10% discount 19 } 20 21 total += itemTotal; 22 } 23 24 return total; 25 } 26 27 public double calculateTotal() { 28 return calculateSubtotal(); 29 } 30 31 public double calculateTotalWithStudentDiscount() { 32 double subtotal = calculateSubtotal(); 33 34 // Apply student discount 35 return subtotal * 0.85; // 15% student discount 36 } 37}
Example: Refactoring Magic Numbers

Next, we'll address "Magic Numbers," which are the oddly specific numbers that appear in your code. Magic Numbers can cause maintenance headaches when these values need to change, especially if used in multiple places. Here's an example:

Java
1public static double calculateItemCost(CartItem item) { 2 double itemTotal = item.getPrice() * item.getQuantity(); 3 4 if (item.getQuantity() >= 5) { 5 itemTotal *= 0.9; 6 } 7 8 return itemTotal; 9}

There are two "Magic Numbers" in this code that make it less maintainable:

  • 5
  • 0.9
Refactor: Extract Magic Numbers

The solution to magic numbers is simple: elevate them to well-defined constants that can be broadly used across the codebase.

Java
1public class ShoppingCart { 2 private static final int BULK_DISCOUNT_THRESHOLD = 5; 3 private static final double BULK_DISCOUNT_RATE = 0.9; // 10% discount 4 5 public static double calculateItemCost(CartItem item) { 6 double itemTotal = item.getPrice() * item.getQuantity(); 7 8 if (item.getQuantity() >= BULK_DISCOUNT_THRESHOLD) { 9 itemTotal *= BULK_DISCOUNT_RATE; 10 } 11 12 return itemTotal; 13 } 14}
Review, Summary, and Preparation for Practice

In this lesson, we explored the importance of refactoring to eliminate code duplication and magic numbers. By adhering to the TDD cycle — Red (write a failing test), Green (implement with minimal code), and Refactor (improve quality while ensuring behavior remains unchanged) — we ensure our code remains both functional and maintainable.

As you prepare for upcoming practice exercises, focus on applying these skills to strengthen your ability to manage complex codebases. Consistently apply the principles of DRY and TDD to maintain robust, adaptable code. Keep practicing and mastering these principles, and you’ll soon find yourself developing highly efficient and scalable applications in Java.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.